From 6b7bbd82007cc2afee959e69032020e7211460f6 Mon Sep 17 00:00:00 2001 From: AnghelLeonard Date: Wed, 30 Nov 2016 08:13:21 +0200 Subject: [PATCH 01/37] Added reCAPTCHA support for registration --- TotalRegistration2/nbactions.xml | 43 ++++++ TotalRegistration2/pom.xml | 132 ++++++++++++++++++ .../java/org/security/config/JmsConfig.java | 41 ++++++ .../config/MainApplicationConfig.java | 20 +++ .../java/org/security/config/MvcConfig.java | 25 ++++ .../security/config/WebSecurityConfig.java | 59 ++++++++ .../security/controller/ErrorController.java | 33 +++++ .../controller/RegistrationController.java | 61 ++++++++ .../java/org/security/dao/UserRepository.java | 23 +++ .../exceptions/UserAlreadyExistException.java | 26 ++++ .../main/java/org/security/model/User.java | 108 ++++++++++++++ .../org/security/pojo/GenericMessage.java | 50 +++++++ .../service/CurrentUserDetailsService.java | 44 ++++++ .../service/EmailReceiverService.java | 24 ++++ .../service/IMessageReceiverService.java | 14 ++ .../service/IMessageSenderService.java | 14 ++ .../service/INotificationService.java | 13 ++ .../org/security/service/IUserService.java | 16 +++ .../service/MessageSenderService.java | 26 ++++ .../service/NotificationServiceImpl.java | 67 +++++++++ .../org/security/service/UserService.java | 73 ++++++++++ .../java/org/security/util/CryptUtils.java | 92 ++++++++++++ .../security/validation/EmailConstraint.java | 30 ++++ .../security/validation/EmailValidator.java | 33 +++++ .../security/validation/JmsErrorHandler.java | 24 ++++ .../validation/PasswordConstraint.java | 29 ++++ .../validation/PasswordsConstraint.java | 28 ++++ .../validation/PasswordsValidator.java | 23 +++ .../validation/SinglePasswordValidator.java | 42 ++++++ .../src/main/resources/application.yaml | 50 +++++++ .../src/main/resources/logback-spring.xml | 15 ++ .../src/main/resources/static/css/style.css | 22 +++ .../src/main/resources/static/js/password.js | 27 ++++ .../templates/credentials/activate.html | 13 ++ .../templates/credentials/login.html | 17 +++ .../templates/credentials/pending.html | 11 ++ .../templates/credentials/register.html | 43 ++++++ .../main/resources/templates/error/403.html | 10 ++ .../main/resources/templates/error/error.html | 11 ++ .../resources/templates/error/invalid.html | 10 ++ .../src/main/resources/templates/home.html | 14 ++ .../src/main/resources/templates/index.html | 12 ++ .../templates/mail/html/pending-email.html | 33 +++++ .../main/resources/templates/pages/admin.html | 17 +++ .../resources/templates/pages/member.html | 19 +++ 45 files changed, 1537 insertions(+) create mode 100644 TotalRegistration2/nbactions.xml create mode 100644 TotalRegistration2/pom.xml create mode 100644 TotalRegistration2/src/main/java/org/security/config/JmsConfig.java create mode 100644 TotalRegistration2/src/main/java/org/security/config/MainApplicationConfig.java create mode 100644 TotalRegistration2/src/main/java/org/security/config/MvcConfig.java create mode 100644 TotalRegistration2/src/main/java/org/security/config/WebSecurityConfig.java create mode 100644 TotalRegistration2/src/main/java/org/security/controller/ErrorController.java create mode 100644 TotalRegistration2/src/main/java/org/security/controller/RegistrationController.java create mode 100644 TotalRegistration2/src/main/java/org/security/dao/UserRepository.java create mode 100644 TotalRegistration2/src/main/java/org/security/exceptions/UserAlreadyExistException.java create mode 100644 TotalRegistration2/src/main/java/org/security/model/User.java create mode 100644 TotalRegistration2/src/main/java/org/security/pojo/GenericMessage.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/CurrentUserDetailsService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/EmailReceiverService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/IMessageReceiverService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/IMessageSenderService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/INotificationService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/IUserService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/MessageSenderService.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/NotificationServiceImpl.java create mode 100644 TotalRegistration2/src/main/java/org/security/service/UserService.java create mode 100644 TotalRegistration2/src/main/java/org/security/util/CryptUtils.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/EmailConstraint.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/EmailValidator.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/JmsErrorHandler.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/PasswordConstraint.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/PasswordsConstraint.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/PasswordsValidator.java create mode 100644 TotalRegistration2/src/main/java/org/security/validation/SinglePasswordValidator.java create mode 100644 TotalRegistration2/src/main/resources/application.yaml create mode 100644 TotalRegistration2/src/main/resources/logback-spring.xml create mode 100644 TotalRegistration2/src/main/resources/static/css/style.css create mode 100644 TotalRegistration2/src/main/resources/static/js/password.js create mode 100644 TotalRegistration2/src/main/resources/templates/credentials/activate.html create mode 100644 TotalRegistration2/src/main/resources/templates/credentials/login.html create mode 100644 TotalRegistration2/src/main/resources/templates/credentials/pending.html create mode 100644 TotalRegistration2/src/main/resources/templates/credentials/register.html create mode 100644 TotalRegistration2/src/main/resources/templates/error/403.html create mode 100644 TotalRegistration2/src/main/resources/templates/error/error.html create mode 100644 TotalRegistration2/src/main/resources/templates/error/invalid.html create mode 100644 TotalRegistration2/src/main/resources/templates/home.html create mode 100644 TotalRegistration2/src/main/resources/templates/index.html create mode 100644 TotalRegistration2/src/main/resources/templates/mail/html/pending-email.html create mode 100644 TotalRegistration2/src/main/resources/templates/pages/admin.html create mode 100644 TotalRegistration2/src/main/resources/templates/pages/member.html diff --git a/TotalRegistration2/nbactions.xml b/TotalRegistration2/nbactions.xml new file mode 100644 index 0000000..631bfd3 --- /dev/null +++ b/TotalRegistration2/nbactions.xml @@ -0,0 +1,43 @@ + + + + run + + jar + + + spring-boot:run + + + com.example.DemoApplication + + + + debug + + jar + + + spring-boot:run + + + -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} + com.example.DemoApplication + true + + + + profile + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.2.1:exec + + + -classpath %classpath com.example.DemoApplication + java + + + diff --git a/TotalRegistration2/pom.xml b/TotalRegistration2/pom.xml new file mode 100644 index 0000000..45ce922 --- /dev/null +++ b/TotalRegistration2/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + org.totaldev.com + TotalRegistration2 + 1.0 + jar + TotalRegistration2 + Complete SpringBoot registration application + + org.springframework.boot + spring-boot-starter-parent + 1.4.2.RELEASE + + + + + -Dfile.encoding=UTF-8 + UTF-8 + UTF-8 + 1.7 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-configuration-processor + true + + + ch.qos.logback + logback-classic + + + org.hibernate + hibernate-validator + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity4 + + + org.thymeleaf.extras + thymeleaf-extras-java8time + + + org.springframework.boot + spring-boot-starter-activemq + + + com.zaxxer + HikariCP + + + mysql + mysql-connector-java + runtime + + + org.apache.activemq + activemq-broker + + + com.fasterxml.jackson.core + jackson-databind + + + org.passay + passay + 1.2.0 + + + com.github.mkopylec + recaptcha-spring-boot-starter + 1.3.7 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + org.hibernate.orm.tooling + hibernate-enhance-maven-plugin + ${hibernate.version} + + + + true + + + enhance + + + + + + + diff --git a/TotalRegistration2/src/main/java/org/security/config/JmsConfig.java b/TotalRegistration2/src/main/java/org/security/config/JmsConfig.java new file mode 100644 index 0000000..f90ac29 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/config/JmsConfig.java @@ -0,0 +1,41 @@ +package org.security.config; + +import javax.jms.ConnectionFactory; +import org.security.validation.JmsErrorHandler; +import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; +import org.springframework.jms.support.converter.MappingJackson2MessageConverter; +import org.springframework.jms.support.converter.MessageConverter; +import org.springframework.jms.support.converter.MessageType; + +/** + * + * @author newlife + */ +@Configuration +@EnableJms +public class JmsConfig { + + @Bean + public JmsListenerContainerFactory totalDevFactory(ConnectionFactory connectionFactory, + DefaultJmsListenerContainerFactoryConfigurer configurer, JmsErrorHandler errorHandler) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + + factory.setErrorHandler(errorHandler); + configurer.configure(factory, connectionFactory); + + return factory; + } + + @Bean + public MessageConverter jacksonJmsMessageConverter() { + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); + converter.setTargetType(MessageType.TEXT); + converter.setTypeIdPropertyName("_type"); + return converter; + } +} diff --git a/TotalRegistration2/src/main/java/org/security/config/MainApplicationConfig.java b/TotalRegistration2/src/main/java/org/security/config/MainApplicationConfig.java new file mode 100644 index 0000000..00e26ef --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/config/MainApplicationConfig.java @@ -0,0 +1,20 @@ +package org.security.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@ComponentScan({"org.security.*"}) +@EnableJpaRepositories("org.security.dao") +@EntityScan("org.security.model") +@EnableTransactionManagement +public class MainApplicationConfig { + + public static void main(String[] args) { + SpringApplication.run(MainApplicationConfig.class, args); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/config/MvcConfig.java b/TotalRegistration2/src/main/java/org/security/config/MvcConfig.java new file mode 100644 index 0000000..96497cf --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/config/MvcConfig.java @@ -0,0 +1,25 @@ +package org.security.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * + * @author newlife + */ +@Configuration +public class MvcConfig extends WebMvcConfigurerAdapter { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("index"); + registry.addViewController("/home").setViewName("home"); + registry.addViewController("/login").setViewName("/credentials/login"); + registry.addViewController("/pending").setViewName("/credentials/pending"); + registry.addViewController("/member").setViewName("/pages/member"); + registry.addViewController("/admin").setViewName("/pages/admin"); + registry.addViewController("/invalid").setViewName("/error/invalid"); + registry.addViewController("/403").setViewName("/error/403"); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/config/WebSecurityConfig.java b/TotalRegistration2/src/main/java/org/security/config/WebSecurityConfig.java new file mode 100644 index 0000000..abcba81 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/config/WebSecurityConfig.java @@ -0,0 +1,59 @@ +package org.security.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * + * @author newlife + */ +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private UserDetailsService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http. + formLogin().loginPage("/login").defaultSuccessUrl("/home").failureUrl("/login?error").usernameParameter("email").passwordParameter("password").permitAll() + .and() + .authorizeRequests() + .antMatchers("/", "/activate/**", "/home/**", "/login/**", "/pending/**", "/register/**", + "/css/**", "/js/**", "/images/**", "/favicon.ico").permitAll() + .antMatchers("/member/**").hasAuthority("ROLE_MEMBER") + .antMatchers("/admin/**").hasAuthority("ROLE_ADMIN") + .anyRequest().fullyAuthenticated() + .and() + .logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout").invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll() + .and() + .exceptionHandling().accessDeniedPage("/403") + .and() + .sessionManagement().invalidSessionUrl("/invalid").maximumSessions(1).maxSessionsPreventsLogin(false) + .and() + .sessionFixation().none() + .and() + .csrf().disable(); + } + + @Autowired + public void configureAuthentication(AuthenticationManagerBuilder auth) throws Exception { + auth + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/controller/ErrorController.java b/TotalRegistration2/src/main/java/org/security/controller/ErrorController.java new file mode 100644 index 0000000..ba0596f --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/controller/ErrorController.java @@ -0,0 +1,33 @@ +package org.security.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * + * @author newlife + */ +@ControllerAdvice +public class ErrorController { + + private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class); + + @ExceptionHandler(Throwable.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public String exception(final Throwable throwable, final Model model) { + + LOGGER.error("++++++++++++++++++++++++++++++++++++++++++"); + LOGGER.error("Exception during execution of application:", throwable); + LOGGER.error("++++++++++++++++++++++++++++++++++++++++++"); + + String errorMessage = (throwable != null ? throwable.getMessage() : "Unknown error"); + model.addAttribute("errorMessage", errorMessage); + return "/error/error"; + } + +} diff --git a/TotalRegistration2/src/main/java/org/security/controller/RegistrationController.java b/TotalRegistration2/src/main/java/org/security/controller/RegistrationController.java new file mode 100644 index 0000000..8728d06 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/controller/RegistrationController.java @@ -0,0 +1,61 @@ +package org.security.controller; + +import com.github.mkopylec.recaptcha.validation.RecaptchaValidator; +import com.github.mkopylec.recaptcha.validation.ValidationResult; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import org.security.model.User; +import org.security.service.IUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.util.HtmlUtils; + +/** + * + * @author newlife + */ +@Controller +public class RegistrationController { + + @Autowired + IUserService userService; + + @Autowired + private RecaptchaValidator recaptchaValidator; + + @GetMapping("/register") + public String registrationShow(final User user) { + return "/credentials/register"; + } + + @PostMapping("/register") + public String registrationPerform(HttpServletRequest request, @Valid final User user, BindingResult bindingResult) { + + if (bindingResult.hasErrors()) { + return "/credentials/register"; + } + + ValidationResult result = recaptchaValidator.validate(request); + if (!result.isSuccess()) { + bindingResult.addError(new ObjectError("user", "Please check the 'I am not a robot' verification")); + return "/credentials/register"; + } + + userService.registerNewUser(user); + + return "redirect:/pending?email=" + HtmlUtils.htmlEscape(user.getEmail()); + } + + @GetMapping("/activate/{token}") + public String activateUser(final @PathVariable("token") String token) { + + userService.activateNewUser(token); + + return "/credentials/activate"; + } +} diff --git a/TotalRegistration2/src/main/java/org/security/dao/UserRepository.java b/TotalRegistration2/src/main/java/org/security/dao/UserRepository.java new file mode 100644 index 0000000..7ec95af --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/dao/UserRepository.java @@ -0,0 +1,23 @@ +package org.security.dao; + +import org.security.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; + +/** + * + * @author newlife + */ +public interface UserRepository extends JpaRepository { + + @Transactional(readOnly = true) + @Query(value = "SELECT p.password, p.enabled, p.roles FROM User p WHERE p.email = ?1") + String findUserCredentialsEmail(String email); + + @Modifying + @Transactional + @Query("UPDATE User p SET p.enabled = TRUE WHERE p.email = ?1 AND p.enabled = FALSE") + int enableUserAccount(String email); +} diff --git a/TotalRegistration2/src/main/java/org/security/exceptions/UserAlreadyExistException.java b/TotalRegistration2/src/main/java/org/security/exceptions/UserAlreadyExistException.java new file mode 100644 index 0000000..e609ba3 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/exceptions/UserAlreadyExistException.java @@ -0,0 +1,26 @@ +package org.security.exceptions; + +/** + * + * @author newlife + */ +public final class UserAlreadyExistException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public UserAlreadyExistException() { + super(); + } + + public UserAlreadyExistException(final String message, final Throwable cause) { + super(message, cause); + } + + public UserAlreadyExistException(final String message) { + super(message); + } + + public UserAlreadyExistException(final Throwable cause) { + super(cause); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/model/User.java b/TotalRegistration2/src/main/java/org/security/model/User.java new file mode 100644 index 0000000..7838d98 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/model/User.java @@ -0,0 +1,108 @@ +package org.security.model; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; +import org.security.validation.EmailConstraint; +import org.security.validation.PasswordConstraint; +import org.security.validation.PasswordsConstraint; + +/** + * + * @author newlife + */ +@Entity +@Table(name = "users") +@PasswordsConstraint +public class User implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @NotNull + @Column(length = 50, nullable = false) + private String username; + + @EmailConstraint + @NotNull + @Column(length = 50, nullable = false, unique = true) + private String email; + + @PasswordConstraint + @NotNull + @Column(length = 150, nullable = false) + private String password; + + @Column(length = 20, nullable = false) + private String roles; + + @Column(nullable = false) + private boolean enabled; + + @NotNull + @Transient + private String confirm; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getRoles() { + return roles; + } + + public void setRoles(String roles) { + this.roles = roles; + } + + public String getConfirm() { + return confirm; + } + + public void setConfirm(String confirm) { + this.confirm = confirm; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/TotalRegistration2/src/main/java/org/security/pojo/GenericMessage.java b/TotalRegistration2/src/main/java/org/security/pojo/GenericMessage.java new file mode 100644 index 0000000..4c05c92 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/pojo/GenericMessage.java @@ -0,0 +1,50 @@ +package org.security.pojo; + +/** + * + * @author newlife + */ +public class GenericMessage { + + private String sender; + private String receiver; + private String payload; + + public GenericMessage() { + } + + public GenericMessage(String sender, String receiver, String payload) { + this.sender = sender; + this.receiver = receiver; + this.payload = payload; + } + + public String getSender() { + return sender; + } + + public void setSender(String sender) { + this.sender = sender; + } + + public String getReceiver() { + return receiver; + } + + public void setReceiver(String receiver) { + this.receiver = receiver; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + + @Override + public String toString() { + return "GenericMessage{" + "sender=" + sender + ", receiver=" + receiver + ", payload=" + payload + '}'; + } +} diff --git a/TotalRegistration2/src/main/java/org/security/service/CurrentUserDetailsService.java b/TotalRegistration2/src/main/java/org/security/service/CurrentUserDetailsService.java new file mode 100644 index 0000000..1cc66c8 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/CurrentUserDetailsService.java @@ -0,0 +1,44 @@ +package org.security.service; + +import org.security.dao.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * + * @author newlife + */ +@Repository +public class CurrentUserDetailsService implements UserDetailsService { + + public CurrentUserDetailsService() { + super(); + } + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(final String email) throws UsernameNotFoundException { + + final String user = findUserCredentialsEmail(email); + + if (user == null || user.isEmpty()) { + throw new UsernameNotFoundException(String.format("User with email=%s was not found ...", email)); + } + + final String[] credentials = user.split(","); + return new org.springframework.security.core.userdetails.User(email, credentials[0], + Boolean.valueOf(credentials[1]), true, true, true, AuthorityUtils.createAuthorityList(credentials[2])); + } + + @Transactional(readOnly = true) + private String findUserCredentialsEmail(final String email) { + return userRepository.findUserCredentialsEmail(email); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/service/EmailReceiverService.java b/TotalRegistration2/src/main/java/org/security/service/EmailReceiverService.java new file mode 100644 index 0000000..9987051 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/EmailReceiverService.java @@ -0,0 +1,24 @@ +package org.security.service; + +import org.security.pojo.GenericMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +/** + * + * @author newlife + */ +@Component +public class EmailReceiverService implements IMessageReceiverService { + + @Autowired + INotificationService notificationService; + + @Override + @JmsListener(destination = "mailbox", containerFactory = "totalDevFactory") + public void receiveMessage(GenericMessage message) { + notificationService.sendUserPendingRegistrationEmail(message.getReceiver()); + } + +} diff --git a/TotalRegistration2/src/main/java/org/security/service/IMessageReceiverService.java b/TotalRegistration2/src/main/java/org/security/service/IMessageReceiverService.java new file mode 100644 index 0000000..44c30cb --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/IMessageReceiverService.java @@ -0,0 +1,14 @@ +package org.security.service; + +import org.security.pojo.GenericMessage; +import org.springframework.stereotype.Service; + +/** + * + * @author newlife + */ +@Service +public interface IMessageReceiverService { + + public void receiveMessage(final GenericMessage message); +} diff --git a/TotalRegistration2/src/main/java/org/security/service/IMessageSenderService.java b/TotalRegistration2/src/main/java/org/security/service/IMessageSenderService.java new file mode 100644 index 0000000..d143318 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/IMessageSenderService.java @@ -0,0 +1,14 @@ +package org.security.service; + +import org.security.pojo.GenericMessage; +import org.springframework.stereotype.Service; + +/** + * + * @author newlife + */ +@Service +public interface IMessageSenderService { + + public void sendMessage(final String destination, final GenericMessage message); +} diff --git a/TotalRegistration2/src/main/java/org/security/service/INotificationService.java b/TotalRegistration2/src/main/java/org/security/service/INotificationService.java new file mode 100644 index 0000000..90b7891 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/INotificationService.java @@ -0,0 +1,13 @@ +package org.security.service; + +import org.springframework.stereotype.Service; + +/** + * + * @author newlife + */ +@Service +public interface INotificationService { + + public void sendUserPendingRegistrationEmail(final String address); +} diff --git a/TotalRegistration2/src/main/java/org/security/service/IUserService.java b/TotalRegistration2/src/main/java/org/security/service/IUserService.java new file mode 100644 index 0000000..31ada7f --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/IUserService.java @@ -0,0 +1,16 @@ +package org.security.service; + +import org.security.model.User; +import org.springframework.stereotype.Service; + +/** + * + * @author newlife + */ +@Service +public interface IUserService { + + public void registerNewUser(final User user); + + public void activateNewUser(final String token); +} diff --git a/TotalRegistration2/src/main/java/org/security/service/MessageSenderService.java b/TotalRegistration2/src/main/java/org/security/service/MessageSenderService.java new file mode 100644 index 0000000..366258d --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/MessageSenderService.java @@ -0,0 +1,26 @@ +package org.security.service; + +import org.security.pojo.GenericMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.stereotype.Component; + +/** + * + * @author newlife + */ +@Component +public class MessageSenderService implements IMessageSenderService { + + private final JmsTemplate jmsTemplate; + + @Autowired + public MessageSenderService(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + @Override + public void sendMessage(final String destination, final GenericMessage message) { + jmsTemplate.convertAndSend(destination, message); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/service/NotificationServiceImpl.java b/TotalRegistration2/src/main/java/org/security/service/NotificationServiceImpl.java new file mode 100644 index 0000000..9baf0b8 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/NotificationServiceImpl.java @@ -0,0 +1,67 @@ +package org.security.service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import org.security.util.CryptUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +/** + * + * @author newlife + */ +@Component +public class NotificationServiceImpl implements INotificationService { + + @Value("${org.security.url.activate}") + private String activate; + + @Autowired + private JavaMailSender javaMailSender; + + @Autowired + private TemplateEngine templateEngine; + + @Override + public void sendUserPendingRegistrationEmail(final String address) { + + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm"); + final String expiration = LocalDateTime.now().plusDays(1).format(formatter); + final String token = CryptUtils.encrypt(address + " " + expiration); + + if (token != null) { + final Context ctx = new Context(); + ctx.setVariable("name", address); + ctx.setVariable("expiration", expiration); + ctx.setVariable("activationlink", activate + token); + + final MimeMessage mimeMessage = this.javaMailSender.createMimeMessage(); + final MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "UTF-8"); + try { + message.setSubject("Total Registration e-mail"); + message.setFrom("totalregistration@domain.com"); + message.setTo(address); + + final String htmlContent = this.templateEngine.process("mail/html/pending-email", ctx); + message.setText(htmlContent, true); + } catch (MessagingException ex) { + throw new RuntimeException("Unable to build a message for: " + + address + " | " + ex.getMessage(), ex); + } + + try { + this.javaMailSender.send(mimeMessage); + } catch (MailSendException e) { + throw new RuntimeException("Cannot send e-mail to address: " + address, e); + } + } + } +} diff --git a/TotalRegistration2/src/main/java/org/security/service/UserService.java b/TotalRegistration2/src/main/java/org/security/service/UserService.java new file mode 100644 index 0000000..b3300de --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/service/UserService.java @@ -0,0 +1,73 @@ +package org.security.service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import org.security.dao.UserRepository; +import org.security.exceptions.UserAlreadyExistException; +import org.security.model.User; +import org.security.pojo.GenericMessage; +import org.security.util.CryptUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Repository; + +/** + * + * @author newlife + */ +@Repository +public class UserService implements IUserService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private IMessageSenderService messageSenderService; + + @Override + public void registerNewUser(final User user) { + + user.setPassword(passwordEncoder.encode(user.getPassword())); + user.setEnabled(false); + user.setRoles("ROLE_MEMBER"); + + try { + userRepository.save(user); + } catch (DataIntegrityViolationException e) { + throw new UserAlreadyExistException("This email exist: " + user.getEmail(), e); + } + + sendUserPendingRegistrationMessage(user.getEmail()); + } + + @Override + public void activateNewUser(String token) { + + String decrypted = CryptUtils.decrypt(token); + + if (decrypted != null) { + final LocalDateTime now = LocalDateTime.now(); + LocalDateTime expiration = LocalDateTime. + parse(decrypted.substring(decrypted.indexOf(" ") + 1), + DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm")); + + if (now.isBefore(expiration)) { + int status = userRepository.enableUserAccount(decrypted.substring(0, decrypted.indexOf(" "))); + if (status == 1) { + return; + } + } + } + + throw new RuntimeException("We cannot activate the account: " + + (decrypted == null ? "null" : decrypted.substring(0, decrypted.indexOf(" ")))); + } + + private void sendUserPendingRegistrationMessage(String address) { + messageSenderService.sendMessage("mailbox", new GenericMessage("", address, "")); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/util/CryptUtils.java b/TotalRegistration2/src/main/java/org/security/util/CryptUtils.java new file mode 100644 index 0000000..0dc95d9 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/util/CryptUtils.java @@ -0,0 +1,92 @@ +package org.security.util; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import org.springframework.security.crypto.codec.Base64; + +/** + * + * @author newlife + */ +public final class CryptUtils { + + private static final byte[] SALT = {(byte) 0x21, (byte) 0x21, (byte) 0xF0, + (byte) 0x55, (byte) 0xC3, (byte) 0x9F, (byte) 0x5A, (byte) 0x75}; + + private final static int ITERATION_COUNT = 31; + + private CryptUtils() { + } + + public static String encrypt(String input) { + + if (input == null) { + throw new IllegalArgumentException(); + } + + KeySpec keySpec = new PBEKeySpec(null, SALT, ITERATION_COUNT); + AlgorithmParameterSpec paramSpec = new PBEParameterSpec(SALT, ITERATION_COUNT); + try { + SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec); + + Cipher ecipher = Cipher.getInstance(key.getAlgorithm()); + ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); + + byte[] enc = ecipher.doFinal(input.getBytes()); + + String res = new String(Base64.encode(enc)); + // escapes for url + res = res.replace('+', '-').replace('/', '_').replace("%", "%25").replace("\n", "%0A"); + + return res; + } catch (InvalidAlgorithmParameterException | InvalidKeyException + | NoSuchAlgorithmException | InvalidKeySpecException | BadPaddingException + | IllegalBlockSizeException | NoSuchPaddingException e) { + return null; + } + } + + public static String decrypt(String token) { + + if (token == null) { + return null; + } + + try { + + String input = token.replace("%0A", "\n").replace("%25", "%").replace('_', '/').replace('-', '+'); + + byte[] dec = Base64.decode(input.getBytes()); + + KeySpec keySpec = new PBEKeySpec(null, SALT, ITERATION_COUNT); + AlgorithmParameterSpec paramSpec = new PBEParameterSpec(SALT, ITERATION_COUNT); + + SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec); + + Cipher dcipher = Cipher.getInstance(key.getAlgorithm()); + dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec); + + byte[] decoded = dcipher.doFinal(dec); + + String result = new String(decoded); + return result; + + } catch (InvalidAlgorithmParameterException | InvalidKeyException + | NoSuchAlgorithmException | InvalidKeySpecException | BadPaddingException + | IllegalBlockSizeException | NoSuchPaddingException e) { + return null; + } + } +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/EmailConstraint.java b/TotalRegistration2/src/main/java/org/security/validation/EmailConstraint.java new file mode 100644 index 0000000..8899b33 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/EmailConstraint.java @@ -0,0 +1,30 @@ +package org.security.validation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +/** + * + * @author newlife + */ +@Target({TYPE, FIELD, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Constraint(validatedBy = EmailValidator.class) +@Documented +public @interface EmailConstraint { + + String message() default "Invalid e-mail ..."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/EmailValidator.java b/TotalRegistration2/src/main/java/org/security/validation/EmailValidator.java new file mode 100644 index 0000000..945fdea --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/EmailValidator.java @@ -0,0 +1,33 @@ +package org.security.validation; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * + * @author newlife + */ +public class EmailValidator implements ConstraintValidator { + + private Pattern pattern; + private Matcher matcher; + private static final String EMAIL_PATTERN + = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; + + @Override + public void initialize(final EmailConstraint constraintAnnotation) { + } + + @Override + public boolean isValid(final String username, final ConstraintValidatorContext context) { + return (validateEmail(username)); + } + + private boolean validateEmail(final String email) { + pattern = Pattern.compile(EMAIL_PATTERN); + matcher = pattern.matcher(email); + return matcher.matches(); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/JmsErrorHandler.java b/TotalRegistration2/src/main/java/org/security/validation/JmsErrorHandler.java new file mode 100644 index 0000000..9863e04 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/JmsErrorHandler.java @@ -0,0 +1,24 @@ +package org.security.validation; + +import org.security.controller.ErrorController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.ErrorHandler; + +/** + * + * @author newlife + */ +@Service +public class JmsErrorHandler implements ErrorHandler { + + private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class); + + @Override + public void handleError(Throwable t) { + LOGGER.error("++++++++++++++++++++++++++++++++++++++++++"); + LOGGER.error("Error in JMS listener", t); + LOGGER.error("++++++++++++++++++++++++++++++++++++++++++"); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/PasswordConstraint.java b/TotalRegistration2/src/main/java/org/security/validation/PasswordConstraint.java new file mode 100644 index 0000000..629d82d --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/PasswordConstraint.java @@ -0,0 +1,29 @@ +package org.security.validation; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +/** + * + * @author newlife + */ +@Documented +@Constraint(validatedBy = SinglePasswordValidator.class) +@Target({TYPE, FIELD, ANNOTATION_TYPE}) +@Retention(RUNTIME) +public @interface PasswordConstraint { + + String message() default "Invalid password ..."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/PasswordsConstraint.java b/TotalRegistration2/src/main/java/org/security/validation/PasswordsConstraint.java new file mode 100644 index 0000000..b83ac5f --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/PasswordsConstraint.java @@ -0,0 +1,28 @@ +package org.security.validation; + +import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Target; + +/** + * + * @author newlife + */ +@Target({TYPE, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Constraint(validatedBy = PasswordsValidator.class) +@Documented +public @interface PasswordsConstraint { + + String message() default "Passwords does not match ..."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/PasswordsValidator.java b/TotalRegistration2/src/main/java/org/security/validation/PasswordsValidator.java new file mode 100644 index 0000000..bdd5df7 --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/PasswordsValidator.java @@ -0,0 +1,23 @@ +package org.security.validation; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import org.security.model.User; + +/** + * + * @author newlife + */ +public class PasswordsValidator implements ConstraintValidator { + + @Override + public void initialize(final PasswordsConstraint constraintAnnotation) { + // + } + + @Override + public boolean isValid(final Object obj, final ConstraintValidatorContext context) { + final User user = (User) obj; + return user.getPassword().equals(user.getConfirm()); + } +} diff --git a/TotalRegistration2/src/main/java/org/security/validation/SinglePasswordValidator.java b/TotalRegistration2/src/main/java/org/security/validation/SinglePasswordValidator.java new file mode 100644 index 0000000..487c7ff --- /dev/null +++ b/TotalRegistration2/src/main/java/org/security/validation/SinglePasswordValidator.java @@ -0,0 +1,42 @@ +package org.security.validation; + +import java.util.Arrays; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import org.passay.LengthRule; +import org.passay.PasswordData; +import org.passay.PasswordValidator; +import org.passay.RepeatCharacterRegexRule; +import org.passay.RuleResult; +import org.passay.WhitespaceRule; + +/** + * + * @author newlife + */ +public class SinglePasswordValidator implements ConstraintValidator { + + @Override + public void initialize(final PasswordConstraint constraintAnnotation) { + } + + @Override + public boolean isValid(final String password, final ConstraintValidatorContext context) { + + final PasswordValidator validator = new PasswordValidator(Arrays. + asList(new LengthRule(6, 20), + new RepeatCharacterRegexRule(), + new WhitespaceRule() + // add here more rules + )); + final RuleResult result = validator.validate(new PasswordData(password)); + if (result.isValid()) { + return true; + } + + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(validator.getMessages(result).toString()).addConstraintViolation(); + return false; + } + +} diff --git a/TotalRegistration2/src/main/resources/application.yaml b/TotalRegistration2/src/main/resources/application.yaml new file mode 100644 index 0000000..2efba45 --- /dev/null +++ b/TotalRegistration2/src/main/resources/application.yaml @@ -0,0 +1,50 @@ +spring: + datasource: + driver-class-name: com.mysql.jdbc.Driver + url: jdbc:mysql://localhost:3306/totaldev?createDatabaseIfNotExist=true + username: root + password: java797b + type: com.zaxxer.hikari.HikariDataSource + hikari: + connectionTimeout: 10000 + connectionTestQuery: SELECT 1 FROM DUAL + maxLifetime: 1800000 + minimumIdle: 10 + maximumPoolSize: 10 + idleTimeout: 0 + poolName: TotalRegistrationPool + jpa: + database-platform: org.hibernate.dialect.MySQL5Dialect + open-in-view: false + properties: + javax: + persistence: + validation: + mode: none + hibernate: + show_sql: true + format_sql: true + use_sql_comments: true + globally_quoted_identifiers: true + hibernate: + ddl-auto: none + connection.provider_class: org.hibernate.hikaricp.internal.HikariCPConnectionProvider + mail: + host: localhost + port: 25 + +server: + error: + whitelabel: + enabled: false + +# custom settings +org: + security: + url: + activate: http://localhost:8080/activate/ + +recaptcha: + validation: + secretKey: 6LfSXw0UAAAAAHhqL0VfFsDo23-8Y8u6gD_PQrOk + \ No newline at end of file diff --git a/TotalRegistration2/src/main/resources/logback-spring.xml b/TotalRegistration2/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..e380a8e --- /dev/null +++ b/TotalRegistration2/src/main/resources/logback-spring.xml @@ -0,0 +1,15 @@ + + + + + + + [%thread] %highlight(%-5level) %yellow(%logger{15}) - %msg %n + + + + + + + + diff --git a/TotalRegistration2/src/main/resources/static/css/style.css b/TotalRegistration2/src/main/resources/static/css/style.css new file mode 100644 index 0000000..faf3441 --- /dev/null +++ b/TotalRegistration2/src/main/resources/static/css/style.css @@ -0,0 +1,22 @@ +meter { + /* Reset the default appearance */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +meter::-webkit-meter-bar { + background: none; + background-color: rgba(0,0,0,0.1); +} + +meter[value="1"]::-webkit-meter-optimum-value { background: red; } +meter[value="2"]::-webkit-meter-optimum-value { background: yellow; } +meter[value="3"]::-webkit-meter-optimum-value { background: orange; } +meter[value="4"]::-webkit-meter-optimum-value { background: green; } + +meter[value="1"]::-moz-meter-bar { background: red; } +meter[value="2"]::-moz-meter-bar { background: yellow; } +meter[value="3"]::-moz-meter-bar { background: orange; } +meter[value="4"]::-moz-meter-bar { background: green; } + diff --git a/TotalRegistration2/src/main/resources/static/js/password.js b/TotalRegistration2/src/main/resources/static/js/password.js new file mode 100644 index 0000000..b6e8e7e --- /dev/null +++ b/TotalRegistration2/src/main/resources/static/js/password.js @@ -0,0 +1,27 @@ +var strength = { + 0: "Worst ☹", + 1: "Bad ☹", + 2: "Weak ☹", + 3: "Good ☺", + 4: "Strong ☻" +}; + +var password = document.getElementById('password'); +var meter = document.getElementById('password-strength-meter'); +var text = document.getElementById('password-strength-text'); + +password.addEventListener('input', function () +{ + var val = password.value; + var result = zxcvbn(val); + + // Update the password strength meter + meter.value = result.score; + + // Update the text indicator + if (val !== "") { + text.innerHTML = "Strength: " + "" + strength[result.score] + "" + "