From 292825d1a8e80ce24af2588adde5403c72fa26be Mon Sep 17 00:00:00 2001 From: Alex Prudencio Date: Mon, 21 Oct 2024 21:06:10 -0400 Subject: [PATCH] reset password views --- .gitignore | 1 + pom.xml | 9 + .../primefactorsolutions/model/Employee.java | 255 +++++++++--------- .../repositories/EmployeeRepository.java | 2 + .../service/AccountService.java | 101 +++++++ .../service/EmailService.java | 32 +++ .../service/EmployeeService.java | 33 ++- .../views/EmployeeView.java | 16 ++ .../views/EvaluationView.java | 3 - .../views/InitAccountView.java | 53 ++++ .../primefactorsolutions/views/LoginView.java | 3 + .../views/PasswordRecoveryView.java | 67 +++++ .../views/ResetPasswordView.java | 71 +++++ src/main/resources/application.properties | 2 + 14 files changed, 505 insertions(+), 143 deletions(-) create mode 100644 src/main/java/com/primefactorsolutions/service/AccountService.java create mode 100644 src/main/java/com/primefactorsolutions/service/EmailService.java create mode 100644 src/main/java/com/primefactorsolutions/views/InitAccountView.java create mode 100644 src/main/java/com/primefactorsolutions/views/PasswordRecoveryView.java create mode 100644 src/main/java/com/primefactorsolutions/views/ResetPasswordView.java diff --git a/.gitignore b/.gitignore index d0b9d6a..f71f2f2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ drivers/ # Error screenshots generated by TestBench for failed integration tests error-screenshots/ webpack.generated.js +*.env diff --git a/pom.xml b/pom.xml index a9cae3e..30815f4 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,10 @@ commons-beanutils 1.9.4 + + com.fasterxml.jackson.core + jackson-core + org.mockito mockito-core @@ -240,6 +244,11 @@ freemarker 2.3.32 + + com.auth0 + java-jwt + 4.4.0 + org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java/com/primefactorsolutions/model/Employee.java b/src/main/java/com/primefactorsolutions/model/Employee.java index e128132..d8a11b0 100644 --- a/src/main/java/com/primefactorsolutions/model/Employee.java +++ b/src/main/java/com/primefactorsolutions/model/Employee.java @@ -1,147 +1,144 @@ - package com.primefactorsolutions.model; - import com.google.common.collect.Lists; - import jakarta.persistence.*; - import lombok.AllArgsConstructor; - import lombok.Data; - import lombok.EqualsAndHashCode; - import lombok.NoArgsConstructor; - import org.springframework.security.core.GrantedAuthority; - import org.springframework.security.core.userdetails.UserDetails; +package com.primefactorsolutions.model; - import java.time.LocalDate; - import java.util.Collection; +import com.google.common.collect.Lists; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; - @Data - @Entity - @AllArgsConstructor - @NoArgsConstructor - @EqualsAndHashCode(callSuper = true) - public class Employee extends BaseEntity implements UserDetails { - private String username; - private String firstName; - private String lastName; - private LocalDate birthday; - private String birthCity; - private String age; +import java.time.LocalDate; +import java.util.Collection; - private String residenceAddress; - private String localAddress; - private String phoneNumber; - private String personalEmail; - private String position; - @ManyToOne - @JoinColumn(name = "team_id", nullable = false) - private Team team; - private String emergencyCName; - private String emergencyCAddress; - private String emergencyCPhone; - private String emergencyCEmail; - private String numberOfChildren; +@Data +@Entity +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class Employee extends BaseEntity implements UserDetails { + private String username; + private String firstName; + private String lastName; + private LocalDate birthday; + private String birthCity; + private String age; - private String ci; - private String issuedIn; + private String residenceAddress; + private String localAddress; + private String phoneNumber; + private String personalEmail; + private String position; + @ManyToOne + @JoinColumn(name = "team_id", nullable = false) + private Team team; + private String emergencyCName; + private String emergencyCAddress; + private String emergencyCPhone; + private String emergencyCEmail; + private String numberOfChildren; - private String pTitle1; - private String pTitle2; - private String pTitle3; + private String ci; + private String issuedIn; - private String pStudy1; - private String pStudy2; - private String pStudy3; + private String pTitle1; + private String pTitle2; + private String pTitle3; - private String certification1; - private String certification2; - private String certification3; - private String certification4; + private String pStudy1; + private String pStudy2; + private String pStudy3; - private String recognition; - private String achievements; + private String certification1; + private String certification2; + private String certification3; + private String certification4; - private String language; - private String languageLevel; + private String recognition; + private String achievements; - private String cod; - private String leadManager; - private String project; + private String language; + private String languageLevel; - private LocalDate dateOfEntry; - private LocalDate dateOfExit; + private String cod; + private String leadManager; + private String project; - private String contractType; - private String seniority; - private String salary; + private LocalDate dateOfEntry; + private LocalDate dateOfExit; - private String bankName; - private String accountNumber; + private String contractType; + private String seniority; + private String salary; - private String gpss; - private String sss; - private String beneficiaries; + private String bankName; + private String accountNumber; - @Column(columnDefinition = "TEXT") - private String profileImage; - @Enumerated(EnumType.STRING) - private Status status; + private String gpss; + private String sss; + private String beneficiaries; - @Override - public Collection getAuthorities() { - return Lists.newArrayList(); - } + @Column(columnDefinition = "TEXT") + private String profileImage; + @Enumerated(EnumType.STRING) + private Status status; - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return this.username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - public enum Status { - ACTIVE, - INACTIVE - } - @Enumerated(EnumType.STRING) - private MaritalStatus maritalStatus; - public enum MaritalStatus { - SINGLE, - MARRIED, - WIDOWED, - DIVORCED - } - @Enumerated(EnumType.STRING) - private Gender gender; - public enum Gender { - MALE, - FEMALE - } - - public Status getStatus() { - - return status; - } - public void setStatus(final Status status) { - this.status = status; - } + @Override + public Collection getAuthorities() { + return Lists.newArrayList(); } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public enum Status { + ACTIVE, + INACTIVE + } + + @Enumerated(EnumType.STRING) + private MaritalStatus maritalStatus; + + public enum MaritalStatus { + SINGLE, + MARRIED, + WIDOWED, + DIVORCED + } + + @Enumerated(EnumType.STRING) + private Gender gender; + + public enum Gender { + MALE, + FEMALE + } +} diff --git a/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java b/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java index e27a88b..3016f8e 100644 --- a/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java +++ b/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java @@ -8,4 +8,6 @@ import java.util.UUID; public interface EmployeeRepository extends JpaRepository { Optional findByUsername(String username); + + Optional findByPersonalEmail(String personalEmail); } diff --git a/src/main/java/com/primefactorsolutions/service/AccountService.java b/src/main/java/com/primefactorsolutions/service/AccountService.java new file mode 100644 index 0000000..8a4bf76 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/service/AccountService.java @@ -0,0 +1,101 @@ +package com.primefactorsolutions.service; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.primefactorsolutions.model.Employee; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Map; + +@Service +@Slf4j +public class AccountService { + private final EmailService emailService; + private final EmployeeService employeeService; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final String secret; + + public AccountService(final EmailService emailService, final EmployeeService employeeService, + @Value("${application.jwtSecret}") final String secret) { + this.emailService = emailService; + this.employeeService = employeeService; + this.secret = secret; + } + + public void sendResetPasswordEmail(final String personalEmail) { + final Employee employee = employeeService.getEmployeeByPersonalEmail(personalEmail); + + if (employee == null) { + log.warn("Could not find employee for email {}", personalEmail); + return; + } + + final String link = createResetPasswordLink(employee.getUsername()); + final String content = "Visit this link to reset your password: " + link; + emailService.sendEmail(personalEmail, "PFS - Reset Password", content); + } + + public void resetPassword(final String username, final String newPassword, final String token) { + DecodedJWT decodedJWT; + + try { + Algorithm algorithm = Algorithm.HMAC512(secret); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer("pfs") + .build(); + + decodedJWT = verifier.verify(token); + final Map payload = (Map) objectMapper.readValue(decodedJWT.getPayload(), Map.class); + + if (Instant.parse((String) payload.get("expire")).isBefore(Instant.now()) + || !username.equals(payload.get("username"))) { + log.warn("token invalid {} {} {}", username, payload.get("username"), payload.get("expire")); + return; + } + } catch (JWTVerificationException | JsonProcessingException e) { + return; + } + + final Employee employee = employeeService.getDetachedEmployeeByUsername(username); + + if (employee == null) { + log.warn("Could not find employee for username {}", username); + return; + } + + if (StringUtils.isBlank(newPassword) || newPassword.length() < 8) { + throw new IllegalArgumentException("New password should be at least 8 chars long"); + } + + employeeService.updatePassword(employee, newPassword); + } + + private String createResetPasswordLink(final String username) { + String token = ""; + + try { + Algorithm algorithm = Algorithm.HMAC512(secret); + token = JWT.create() + .withIssuer("pfs") + .withPayload(objectMapper.writeValueAsString(Map.of("username", username, + "expire", Instant.now().plus(1, ChronoUnit.HOURS).toString()))) + .sign(algorithm); + } catch (JWTCreationException | JsonProcessingException e) { + throw new RuntimeException(e); + } + + return String.format("https://intra.primefactorsolutions.com/reset-password?username=%s&token=%s", username, + token); + } +} diff --git a/src/main/java/com/primefactorsolutions/service/EmailService.java b/src/main/java/com/primefactorsolutions/service/EmailService.java new file mode 100644 index 0000000..8045887 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/service/EmailService.java @@ -0,0 +1,32 @@ +package com.primefactorsolutions.service; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +@Slf4j +public class EmailService { + public static final String NO_REPLY_PRIMEFACTORSOLUTIONS_COM = "no-reply@primefactorsolutions.com"; + private final JavaMailSender emailSender; + + public void sendEmail(final String email, final String title, final String messageContent) { + try { + final SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(NO_REPLY_PRIMEFACTORSOLUTIONS_COM); + message.setBcc(NO_REPLY_PRIMEFACTORSOLUTIONS_COM); + message.setTo(email); + message.setSubject(title); + message.setText(messageContent); + + emailSender.send(message); + log.info("Sent email to {}", email); + } catch (Exception e) { + log.error("Error sending email to {}", email, e); + throw e; + } + } +} diff --git a/src/main/java/com/primefactorsolutions/service/EmployeeService.java b/src/main/java/com/primefactorsolutions/service/EmployeeService.java index 4313877..d273ac4 100644 --- a/src/main/java/com/primefactorsolutions/service/EmployeeService.java +++ b/src/main/java/com/primefactorsolutions/service/EmployeeService.java @@ -18,12 +18,18 @@ import java.util.Collections; @Service @AllArgsConstructor public class EmployeeService { + private static final String USERPASSWORD = "userpassword"; + private static final String OBJECTCLASS = "objectclass"; + private static final String ORGANIZATIONAL_PERSON = "organizationalPerson"; + private static final String INET_ORG_PERSON = "inetOrgPerson"; + private static final String TOP = "top"; + private static final String PERSON = "person"; + public static final String BASE_DN = "dc=primefactorsolutions,dc=com"; + private final EmployeeRepository employeeRepository; private final LdapTemplate ldapTemplate; private final EntityManager entityManager; - public static final String BASE_DN = "dc=primefactorsolutions,dc=com"; - protected Name buildDn(final Employee employee) { return LdapNameBuilder.newInstance(BASE_DN) .add("ou", "users") @@ -63,6 +69,10 @@ public class EmployeeService { return employees.subList(start, end); } + public Employee getEmployeeByPersonalEmail(final String email) { + return employeeRepository.findByPersonalEmail(email).orElse(null); + } + public Employee createOrUpdate(final Employee employee) { if (employee.getId() == null) { final Name dn = buildDn(employee); @@ -74,28 +84,29 @@ public class EmployeeService { } public Employee getEmployee(final UUID id) { - Optional employee = employeeRepository.findById(id); + final Optional employee = employeeRepository.findById(id); + return employee.orElse(null); } private Attributes buildAttributes(final Employee employee) { final Attributes attrs = new BasicAttributes(); - final BasicAttribute ocattr = new BasicAttribute("objectclass"); - ocattr.add("top"); - ocattr.add("person"); - ocattr.add("organizationalPerson"); - ocattr.add("inetOrgPerson"); + final BasicAttribute ocattr = new BasicAttribute(OBJECTCLASS); + ocattr.add(TOP); + ocattr.add(PERSON); + ocattr.add(ORGANIZATIONAL_PERSON); + ocattr.add(INET_ORG_PERSON); attrs.put(ocattr); attrs.put("cn", String.format("%s %s", employee.getFirstName(), employee.getLastName())); attrs.put("sn", String.format("%s %s", employee.getFirstName(), employee.getLastName())); attrs.put("uid", employee.getUsername()); - attrs.put("userpassword", String.format("%s%s", employee.getUsername(), 123)); + attrs.put(USERPASSWORD, String.format("%s%s", employee.getUsername(), 123)); return attrs; } - public void updatePassword(final Employee employee) { - final Attribute attr = new BasicAttribute("userpassword", employee.getUsername() + "123"); + public void updatePassword(final Employee employee, final String newPassword) { + final Attribute attr = new BasicAttribute(USERPASSWORD, newPassword); final ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); ldapTemplate.modifyAttributes(buildDn(employee), new ModificationItem[] {item}); diff --git a/src/main/java/com/primefactorsolutions/views/EmployeeView.java b/src/main/java/com/primefactorsolutions/views/EmployeeView.java index bdf5a82..9296f55 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeeView.java @@ -1,6 +1,7 @@ package com.primefactorsolutions.views; import com.primefactorsolutions.model.Employee; +import com.primefactorsolutions.model.Team; import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.ReportService; import com.vaadin.componentfactory.pdfviewer.PdfViewer; @@ -22,6 +23,9 @@ import com.vaadin.flow.component.textfield.EmailField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MemoryBuffer; +import com.vaadin.flow.data.binder.Result; +import com.vaadin.flow.data.binder.ValueContext; +import com.vaadin.flow.data.converter.Converter; import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.router.*; import com.vaadin.flow.server.StreamResource; @@ -143,6 +147,18 @@ public class EmployeeView extends BeanValidationForm implements HasUrl configureComponents(); addClassName("main-layout"); + + getBinder().setConverter("team", new Converter() { + @Override + public Result convertToModel(final Object o, final ValueContext valueContext) { + return Result.ok(new Team((String) o)); + } + + @Override + public Object convertToPresentation(final Object o, final ValueContext valueContext) { + return ((Team) o).getName(); + } + }); } private void configureComponents() { diff --git a/src/main/java/com/primefactorsolutions/views/EvaluationView.java b/src/main/java/com/primefactorsolutions/views/EvaluationView.java index 95298de..84a1a36 100644 --- a/src/main/java/com/primefactorsolutions/views/EvaluationView.java +++ b/src/main/java/com/primefactorsolutions/views/EvaluationView.java @@ -196,7 +196,6 @@ public class EvaluationView extends Main implements HasUrlParameter { final MenuBar navMenuBar = new MenuBar(); prev = navMenuBar.addItem("Anterior pregunta", (ComponentEventListener>) menuItemClickEvent -> { - log.info(">>> prev"); this.currSubmission.setResponse(this.questionEditor.getValue()); this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission); @@ -253,7 +252,6 @@ public class EvaluationView extends Main implements HasUrlParameter { start = new Button("Empezar"); start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); start.addClickListener((ComponentEventListener>) buttonClickEvent -> { - log.info(">>> start"); this.assessment = this.assessmentService.startAssessment(this.assessment.getId()); if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) { @@ -318,7 +316,6 @@ public class EvaluationView extends Main implements HasUrlParameter { } private void goToNext() { - log.info(">>> next"); Submission found = this.assessmentService.getNextSubmission(assessment.getId(), this.currSubmission.getId()); diff --git a/src/main/java/com/primefactorsolutions/views/InitAccountView.java b/src/main/java/com/primefactorsolutions/views/InitAccountView.java new file mode 100644 index 0000000..2cc8524 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/views/InitAccountView.java @@ -0,0 +1,53 @@ +package com.primefactorsolutions.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.PasswordField; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.BeforeEnterObserver; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; + +@Route("init-account") +@PageTitle("PFS Intra") +@AnonymousAllowed +public class InitAccountView extends VerticalLayout implements BeforeEnterObserver { + + public InitAccountView() { + setSizeFull(); + setAlignItems(Alignment.CENTER); + setJustifyContentMode(JustifyContentMode.CENTER); + + final VerticalLayout vl = new VerticalLayout(); + vl.setJustifyContentMode(JustifyContentMode.CENTER); + vl.setWidth("400px"); + + final PasswordField password = new PasswordField("Password"); + final PasswordField confirmPassword = new PasswordField("Confirm Password"); + + final FormLayout formLayout = new FormLayout(password, confirmPassword); + formLayout.setColspan(password, 3); + formLayout.setColspan(confirmPassword, 3); + + final Button primaryButton = new Button("Submit"); + primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + + final Button secondaryButton = new Button("Cancel"); + HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton); + + vl.add(new H3("Set Account Password")); + vl.add(formLayout); + vl.add(hl); + + add(vl); + } + + @Override + public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) { + } +} \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/views/LoginView.java b/src/main/java/com/primefactorsolutions/views/LoginView.java index 6864493..9a280c5 100644 --- a/src/main/java/com/primefactorsolutions/views/LoginView.java +++ b/src/main/java/com/primefactorsolutions/views/LoginView.java @@ -1,5 +1,6 @@ package com.primefactorsolutions.views; +import com.vaadin.flow.component.html.Anchor; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.login.LoginForm; import com.vaadin.flow.component.orderedlayout.VerticalLayout; @@ -23,9 +24,11 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver { setJustifyContentMode(JustifyContentMode.CENTER); login.setAction("login"); + login.setForgotPasswordButtonVisible(false); add(new H1("PFS Intra")); add(login); + add(new Anchor("/password-recovery", "Reset password?")); } @Override diff --git a/src/main/java/com/primefactorsolutions/views/PasswordRecoveryView.java b/src/main/java/com/primefactorsolutions/views/PasswordRecoveryView.java new file mode 100644 index 0000000..2d9c4a5 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/views/PasswordRecoveryView.java @@ -0,0 +1,67 @@ +package com.primefactorsolutions.views; + +import com.primefactorsolutions.service.AccountService; +import com.vaadin.flow.component.ClickEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.EmailField; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.BeforeEnterObserver; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; + +@Route("password-recovery") +@PageTitle("PFS Intra") +@AnonymousAllowed +public class PasswordRecoveryView extends VerticalLayout implements BeforeEnterObserver { + + public PasswordRecoveryView(final AccountService accountService) { + setSizeFull(); + setAlignItems(Alignment.CENTER); + setJustifyContentMode(JustifyContentMode.CENTER); + + final VerticalLayout vl = new VerticalLayout(); + vl.setJustifyContentMode(JustifyContentMode.CENTER); + vl.setWidth("400px"); + + final EmailField personalEmail = new EmailField("Personal Email"); + personalEmail.setRequired(true); + + final EmailField confirmPersonalEmail = new EmailField("Confirm Personal Email"); + confirmPersonalEmail.setRequired(true); + + final FormLayout formLayout = new FormLayout(personalEmail, confirmPersonalEmail); + formLayout.setColspan(personalEmail, 3); + formLayout.setColspan(confirmPersonalEmail, 3); + + final Button primaryButton = new Button("Submit"); + primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + primaryButton.addClickListener((ComponentEventListener>) buttonClickEvent -> { + if (personalEmail.getValue().equals(confirmPersonalEmail.getValue())) { + accountService.sendResetPasswordEmail(personalEmail.getValue()); + getUI().ifPresent(ui -> ui.navigate(MainView.class)); + } + }); + + final Button secondaryButton = new Button("Cancel"); + final HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton); + secondaryButton.addClickListener((ComponentEventListener>) buttonClickEvent -> + getUI().ifPresent(ui -> ui.navigate(MainView.class))); + + vl.add(new H3("PFS - Password Recovery")); + vl.add(formLayout); + vl.add(hl); + + add(vl); + } + + @Override + public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) { + } +} \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/views/ResetPasswordView.java b/src/main/java/com/primefactorsolutions/views/ResetPasswordView.java new file mode 100644 index 0000000..c39edb2 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/views/ResetPasswordView.java @@ -0,0 +1,71 @@ +package com.primefactorsolutions.views; + +import com.primefactorsolutions.service.AccountService; +import com.vaadin.flow.component.ClickEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.PasswordField; +import com.vaadin.flow.router.*; +import com.vaadin.flow.server.auth.AnonymousAllowed; + +import java.util.List; + +@Route("reset-password") +@PageTitle("PFS Intra") +@AnonymousAllowed +public class ResetPasswordView extends VerticalLayout implements BeforeEnterObserver { + private String username; + private String token; + + public ResetPasswordView(final AccountService accountService) { + setSizeFull(); + setAlignItems(Alignment.CENTER); + setJustifyContentMode(JustifyContentMode.CENTER); + + final VerticalLayout vl = new VerticalLayout(); + vl.setJustifyContentMode(JustifyContentMode.CENTER); + vl.setWidth("400px"); + + final PasswordField password = new PasswordField("Password"); + final PasswordField confirmPassword = new PasswordField("Confirm Password"); + + final FormLayout formLayout = new FormLayout(password, confirmPassword); + formLayout.setColspan(password, 3); + formLayout.setColspan(confirmPassword, 3); + + final Button primaryButton = new Button("Submit"); + primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + primaryButton.addClickListener((ComponentEventListener>) buttonClickEvent -> { + accountService.resetPassword(username, password.getValue(), token); + getUI().ifPresent(ui -> ui.navigate(MainView.class)); + }); + + final Button secondaryButton = new Button("Cancel"); + secondaryButton.addClickListener((ComponentEventListener>) buttonClickEvent -> + getUI().ifPresent(ui -> ui.navigate(MainView.class))); + + HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton); + + vl.add(new H3("PFS - Reset Password")); + vl.add(formLayout); + vl.add(hl); + + add(vl); + } + + @Override + public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) { + final Location location = beforeEnterEvent.getLocation(); + final QueryParameters queryParameters = location.getQueryParameters(); + + this.username = queryParameters.getParameters().getOrDefault("username", List.of()).stream() + .findFirst().orElse(null); + this.token = queryParameters.getParameters().getOrDefault("token", List.of()).stream() + .findFirst().orElse(null); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f88aa2f..3b6c54e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -44,3 +44,5 @@ spring.sql.init.mode=${SQL_INIT:embedded} spring.h2.console.enabled=true spring.h2.console.settings.web-allow-others=true + +application.jwtSecret=${JWT_SECRET} \ No newline at end of file