diff --git a/chatto/.attach_pid30353 b/chatto/.attach_pid30353 new file mode 100644 index 0000000..e69de29 diff --git a/chatto/config/application.properties b/chatto/config/application.properties index e418935..ed7ad66 100644 --- a/chatto/config/application.properties +++ b/chatto/config/application.properties @@ -5,3 +5,4 @@ spring.datasource.username = chatto_user spring.datasource.password = password database-name = chatto_db2 website-url = 192.168.1.13 +test.bindAddress=192.168.1.106 diff --git a/chatto/config/messages.properties b/chatto/config/messages.properties new file mode 100644 index 0000000..45b2e30 --- /dev/null +++ b/chatto/config/messages.properties @@ -0,0 +1 @@ +test.bindAddress=192.168.1.106 diff --git a/chatto/message.properties b/chatto/message.properties new file mode 100644 index 0000000..822a5e2 --- /dev/null +++ b/chatto/message.properties @@ -0,0 +1,2 @@ +#Sat Oct 12 01:13:02 IST 2019 +test.bindAddress=192.168.1.106 diff --git a/chatto/messages.properties b/chatto/messages.properties new file mode 100644 index 0000000..ca1c7f3 --- /dev/null +++ b/chatto/messages.properties @@ -0,0 +1,2 @@ +#Sat Oct 12 01:15:41 IST 2019 +test.bindAddress=192.168.1.106 diff --git a/chatto/pom.xml b/chatto/pom.xml index ec9dfd3..45e7c6d 100644 --- a/chatto/pom.xml +++ b/chatto/pom.xml @@ -84,6 +84,46 @@ 2.3.5 + + org.springframework.boot + spring-boot-starter-cache + + + + + org.ehcache + ehcache + + + + javax.cache + cache-api + + + + com.sun.xml.bind + jaxb-impl + 2.2.11 + + + + com.sun.xml.bind + jaxb-core + 2.2.11 + + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity5 + + + + + + + diff --git a/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java b/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java index ea54bfb..fab183d 100644 --- a/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java +++ b/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java @@ -1,15 +1,13 @@ package org.ros.chatto; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - import org.modelmapper.ModelMapper; import org.ros.chatto.security.AuthenticationSuccessHandlerImpl; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; @PropertySource(value = "classpath:queries.properties") @@ -36,6 +34,16 @@ public class BeanConfigurations { return modelMapper; } + @Bean + public MessageSource messageSource() { + final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasenames("classpath:/messages,file:./config/messages"); + messageSource.setUseCodeAsDefaultMessage(true); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setCacheSeconds(5); + return messageSource; + } + // @Bean // public Connection connection() throws SQLException // { diff --git a/chatto/src/main/java/org/ros/chatto/ChattoApplication.java b/chatto/src/main/java/org/ros/chatto/ChattoApplication.java index 310bbd8..e25052c 100644 --- a/chatto/src/main/java/org/ros/chatto/ChattoApplication.java +++ b/chatto/src/main/java/org/ros/chatto/ChattoApplication.java @@ -1,31 +1,38 @@ package org.ros.chatto; +import java.sql.SQLException; + +import org.ros.chatto.service.DBInitializerService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.cache.annotation.EnableCaching; -@EnableAutoConfiguration @SpringBootApplication +//@EnableCaching public class ChattoApplication extends SpringBootServletInitializer { // @Value("${spring.datasource.url}") // private static String url; - public static void main(String[] args) { + + public static void main(String[] args) throws SQLException { SpringApplication application = new SpringApplication(ChattoApplication.class); addInitHooks(application); // SpringApplication.run(ChattoApplication.class, args); application.run(args); + + } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(ChattoApplication.class); } - static void addInitHooks(SpringApplication application) { + static void addInitHooks(SpringApplication application) throws SQLException { // TBD … // System.out.println("Hello world very loooooooooooooooooooooooooooooooooooooong string"); // String url = environment.getProperty("spring.datasource.url"); diff --git a/chatto/src/main/java/org/ros/chatto/RestAuthenticationEntryPoint.java b/chatto/src/main/java/org/ros/chatto/RESTAuthenticationEntryPoint.java similarity index 96% rename from chatto/src/main/java/org/ros/chatto/RestAuthenticationEntryPoint.java rename to chatto/src/main/java/org/ros/chatto/RESTAuthenticationEntryPoint.java index 83550e9..e0c67f3 100644 --- a/chatto/src/main/java/org/ros/chatto/RestAuthenticationEntryPoint.java +++ b/chatto/src/main/java/org/ros/chatto/RESTAuthenticationEntryPoint.java @@ -12,7 +12,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn import org.springframework.stereotype.Component; @Component -public final class RestAuthenticationEntryPoint +public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { // @Override diff --git a/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java b/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java index e5a1437..8c4b774 100644 --- a/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java +++ b/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java @@ -5,14 +5,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @@ -25,13 +30,15 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { private MyUserDetailsService myUserDetailsService; @Autowired private PasswordEncoder passwordEncoder; - + @Autowired + private UserCache userCache; // @SuppressWarnings("deprecation") @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(myUserDetailsService); + provider.setUserCache(userCache); provider.setPasswordEncoder(passwordEncoder); return provider; } @@ -40,30 +47,24 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { public static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - - @Configuration @Order(1) public static class ApiWebSecurity extends WebSecurityConfigurerAdapter { @Autowired - private RestAuthenticationEntryPoint restAuthenticationEntryPoint; + private RESTAuthenticationEntryPoint authenticationEntryPoint; + @Override protected void configure(HttpSecurity http) throws Exception { - http - .csrf().disable() - .exceptionHandling() - - .and() + http.csrf().disable().exceptionHandling() + + .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // .cors().and() - .antMatcher("/api/**") - .authorizeRequests() + .antMatcher("/api/**").authorizeRequests() // .antMatchers("/perform-login").permitAll() .anyRequest() // .hasAnyRole("USER", "ADMIN", "SUPER_USER") - .authenticated() - .and().httpBasic() - .authenticationEntryPoint(restAuthenticationEntryPoint) + .authenticated().and().httpBasic().authenticationEntryPoint(authenticationEntryPoint) // .and() // .logout().invalidateHttpSession(true).clearAuthentication(true) // .logoutRequestMatcher(new AntPathRequestMatcher("/api/perform_logout")) @@ -79,8 +80,19 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { } +// @Override +// protected void configure(AuthenticationManagerBuilder auth) throws Exception { +// auth.eraseCredentials(false); +// } +// +// public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { +// +// } + } + + @Configuration @Order(2) public static class FormWebSecurity extends WebSecurityConfigurerAdapter { @@ -89,12 +101,12 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() // .antMatchers(HttpMethod.POST, "/api/**").permitAll() - .antMatchers("/", "perform_login","/login*", "/registration", "/perform_registration", "/css/**", "/js/**", - "/images/**") + .antMatchers("/", "perform_login","/logout**" ,"/favicon.ico","/login*", "/registration", "/perform_registration", "/css/**", + "/js/**", "/img/**") .permitAll() // .antMatchers("/","/api**","/api/**","/login*","/registration","/perform_registration","/css/**", "/js/**", "/images/**").permitAll() - .antMatchers("/user/**").hasAnyRole("USER", "ADMIN", "SUPER_USER") - .antMatchers("/admin/**").hasAnyRole("ADMIN", "SUPER_USER") + .antMatchers("/user/**").hasAnyRole("USER", "ADMIN", "SUPER_USER").antMatchers("/admin/**") + .hasAnyRole("ADMIN", "SUPER_USER") // .and() // .antMatcher("/api/**") // .authorizeRequests() @@ -102,19 +114,18 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { .and() - .formLogin() - .loginPage("/login").permitAll() - .loginProcessingUrl("/login") + .formLogin().loginPage("/login").permitAll().loginProcessingUrl("/perform_login") // .successHandler(authenticationSuccessHandler) // .failureUrl("/?login_error") - .and() - .logout().invalidateHttpSession(true).clearAuthentication(true) - .logoutRequestMatcher(new AntPathRequestMatcher("/perform_logout")) +// .and() +// .logout().invalidateHttpSession(true) +// .clearAuthentication(true) +// .logoutRequestMatcher(new AntPathRequestMatcher("/perform_logout")) // .logoutSuccessUrl("/").permitAll() // .and().httpBasic(); // .and().cors() -// .and().csrf().disable(); - ; + .and().csrf().disable(); + ; // httpSecurity // .csrf().disable() // .authorizeRequests().antMatchers("login").permitAll() @@ -129,7 +140,17 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { // .logoutSuccessUrl("/").permitAll(); } +// @Override +// protected void configure(AuthenticationManagerBuilder auth) throws Exception { +// auth.eraseCredentials(false); +// } + } + +// @Override +// protected void configure(AuthenticationManagerBuilder auth) throws Exception { +// auth.eraseCredentials(false); +// } // @Override // protected void configure(AuthenticationManagerBuilder auth) throws Exception { diff --git a/chatto/src/main/java/org/ros/chatto/config/CacheConfig.java b/chatto/src/main/java/org/ros/chatto/config/CacheConfig.java new file mode 100644 index 0000000..33f8809 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/config/CacheConfig.java @@ -0,0 +1,47 @@ +package org.ros.chatto.config; + + +import org.ros.chatto.security.MyUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.UserCache; +import org.springframework.security.core.userdetails.cache.SpringCacheBasedUserCache; + +@EnableCaching +@Configuration +public class CacheConfig { + + @Autowired + private CacheManager cacheManager; + + + @Bean + public UserCache userCache() throws Exception { +// return new EhCacheBasedUserCache(); +// Cache cache = (Cache) cacheManager().getCache("userCache"); + Cache cache = cacheManager.getCache("chatUser"); + return new SpringCacheBasedUserCache(cache); + } + +// private net.sf.ehcache.CacheManager cacheManager; + +// @PreDestroy +// public void destroy() { +// cacheManager.shutdown(); +// } +// +// @Bean +// public CacheManager cacheManager() { +//// log.debug("Starting Ehcache"); +// cacheManager = net.sf.ehcache.CacheManager.create(); +// cacheManager.getConfiguration().setMaxBytesLocalHeap("16M"); +// EhCacheCacheManager ehCacheManager = new EhCacheCacheManager(); +// ehCacheManager.setCacheManager(cacheManager); +// return ehCacheManager; +// } +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/config/CustomCacheEventLogger.java b/chatto/src/main/java/org/ros/chatto/config/CustomCacheEventLogger.java new file mode 100644 index 0000000..2324078 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/config/CustomCacheEventLogger.java @@ -0,0 +1,19 @@ +package org.ros.chatto.config; + +import org.ehcache.event.CacheEvent; +import org.ehcache.event.CacheEventListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +//@Component +public class CustomCacheEventLogger implements CacheEventListener { + + private static final Logger LOG = LoggerFactory.getLogger(CustomCacheEventLogger.class); + + @Override + public void onEvent(CacheEvent extends Object, ? extends Object> cacheEvent) { + LOG.info("custom Caching event {} key = {} old {} new {} ", cacheEvent.getType(), cacheEvent.getKey(), + cacheEvent.getOldValue(), cacheEvent.getNewValue()); + } +} diff --git a/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java b/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java index 11e8aa8..baf4c4d 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java +++ b/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java @@ -10,6 +10,7 @@ import java.util.List; import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.model.MessageCipher; import org.ros.chatto.service.ChatService; +import org.ros.chatto.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -26,6 +27,9 @@ import org.springframework.web.bind.annotation.RestController; public class ChatMessageController { @Autowired private ChatService chatService; + + @Autowired + private UserService userService; @PostMapping(value = "/post/message", consumes = { "application/json" }) @ResponseBody @@ -64,6 +68,11 @@ public class ChatMessageController { List chatMessageDTOs = chatService.getNewMessages(principal.getName(), userName, date); return chatMessageDTOs; } + @GetMapping("/get/users") + public List getAllOtherUsers(Principal principal) + { + return userService.findAllOtherUsers(principal.getName()); + } } //public ResponseEntity> getMessages(@PathVariable String userName, Principal principal) { diff --git a/chatto/src/main/java/org/ros/chatto/controller/Home.java b/chatto/src/main/java/org/ros/chatto/controller/Home.java index d892387..0a0a789 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/Home.java +++ b/chatto/src/main/java/org/ros/chatto/controller/Home.java @@ -34,23 +34,27 @@ public class Home { @Autowired private UserService userService; - @Autowired - private DBInitializerService dbInitializerService; +// @Autowired +// private DBInitializerService dbInitializerService; - private boolean installationChecked = false; +// private boolean installationChecked = false; @RequestMapping("/") public ModelAndView showPage(Principal principal) throws SQLException { ModelAndView mv = new ModelAndView("home"); - mv.addObject("message", "Welcome!"); + String welcomeMesage = String.format("Welcome to chatto"); + if (principal != null) { + welcomeMesage = String.format("Welcome back %s!", principal.getName()); + } + mv.addObject("message", welcomeMesage); // mv.addObject("userNames", userService.findAllOtherUsers(principal.getName())); - if (!installationChecked) { - dbInitializerService.connectDB(); - if(dbInitializerService.getNumTables() == 0) - dbInitializerService.populateDB(); - dbInitializerService.closeConnection(); - installationChecked = true; - } +// if (!installationChecked) { +// dbInitializerService.connectDB(); +// if(dbInitializerService.getNumTables() == 0) +// dbInitializerService.populateDB(); +// dbInitializerService.closeConnection(); +// installationChecked = true; +// } return mv; } diff --git a/chatto/src/main/java/org/ros/chatto/model/ChatUser.java b/chatto/src/main/java/org/ros/chatto/model/ChatUser.java index 7328bc7..30afad8 100644 --- a/chatto/src/main/java/org/ros/chatto/model/ChatUser.java +++ b/chatto/src/main/java/org/ros/chatto/model/ChatUser.java @@ -45,8 +45,9 @@ public class ChatUser { // @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) @JsonBackReference - private Set userRoles = new HashSet(); - +// private Set userRoles = new HashSet(); + private Set userRoles; + public int getUserID() { return userID; } diff --git a/chatto/src/main/java/org/ros/chatto/repository/UserRoleRepository.java b/chatto/src/main/java/org/ros/chatto/repository/UserRoleRepository.java index 943a946..a17785e 100644 --- a/chatto/src/main/java/org/ros/chatto/repository/UserRoleRepository.java +++ b/chatto/src/main/java/org/ros/chatto/repository/UserRoleRepository.java @@ -11,4 +11,7 @@ import org.springframework.stereotype.Repository; public interface UserRoleRepository extends JpaRepository{ @Query("select ur from UserRole ur where ur.user.userID = ?1") public List findByUser(int userID); + + @Query("select ur from UserRole ur where ur.user.userName = ?1") + public List findByUser(String username); } diff --git a/chatto/src/main/java/org/ros/chatto/security/MyUserDetailsService.java b/chatto/src/main/java/org/ros/chatto/security/MyUserDetailsService.java index e86fd8f..9b5a0fc 100644 --- a/chatto/src/main/java/org/ros/chatto/security/MyUserDetailsService.java +++ b/chatto/src/main/java/org/ros/chatto/security/MyUserDetailsService.java @@ -1,8 +1,11 @@ package org.ros.chatto.security; import java.util.List; +import java.util.stream.Collector; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; +import javax.validation.constraints.Size; import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.UserRole; @@ -10,7 +13,11 @@ import org.ros.chatto.repository.RoleRepository; import org.ros.chatto.repository.UserRepository; import org.ros.chatto.repository.UserRoleRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.Cache.ValueWrapper; +import org.springframework.cache.annotation.Cacheable; import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -19,57 +26,102 @@ import org.springframework.web.context.WebApplicationContext; @Service public class MyUserDetailsService implements UserDetailsService { - + // @Autowired // private WebApplicationContext applicationContext; - @Autowired - private UserRepository userRepository; - + @Autowired + private UserRepository userRepository; + // @Autowired // private RoleRepository roleRepository; - - @Autowired - private UserRoleRepository userRoleRepository; + + @Autowired + private UserRoleRepository userRoleRepository; + +// @Autowired +// private UserCache userCache; + + @Autowired + private CacheManager cacheManager; + +// @Autowired +// private UserCache userCache; // @PostConstruct // public void completeSetup() { // userRepository = applicationContext.getBean(UserRepository.class); // } - - public MyUserDetailsService() { - super(); - } - @Override - public UserDetails loadUserByUsername(String username) { - ChatUser user = userRepository.findByUserName(username); - - - - if (user == null) { - throw new UsernameNotFoundException(username); - } - System.out.println("Found useeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer " + user.getUserName() + user.getPassword()); - List userRoles = userRoleRepository.findByUser(user.getUserID()); - System.out.println("User role iddddddddddddddddd = " + userRoles.get(0).getRole().getName()); + + public MyUserDetailsService() { + super(); + } + + @Override +// @Cacheable(value="chatUser") + public UserDetails loadUserByUsername(String username) { +// ChatUser user = userRepository.findByUserName(username); + + List userRoles = userRoleRepository.findByUser(username); +// @SuppressWarnings("unchecked") +// List userRoles = (List)cacheManager.getCache(username); +// if((userRoles == null)) { +// userRoles = userRoleRepository.findByUser(username); +// } +// UserDetails userDetails = (UserDetails) cacheManager.getCache(username); +// if((userDetails == null)) { +// user = userRoleRepository.findByUser(username); +// } + System.out.println("Test from userdetails"); + + ValueWrapper valueWrapper = cacheManager.getCache("chatUser").get("hmm"); + if (valueWrapper != null) { + UserDetails userDetails = (UserDetails) valueWrapper.get(); + if (userDetails != null) { + System.out.println("cache username = " + userDetails.getUsername()); + System.out.println("cache password = " + userDetails.getPassword()); + } + } + if (userRoles.size() == 0) { + throw new UsernameNotFoundException(username); + } +// System.out.println("Found useeeeeeeeeeeeeeeeeeeeeeeeeeeeeeer " + user.getUserName() + user.getPassword()); + +// ChatUser user2 = userRoles.get(0).getUser(); +// System.out.println("User role iddddddddddddddddd = " + userRoles.get(0).getRole().getName()); // System.out.println(userRoles.); // return new MyUserPrincipal(user); - return toUserDetails(new UserObject(user.getUserName(), user.getPassword(), userRoles.get(0).getRole().getName())); - } - - private UserDetails toUserDetails(UserObject userObject) { - return User.withUsername(userObject.name) - .password(userObject.password) - .roles(userObject.role).build(); - } - - private static class UserObject { - private String name; - private String password; - private String role; - - public UserObject(String name, String password, String role) { - this.name = name; - this.password = password; - this.role = role; - } - } +// return toUserDetails(new UserObject(user.getUserName(), user.getPassword(), userRoles.get(0).getRole().getName())); +// return User.withUsername(user.getUserName()).password(user.getPassword()) +// .roles( +// user.getUserRoles() +// .stream() +// .map(userRole -> { +// System.out.println("role = " + userRole.getRole().getName()); +// return userRole.getRole().getName(); +// }) +// .toArray(size -> new String[size]) +// ) +// .build(); + ChatUser user = userRoles.get(0).getUser(); + return User.withUsername(user.getUserName()).password(user.getPassword()) + .roles(userRoles.stream().map(userRole -> { + System.out.println("role = " + userRole.getRole().getName()); + return userRole.getRole().getName(); + }).toArray(size -> new String[size])).build(); + } + + private UserDetails toUserDetails(UserObject userObject) { + return User.withUsername(userObject.name).password(userObject.password).roles(userObject.role).build(); + } + + private static class UserObject { + private String name; + private String password; + private String role; + + public UserObject(String name, String password, String role) { + this.name = name; + this.password = password; + this.role = role; + } + } } \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java b/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java index 93c48d7..89d756d 100644 --- a/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java +++ b/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java @@ -1,14 +1,22 @@ package org.ros.chatto.service; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Properties; +import org.ros.chatto.ChattoApplication; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.PropertySource; +import org.springframework.context.event.EventListener; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.EncodedResource; import org.springframework.jdbc.datasource.init.ScriptUtils; @@ -38,6 +46,9 @@ public class DBInitializerService { @Value("${num-tables}") private String numTablesQuery; + @Value("${test.bindAddress}") + private String bindAddress; + private Connection connection; // public DBInitializerService(Connection connection) { @@ -111,7 +122,18 @@ public class DBInitializerService { return numTables; } - public void populateDB() throws SQLException { + @EventListener(ApplicationReadyEvent.class) + public void doSomethingAfterStartup() throws SQLException, IOException { +// setProperties(); + System.out.println("Hello world, I have just started up"); + System.out.println("Initializing database"); + connectDB(); + if (getNumTables() == 0) + populateDB(); + closeConnection(); + } + + public void populateDB() throws SQLException, IOException { // System.out.println("Database name = " + dbName); // String sql = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '" + dbName + "' and TABLE_TYPE='BASE TABLE' "; // String sql = numTablesQuery; @@ -139,6 +161,36 @@ public class DBInitializerService { // connection.close(); } + public void setProperties() throws IOException { +// InputStream input = ChattoApplication.class.getClassLoader().getResourceAsStream("messages.properties"); + OutputStream outputStream = new FileOutputStream("messages.properties"); +// FileInputStream in = new FileInputStream("First.properties"); +// Properties props = new Properties(); +// props.load(in); +// in.close(); +// +// FileOutputStream out = new FileOutputStream("First.properties"); +// props.setProperty("country", "america"); +// props.store(out, null); +// out.close(); + Properties prop = new Properties(); + System.out.println("Hello from setProperties"); + + prop.setProperty("test.bindAddress", bindAddress); + prop.store(outputStream, null); +// if (input == null) { +// System.out.println("Sorry, unable to find messages.properties"); +// return; +// } + + // load a properties file from class path, inside static method +// prop.load(input); +// Object object = prop.setProperty("test.bindAddress", bindAddress); +// input.close(); + outputStream.close(); +// prop.store(object, comments); + } + public void closeConnection() throws SQLException { connection.close(); } diff --git a/chatto/src/main/resources/application.properties b/chatto/src/main/resources/application.properties index dd5ee56..24bf0ae 100644 --- a/chatto/src/main/resources/application.properties +++ b/chatto/src/main/resources/application.properties @@ -20,4 +20,8 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE spring.http.log-request-details=true #spring.jackson.date-format=yyyy-MM-d spring.jackson.serialization.write-dates-as-timestamps=false -#spring.mvc.static-path-pattern=/static/** \ No newline at end of file +#spring.mvc.static-path-pattern=/static/** +#spring.cache.type=ehcache3 +spring.cache.jcache.config=classpath:ehcache.xml +logging.level.org.springframework.cache = DEBUG +#test.bindAddress=192.168.1.106 \ No newline at end of file diff --git a/chatto/src/main/resources/ehcache.xml b/chatto/src/main/resources/ehcache.xml new file mode 100644 index 0000000..8cbdabe --- /dev/null +++ b/chatto/src/main/resources/ehcache.xml @@ -0,0 +1,44 @@ + + + + + + + + java.lang.String + org.springframework.security.core.userdetails.UserDetails + + 600 + + + + org.ros.chatto.config.CustomCacheEventLogger + ASYNCHRONOUS + UNORDERED + CREATED + UPDATED + EXPIRED + REMOVED + EVICTED + + + + 2000 + 100 + + + + + + \ No newline at end of file diff --git a/chatto/src/main/resources/messages.properties b/chatto/src/main/resources/messages.properties new file mode 100644 index 0000000..a39da72 --- /dev/null +++ b/chatto/src/main/resources/messages.properties @@ -0,0 +1 @@ +#test.bindAddress=192.168.1.106 \ No newline at end of file diff --git a/chatto/src/main/resources/static/css/colors.css b/chatto/src/main/resources/static/css/colors.css new file mode 100644 index 0000000..a4437e2 --- /dev/null +++ b/chatto/src/main/resources/static/css/colors.css @@ -0,0 +1,322 @@ +/*------------------------------------ +- COLOR primary +------------------------------------*/ + + +/* +.alert-primary { + color: #191d21; + background-color: #b8c1c9; + border-color: #adb6c0; +} + +.alert-primary hr { + border-top-color: #9eaab5; +} + +.alert-primary .alert-link { + color: #030404; +} + +.badge-primary { + color: #fff; + background-color: #4f5b67; +} + +.badge-primary[href]:hover, +.badge-primary[href]:focus { + color: #fff; + background-color: #38414a; +} + +.bg-primary { + background-color: #4f5b67 !important; +} + +a.bg-primary:hover, +a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #38414a !important; +} + +.border-primary { + border-color: #4f5b67 !important; +} + +.btn-primary { + color: #fff; + background-color: #4f5b67; + border-color: #4f5b67; +} + +.btn-primary:hover { + color: #fff; + background-color: #3f4952; + border-color: #38414a; +} + +.btn-primary:focus, +.btn-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(79, 91, 103, 0.5); +} + +.btn-primary.disabled, +.btn-primary:disabled { + color: #fff; + background-color: #4f5b67; + border-color: #4f5b67; +} + +.btn-primary:not(:disabled):not(.disabled):active, +.btn-primary:not(:disabled):not(.disabled).active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + background-color: #38414a; + border-color: #323941; +} + +.btn-primary:not(:disabled):not(.disabled):active:focus, +.btn-primary:not(:disabled):not(.disabled).active:focus, +.show>.btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(79, 91, 103, 0.5); +} + +.btn-outline-primary { + color: #4f5b67; + background-color: transparent; + border-color: #4f5b67; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #4f5b67; + border-color: #4f5b67; +} + +.btn-outline-primary:focus, +.btn-outline-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(79, 91, 103, 0.5); +} + +.btn-outline-primary.disabled, +.btn-outline-primary:disabled { + color: #4f5b67; + background-color: transparent; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active, +.btn-outline-primary:not(:disabled):not(.disabled).active, +.show>.btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #4f5b67; + border-color: #4f5b67; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, +.btn-outline-primary:not(:disabled):not(.disabled).active:focus, +.show>.btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(79, 91, 103, 0.5); +} + +.list-group-item-primary { + color: #191d21; + background-color: #adb6c0; +} + +.list-group-item-primary.list-group-item-action:hover, +.list-group-item-primary.list-group-item-action:focus { + color: #191d21; + background-color: #9eaab5; +} + +.list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #191d21; + border-color: #191d21; +} + +.table-primary, +.table-primary>th, +.table-primary>td { + background-color: #adb6c0; +} + +.table-hover .table-primary:hover { + background-color: #9eaab5; +} + +.table-hover .table-primary:hover>td, +.table-hover .table-primary:hover>th { + background-color: #9eaab5; +} + +.text-primary { + color: #4f5b67 !important; +} + +a.text-primary:hover, +a.text-primary:focus { + color: #38414a !important; +} */ + + +/*------------------------------------ +- COLOR primary +------------------------------------*/ + +.alert-primary { + color: #14171b; + background-color: #b1bbc4; + border-color: #a5b0bb; +} + +.alert-primary hr { + border-top-color: #97a4b0; +} + +.alert-primary .alert-link { + color: #000000; +} + +.badge-primary { + color: #fff; + background-color: #495561; +} + +.badge-primary[href]:hover, +.badge-primary[href]:focus { + color: #fff; + background-color: #333b43; +} + +.bg-primary { + background-color: #495561 !important; +} + +a.bg-primary:hover, +a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #333b43 !important; +} + +.border-primary { + border-color: #495561 !important; +} + +.btn-primary { + color: #fff; + background-color: #495561; + border-color: #495561; +} + +.btn-primary:hover { + color: #fff; + background-color: #39434c; + border-color: #333b43; +} + +.btn-primary:focus, +.btn-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(73, 85, 97, 0.5); +} + +.btn-primary.disabled, +.btn-primary:disabled { + color: #fff; + background-color: #495561; + border-color: #495561; +} + +.btn-primary:not(:disabled):not(.disabled):active, +.btn-primary:not(:disabled):not(.disabled).active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + background-color: #333b43; + border-color: #2c333b; +} + +.btn-primary:not(:disabled):not(.disabled):active:focus, +.btn-primary:not(:disabled):not(.disabled).active:focus, +.show>.btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(73, 85, 97, 0.5); +} + +.btn-outline-primary { + color: #495561; + background-color: transparent; + border-color: #495561; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #495561; + border-color: #495561; +} + +.btn-outline-primary:focus, +.btn-outline-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(73, 85, 97, 0.5); +} + +.btn-outline-primary.disabled, +.btn-outline-primary:disabled { + color: #495561; + background-color: transparent; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active, +.btn-outline-primary:not(:disabled):not(.disabled).active, +.show>.btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #495561; + border-color: #495561; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, +.btn-outline-primary:not(:disabled):not(.disabled).active:focus, +.show>.btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(73, 85, 97, 0.5); +} + +.list-group-item-primary { + color: #14171b; + background-color: #a5b0bb; +} + +.list-group-item-primary.list-group-item-action:hover, +.list-group-item-primary.list-group-item-action:focus { + color: #14171b; + background-color: #97a4b0; +} + +.list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #14171b; + border-color: #14171b; +} + +.table-primary, +.table-primary>th, +.table-primary>td { + background-color: #a5b0bb; +} + +.table-hover .table-primary:hover { + background-color: #97a4b0; +} + +.table-hover .table-primary:hover>td, +.table-hover .table-primary:hover>th { + background-color: #97a4b0; +} + +.text-primary { + color: #495561 !important; +} + +a.text-primary:hover, +a.text-primary:focus { + color: #333b43 !important; +} \ No newline at end of file diff --git a/chatto/src/main/resources/static/css/master.css b/chatto/src/main/resources/static/css/master.css index 0895505..569b3d5 100644 --- a/chatto/src/main/resources/static/css/master.css +++ b/chatto/src/main/resources/static/css/master.css @@ -1,24 +1,126 @@ -.myClass { +/* .myClass { color: red; +} */ + + +/* https://arcusiridis.com/images/background.jpg */ + +body { + background: #333; + color: #ffffff; + /* background-image: url('https://bluestnight.com/images/background_lg.jpg'); */ } -#body-container { + + +/* #body-container { margin: 0 auto 0 auto; max-width: 80%; - /* vertical-align: auto; */ -} -.shadow-sm { - width: 50%; -} -input[type="radio"]{ - /*position:fixed;*/ - opacity:0; + vertical-align: auto; +} +*/ + +input[type="radio"] { + /*position:fixed;*/ + opacity: 0; } + input[type=radio]+label { - font-weight: normal; + font-weight: normal; } + input[type=radio]:checked+label { - font-weight: bold; + font-weight: bold; + background-color: #566069; } + input[type=radio]:focus+label { - border: 1px dotted #000; + border: 1px dotted #000; +} + +#home-section { + background-image: url('../img/home.jpg'); + background-repeat: no-repeat; + background-size: cover; + background-attachment: fixed; + min-height: 900px; + height: auto; +} + +#home-section .home-inner { + padding-top: 75px; + /* padding-bottom: 10px; */ + /* background: #333; */ +} + + +/* #home-section .card-form { + opacity: 0.8; +} */ + +#home-section .dark-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 900px; + background: rgba(0, 0, 0, 0.7); +} + +#chat-section { + /* background-image: url('../img/home.jpg'); */ + background: #495561; + /* background-repeat: no-repeat; + background-size: cover; + background-attachment: fixed; */ + min-height: 500px; +} + +#chat-section .chat-inner { + padding-top: 75px; + /* background: #333; */ +} + +#chat-section .dark-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 600px; + /* background: rgba(0, 0, 0, 0.7); */ +} + +textarea { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; +} + +#chatTextArea { + min-height: 300px; +} + + +/* .container { + width: 50%; +} */ + +.my-form-inputs { + width: 80%; +} + +#login-card { + width: 40%; + /* margin: 0 auto; */ + /* Added */ + /* float: none; */ + /* Added */ + /* margin-bottom: 10px; */ + /* Added */ +} + +@media only screen and (max-width: 600px) { + #login-card { + width: 90%; + } } \ No newline at end of file diff --git a/chatto/src/main/resources/static/img/home.jpg b/chatto/src/main/resources/static/img/home.jpg new file mode 100644 index 0000000..68474cb Binary files /dev/null and b/chatto/src/main/resources/static/img/home.jpg differ diff --git a/chatto/src/main/resources/static/js/chat.js b/chatto/src/main/resources/static/js/chat.js index 40b7987..e4b8697 100644 --- a/chatto/src/main/resources/static/js/chat.js +++ b/chatto/src/main/resources/static/js/chat.js @@ -14,14 +14,19 @@ if(!ischecked_method) { //payment method button is not checked var toUserRadios = document.getElementsByName('toUser'); var isCheckedUser = false; var chatTextArea = document.getElementById('chatTextArea'); + var passphraseInput = document.getElementById('passphrase'); -var postNewMessageUrl = "http://localhost:8080/api/chat/post/message"; -var getAllMessagesUrl = "http://localhost:8080/api/chat/get/messages/"; -var getNewMessagesUrl = "http://localhost:8080/api/chat/get/messages/"; +var postNewMessageUrl = `http://${hostAddress}/api/chat/post/message`; //hostAddress variable is set in the thymeleaf head fragment +var getAllMessagesUrl = `http://${hostAddress}/api/chat/get/messages/`; +var getNewMessagesUrl = `http://${hostAddress}/api/chat/get/messages/`; +// var postNewMessageUrl = "http://localhost:8080/api/chat/post/message"; +// var getAllMessagesUrl = "http://localhost:8080/api/chat/get/messages/"; +// var getNewMessagesUrl = "http://localhost:8080/api/chat/get/messages/"; // var messageLog = []; var username = sessionStorage.getItem('username'); var password = sessionStorage.getItem('password'); var authToken = 'Basic ' + btoa(username + ":" + password); +var iterations = 100000; // var lastMessageTimeStamp; // console.log(authToken); @@ -60,8 +65,9 @@ function handleChatForm() { let localDate = new Date(); let messageLine = sprintf('%s %s %s: %s', localDate.toLocaleDateString(), localDate.toLocaleTimeString(), username, messageContent); chatTextArea.append(messageLine + "\n"); + chatTextArea.scrollTop = chatTextArea.scrollHeight; // let messageCipher = sjcl.encrypt("password", messageContent); - let messageCipher = sjcl.encrypt(passphraseInput.value, messageContent); + let messageCipher = sjcl.encrypt(passphraseInput.value, messageContent,{mode: "gcm",ts: 128, adata: "",iter: iterations}); let messageCipherJson = JSON.parse(messageCipher); // let messageCipherSpring = JSON.stringify(messageCipherJson); // console.log('message cipher json ' + messageCipherJson); @@ -75,6 +81,7 @@ function handleChatForm() { messageSend(JSON.stringify(chatMessageDTO)); // sessionStorage.setItem('passphrase', passphraseInput.value); // console.log(sessionStorage.getItem('passphrase')); + }) } @@ -215,6 +222,7 @@ parent.addDelegatedListener("click", "input[type='radio']", function (event) { // chatTextArea.append(obj.fromUser + ": " + message + "\n"); chatTextArea.append(messageLine + '\n'); messageLog[i++] = messageLine; + chatTextArea.scrollTop = chatTextArea.scrollHeight; // console.log('Message log = ' + messageLog); @@ -255,7 +263,7 @@ parent.addDelegatedListener("click", "input[type='radio']", function (event) { console.log(messageLine); // chatTextArea.append(obj.fromUser + ": " + message + "\n"); chatTextArea.append(messageLine + '\n'); - + chatTextArea.scrollTop = chatTextArea.scrollHeight; storedMessages.push(messageLine); }) @@ -264,12 +272,15 @@ parent.addDelegatedListener("click", "input[type='radio']", function (event) { console.log("this value stored" + sessionStorage.getItem(this.value)) console.log("last message time stamp = " + lastMessageTimeStamp); console.log(sessionStorage.getItem(this.value + '-time')); - chatTextArea.textContent = ''; + + } + chatTextArea.textContent = ''; console.log("Stored messages 2 = " + storedMessages); storedMessages.forEach(function (messageLine) { chatTextArea.append(messageLine + '\n'); + chatTextArea.scrollTop = chatTextArea.scrollHeight; }) - } + }); diff --git a/chatto/src/main/resources/static/js/chatStatic.js b/chatto/src/main/resources/static/js/chatStatic.js new file mode 100644 index 0000000..1dbaae6 --- /dev/null +++ b/chatto/src/main/resources/static/js/chatStatic.js @@ -0,0 +1,22 @@ +var chatTextArea = document.getElementById('chatTextArea'); +function handleChatForm() { + let chatInput = document.getElementById('chatInput'); + let myForm = document.getElementById('chatMessageForm').addEventListener( + 'submit', function (e) { + e.preventDefault(); + + // let user = getSelectedUser(); + // if (!isCheckedUser) { + // window.alert('please select a user'); + // return; + // } + // console.log('second user = ' + user); + let messageContent = chatInput.value; + let localDate = new Date(); + let messageLine = localDate.toLocaleDateString() + localDate.toLocaleTimeString() + 'fromUser' + ': ' + messageContent; + chatTextArea.append(messageLine + "\n"); + chatTextArea.scrollTop = chatTextArea.scrollHeight; + }) +} + +handleChatForm(); \ No newline at end of file diff --git a/chatto/src/main/resources/templates/NewFile.html b/chatto/src/main/resources/templates/NewFile.html new file mode 100644 index 0000000..c38c981 --- /dev/null +++ b/chatto/src/main/resources/templates/NewFile.html @@ -0,0 +1,17 @@ + + + + + + Home + + + + + + + + + + \ No newline at end of file diff --git a/chatto/src/main/resources/templates/chat.html b/chatto/src/main/resources/templates/chat.html index 89c3e4e..c8385f2 100644 --- a/chatto/src/main/resources/templates/chat.html +++ b/chatto/src/main/resources/templates/chat.html @@ -2,37 +2,104 @@ - - - Chat - - - - + + + + + + Chat + + + + - - - - - User to send to: - - - DemoUser - - Your message: - - Passphrase: - - - + + + + + + + Chat with your friends + + + + + + + Lorem ipsum dolor sit, amet consectetur adipisicing elit. Laboriosam dolorem nostrum consequatur eos voluptates. Ipsam ullam quos illo qui. Quaerat corrupti nisi numquam rerum quasi nesciunt deserunt fugit commodi consequatur! + + + + + + + + + + + + + + Chat + + + + + + + + + + + + + + + + + User to send to: + + + + Demo User + + + + + + + + Your message: + + + + Passphrase: + + + + + + + + + + + + + + + + + + - + + + - - - - Layout Generic Title - + + + + + + + + + + + + + + + + Layout Generic Title + diff --git a/chatto/src/main/resources/templates/fragments/navbar.html b/chatto/src/main/resources/templates/fragments/navbar.html new file mode 100644 index 0000000..995b9d0 --- /dev/null +++ b/chatto/src/main/resources/templates/fragments/navbar.html @@ -0,0 +1,60 @@ + + + + + + + Navbar Fragment + + + + + + + + + + + + + Chatto + + + + + + + + + + + + + + Home + + + User Area + + + Chat + + + Login + + + About + + + Contact + + + + + + + + + + + \ No newline at end of file diff --git a/chatto/src/main/resources/templates/home.html b/chatto/src/main/resources/templates/home.html index 85a5d36..216bbef 100644 --- a/chatto/src/main/resources/templates/home.html +++ b/chatto/src/main/resources/templates/home.html @@ -1,19 +1,107 @@ + -Title + + + Home + + + + + + + + + + + + - - Web Application. Passed parameter : - - - Welcome to home page. Please login to access any features. - login - - + + + + + + + + + + + + Chatto - Self Hosted, Minimal E2E Chat + + Welcome + Chatto is a minimal, end to end encrypted chat application. + + Get Started + + + + + + + Features + + + + + + + + Self Hosted + + + + + End To End Encrypted Messaging + + + + + Free Software (AGPLv3 Licensed) + + + + + Built With Java And Spring + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit, amet consectetur adipisicing elit. Aliquid illum ea accusamus animi voluptate. Quam temporibus aperiam, similique in labore sint quasi harum. Praesentium enim iste dicta quaerat perspiciatis eos. + + Find out more + + + + + + + \ No newline at end of file diff --git a/chatto/src/main/resources/templates/login.html b/chatto/src/main/resources/templates/login.html index ccc2936..7ca5c70 100644 --- a/chatto/src/main/resources/templates/login.html +++ b/chatto/src/main/resources/templates/login.html @@ -2,50 +2,103 @@ - - Login - - + + Login + + + + - - - - - + + + + + + + + + + + + + + + + Please Sign In + + Invalid username or password. + + + You have been logged out. + + + Enter user name: + + + + + Enter password: + + + + + Login + + + + + + + + + + + + + + + + + + - - Please Login - - Invalid username or password. - - - You have been logged out. - - - Enter user name: - - - Enter password: - - - Login - - - - - - - -
Welcome to home page. Please login to access any features.
Chatto is a minimal, end to end encrypted chat application.
+
+ + Self Hosted
+ + End To End Encrypted Messaging
+ + Free Software (AGPLv3 Licensed)
+ + Built With Java And Spring
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Aliquid illum ea accusamus animi voluptate. Quam temporibus aperiam, similique in labore sint quasi harum. Praesentium enim iste dicta quaerat perspiciatis eos.