From 54ab686d1db3dd52672e0e6503be329cf13b49de Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 14 Jul 2026 22:37:57 -0700 Subject: [PATCH 01/42] Added new columns to teacher data and modified welcome email --- .../org/wise/portal/dao/user/UserDao.java | 1 + .../dao/user/impl/HibernateUserDao.java | 12 +++++++++-- .../impl/TeacherUserDetails.java | 16 ++++++++++++++ .../teacher/TeacherAPIController.java | 21 ++++++++++++++----- .../controllers/user/UserAPIController.java | 1 + .../wise/portal/service/user/UserService.java | 2 ++ .../service/user/impl/UserServiceImpl.java | 4 ++++ src/main/resources/i18n/i18n.properties | 2 +- 8 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/wise/portal/dao/user/UserDao.java b/src/main/java/org/wise/portal/dao/user/UserDao.java index b0d8715c3b..fbc10f77d3 100644 --- a/src/main/java/org/wise/portal/dao/user/UserDao.java +++ b/src/main/java/org/wise/portal/dao/user/UserDao.java @@ -46,6 +46,7 @@ List retrieveStudentsByNameAndBirthday(String firstName, String lastName, List retrieveTeachersByFirstName(String firstName); List retrieveTeachersByLastName(String lastName); User retrieveTeacherByUsername(String username); + User retrieveTeacherByVerificationCode(String verificationCode); List retrieveTeachersByDisplayName(String displayName); List retrieveTeachersByCity(String city); List retrieveTeachersByState(String state); diff --git a/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java b/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java index 6a97b31d53..e6d090b429 100644 --- a/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java +++ b/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java @@ -148,8 +148,11 @@ public List retrieveTeachersByLastName(String lastName) { } public User retrieveTeacherByUsername(String username) { - List resultList = retrieveTeachersByFieldValue("username", username); - return resultList.isEmpty() ? null : resultList.get(0); + return retrieveTeacherByFieldValue("username", username); + } + + public User retrieveTeacherByVerificationCode(String verificationCode) { + return retrieveTeacherByFieldValue("verificationCode", verificationCode); } public List retrieveTeachersByDisplayName(String displayName) { @@ -176,6 +179,11 @@ public List retrieveTeachersByEmail(String emailAddress) { return retrieveTeachersByFieldValue("emailAddress", emailAddress); } + private User retrieveTeacherByFieldValue(String field, String value) { + List resultList = retrieveTeachersByFieldValue(field, value); + return resultList.isEmpty() ? null : resultList.get(0); + } + @SuppressWarnings("unchecked") private List retrieveTeachersByFieldValue(String field, String value) { CriteriaBuilder cb = getCriteriaBuilder(); diff --git a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java index 76f7e51340..52b53ff702 100644 --- a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java +++ b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java @@ -93,6 +93,12 @@ public class TeacherUserDetails extends PersistentUserDetails implements Mutable @Transient private static final String COLUMN_NAME_HOW_HEAR = "howDidYouHearAboutUs"; + @Transient + private static final String COLUMN_NAME_VERIFIED = "isVerified"; + + @Transient + private static final String COLUMN_NAME_VERIFICATION_CODE = "verificationCode"; + @Transient private static final long serialVersionUID = 1L; @@ -164,6 +170,16 @@ public class TeacherUserDetails extends PersistentUserDetails implements Mutable @Setter private String howDidYouHearAboutUs; + @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFIED, nullable = false) + @Getter + @Setter + private boolean verified = false; + + @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFICATION_CODE) + @Getter + @Setter + private String verificationCode; + public String getCoreUsername() { return (firstname + lastname).replaceAll("[\\s-]+", ""); } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 559ba9040a..87c1f3d701 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.UUID; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -178,19 +179,22 @@ ResponseEntity> createTeacherAccount( tud.setHowDidYouHearAboutUs(teacherFields.get("howDidYouHearAboutUs")); Locale locale = request.getLocale(); tud.setLanguage(locale.getLanguage()); + tud.setVerified(false); + String verificationCode = UUID.randomUUID().toString(); + tud.setVerificationCode(verificationCode); User createdUser = this.userService.createUser(tud); String username = createdUser.getUserDetails().getUsername(); String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); - Boolean iSendEmailEnabled = Boolean.valueOf(sendEmailEnabledStr); + Boolean isSendEmailEnabled = Boolean.valueOf(sendEmailEnabledStr); boolean socialAccount = this.isSet(googleUserId) || this.isSet(microsoftUserId); - if (iSendEmailEnabled) { - sendCreateTeacherAccountEmail(email, displayName, username, socialAccount, locale, request); + if (isSendEmailEnabled) { + sendCreateTeacherAccountEmail(email, displayName, username, socialAccount, locale, verificationCode, request); } return createRegisterSuccessResponse(username); } private void sendCreateTeacherAccountEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, HttpServletRequest request) { + boolean socialAccount, Locale locale, String verificationCode, HttpServletRequest request) { String fromEmail = appProperties.getProperty("portalemailaddress"); String[] recipients = { email }; String defaultSubject = messageSource.getMessage( @@ -209,9 +213,10 @@ private void sendCreateTeacherAccountEmail(String email, String displayName, Str "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername", new Object[] { displayName, gettingStartedUrl }, defaultBody, locale); } else { + String verificationUrl = getVerificationUrl(request, username, verificationCode); message = messageSource.getMessage( "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", - new Object[] { displayName, username, gettingStartedUrl }, defaultBody, locale); + new Object[] { displayName, username, verificationUrl, gettingStartedUrl }, defaultBody, locale); } try { mailService.postMail(recipients, subject, message, fromEmail); @@ -220,6 +225,12 @@ private void sendCreateTeacherAccountEmail(String email, String displayName, Str } } + private String getVerificationUrl(HttpServletRequest request, String username, + String verificationCode) { + return String.format("%s/api/teacher/verify?username=%s&code=%s", + ControllerUtil.getPortalUrlString(request), username, verificationCode); + } + private String getGettingStartedUrl(HttpServletRequest request) { return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java index ca965b81fb..a08b962c13 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java @@ -118,6 +118,7 @@ HashMap getUserInfo(Authentication auth, info.put("country", tud.getCountry()); info.put("schoolName", tud.getSchoolname()); info.put("schoolLevel", tud.getSchoollevel()); + info.put("isVerified", tud.isVerified()); } return info; } else { diff --git a/src/main/java/org/wise/portal/service/user/UserService.java b/src/main/java/org/wise/portal/service/user/UserService.java index 0d1cdbcc8e..4bcd3aa627 100644 --- a/src/main/java/org/wise/portal/service/user/UserService.java +++ b/src/main/java/org/wise/portal/service/user/UserService.java @@ -155,6 +155,8 @@ List retrieveStudentsByNameAndBirthday(String firstName, String lastName, User retrieveTeacherByUsername(String username); + User retrieveTeacherByVerificationCode(String verificationCode); + List retrieveTeachersByDisplayName(String displayName); List retrieveTeachersByCity(String city); diff --git a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java index 79446d49a5..d7c9a0d479 100644 --- a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java +++ b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java @@ -208,6 +208,10 @@ public User retrieveTeacherByUsername(String username) { return userDao.retrieveTeacherByUsername(username); } + public User retrieveTeacherByVerificationCode(String verificationCode) { + return userDao.retrieveTeacherByVerificationCode(verificationCode); + } + public List retrieveTeachersByDisplayName(String displayName) { return userDao.retrieveTeachersByDisplayName(displayName); } diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties index 92d0bd5d2f..9f5c52d00a 100644 --- a/src/main/resources/i18n/i18n.properties +++ b/src/main/resources/i18n/i18n.properties @@ -3756,7 +3756,7 @@ presentation.web.controllers.teacher.project.customized.ShareProjectController.s presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject=Welcome to WISE! presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject.description=Text "Welcome to WISE!" to welcome new teachers in a welcome email. -presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nFor more information on how to get started, please visit our Getting Started page.\n{2}\n\nThank you for joining WISE,\nWISE Team +presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nPlease click the following link to verify your email address and sign in: {2}\n\nFor more information on how to get started, please visit our Getting Started page.\n{3}\n\nThank you for joining WISE,\nWISE Team presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody.description=Body of the Welcome email message sent to new WISE teachers. presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername=Hi {0}!\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nFor more information on how to get started, please visit our Getting Started page.\n{1}\n\nThank you for joining WISE,\nWISE Team presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername.description=Body of the Welcome email message sent to new WISE teachers that use Google ID to register. From 9f8cfb0f3f3ba9680ca267b9a7a577e5d6409a83 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 14 Jul 2026 22:52:06 -0700 Subject: [PATCH 02/42] Verification endpoints --- .../user/UserVerificationAPIController.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java new file mode 100644 index 0000000000..d784290489 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java @@ -0,0 +1,59 @@ +package org.wise.portal.presentation.web.controllers.user; + +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.service.user.UserService; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + + +@RestController +@RequestMapping("/api/user") +public class UserVerificationAPIController { + + private final UserService userService; + + public UserVerificationAPIController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/verify") + public void openVerificationLink(@RequestParam String code, @RequestParam String username, + HttpServletResponse response) throws IOException { + this.verifyTeacherAccount(code); + response.sendRedirect("/login?username=" + username); + } + + private void verifyTeacherAccount(String verificationCode) { + User user = userService.retrieveTeacherByVerificationCode(verificationCode); + if (this.isTeacher(user)) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (!tud.isVerified()) { + tud.setVerified(true); + userService.updateUser(user); + } + } + } + + private boolean isTeacher(User user) { + return user != null && !user.getRoles().contains("ROLE_STUDENT"); + } + + @GetMapping("/is-verified") + public boolean isUserVerified(@RequestParam String username) { + User user = userService.retrieveTeacherByUsername(username); + return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts + } + + private boolean isTeacherVerified(User user) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + return tud.isVerified(); + } +} From 8e2d7025d241c98f4d01d4135c24380f5c1ead5d Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 14 Jul 2026 23:04:09 -0700 Subject: [PATCH 03/42] Update verification URL to match endpoint --- .../web/controllers/teacher/TeacherAPIController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 87c1f3d701..f453aff06c 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -227,7 +227,7 @@ private void sendCreateTeacherAccountEmail(String email, String displayName, Str private String getVerificationUrl(HttpServletRequest request, String username, String verificationCode) { - return String.format("%s/api/teacher/verify?username=%s&code=%s", + return String.format("%s/api/user/verify?username=%s&code=%s", ControllerUtil.getPortalUrlString(request), username, verificationCode); } From ac5ed8094b2392994e7537c1c7be20d26f29c99c Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 15 Jul 2026 15:41:27 -0700 Subject: [PATCH 04/42] Show a confirmation when a user's account has just been verified --- .../controllers/user/UserVerificationAPIController.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java index d784290489..207c3380eb 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java @@ -27,19 +27,21 @@ public UserVerificationAPIController(UserService userService) { @GetMapping("/verify") public void openVerificationLink(@RequestParam String code, @RequestParam String username, HttpServletResponse response) throws IOException { - this.verifyTeacherAccount(code); - response.sendRedirect("/login?username=" + username); + boolean verified = this.verifyTeacherAccount(code); + response.sendRedirect("/login?verified=" + verified + "&username=" + username); } - private void verifyTeacherAccount(String verificationCode) { + private boolean verifyTeacherAccount(String verificationCode) { User user = userService.retrieveTeacherByVerificationCode(verificationCode); if (this.isTeacher(user)) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); if (!tud.isVerified()) { tud.setVerified(true); userService.updateUser(user); + return true; } } + return false; } private boolean isTeacher(User user) { From 8cce7f5e729af3fb884b1a22bef29a3a29c3028d Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 16 Jul 2026 15:48:59 -0700 Subject: [PATCH 05/42] Added endpoint to resend the verification email --- .../teacher/TeacherAPIController.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index f453aff06c..a303db9ce3 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -239,6 +239,26 @@ private boolean isSet(String value) { return value != null && !value.isEmpty(); } + @PostMapping("/verify-email") + @Secured({ "ROLE_ANONYMOUS" }) + ResponseEntity> resendCreateTeacherAccountEmail(@RequestParam String username, HttpServletRequest request) { + User user = userService.retrieveTeacherByUsername(username); + if (user == null) { + return ResponseEntityGenerator.createError("usernameNotFound"); + } + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (tud.isVerified()) { + return ResponseEntityGenerator.createError("accountAlreadyVerified"); + } + String email = tud.getEmailAddress(); + String displayName = tud.getFirstname() + " " + tud.getLastname(); + String verificationCode = tud.getVerificationCode(); + userService.updateUser(user); + Locale locale = request.getLocale(); + sendCreateTeacherAccountEmail(email, displayName, username, false, locale, verificationCode, request); + return createRegisterSuccessResponse(username); + } + private List> getRunSharedOwnersList(Run run) { List> sharedOwners = new ArrayList>(); for (User sharedOwner : run.getSharedowners()) { From 6d52c633a3be03bc9ebc10639d78ffb3105f5211 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 16 Jul 2026 16:21:29 -0700 Subject: [PATCH 06/42] Minor change to email --- src/main/resources/i18n/i18n.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties index 9f5c52d00a..ec43dd3f67 100644 --- a/src/main/resources/i18n/i18n.properties +++ b/src/main/resources/i18n/i18n.properties @@ -3756,7 +3756,7 @@ presentation.web.controllers.teacher.project.customized.ShareProjectController.s presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject=Welcome to WISE! presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject.description=Text "Welcome to WISE!" to welcome new teachers in a welcome email. -presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nPlease click the following link to verify your email address and sign in: {2}\n\nFor more information on how to get started, please visit our Getting Started page.\n{3}\n\nThank you for joining WISE,\nWISE Team +presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nPlease click the following link to verify your email address and sign in:\n{2}\n\nFor more information on how to get started, please visit our Getting Started page.\n{3}\n\nThank you for joining WISE,\nWISE Team presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody.description=Body of the Welcome email message sent to new WISE teachers. presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername=Hi {0}!\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nFor more information on how to get started, please visit our Getting Started page.\n{1}\n\nThank you for joining WISE,\nWISE Team presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername.description=Body of the Welcome email message sent to new WISE teachers that use Google ID to register. From 92e45aba109fd0ee30506fa441a7b151b41533b4 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 09:56:23 -0700 Subject: [PATCH 07/42] Move teacher registration into separate class --- .../teacher/TeacherAPIController.java | 109 --------------- .../TeacherRegistrationAPIController.java | 127 ++++++++++++++++++ .../teacher/TeacherAPIControllerTest.java | 43 ------ .../TeacherRegistrationAPIControllerTest.java | 77 +++++++++++ 4 files changed, 204 insertions(+), 152 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 559ba9040a..92e6baadd5 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -4,24 +4,19 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import java.util.TreeSet; -import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.security.acls.model.Permission; import org.springframework.security.core.Authentication; 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.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -32,13 +27,8 @@ import org.wise.portal.domain.group.Group; import org.wise.portal.domain.run.Run; import org.wise.portal.domain.user.User; -import org.wise.portal.domain.usertag.UserTag; -import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.presentation.web.controllers.user.UserAPIController; -import org.wise.portal.presentation.web.exception.InvalidNameException; -import org.wise.portal.presentation.web.response.ResponseEntityGenerator; import org.wise.portal.presentation.web.response.SimpleResponse; -import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; import org.wise.portal.service.usertags.UserTagsService; @@ -129,105 +119,6 @@ List getAllTeacherUsernames() { return userDetailsService.retrieveAllTeacherUsernames(); } - @PostMapping("/register") - @Secured({ "ROLE_ANONYMOUS" }) - ResponseEntity> createTeacherAccount( - @RequestBody Map teacherFields, HttpServletRequest request) - throws DuplicateUsernameException, InvalidNameException { - if (ControllerUtil.isReCaptchaEnabled()) { - String token = teacherFields.get("token"); - if (!ControllerUtil.isReCaptchaResponseValid(token)) { - return ResponseEntityGenerator.createError("recaptchaResponseInvalid"); - } - } - TeacherUserDetails tud = new TeacherUserDetails(); - String firstName = teacherFields.get("firstName"); - String lastName = teacherFields.get("lastName"); - if (!isFirstNameAndLastNameValid(firstName, lastName)) { - String messageCode = this.getInvalidNameMessageCode(firstName, lastName); - throw new InvalidNameException(messageCode); - } - tud.setFirstname(firstName); - tud.setLastname(lastName); - String email = teacherFields.get("email"); - tud.setEmailAddress(email); - tud.setCity(teacherFields.get("city")); - tud.setState(teacherFields.get("state")); - tud.setCountry(teacherFields.get("country")); - String googleUserId = teacherFields.get("googleUserId"); - String microsoftUserId = teacherFields.get("microsoftUserId"); - if (isSet(googleUserId)) { - tud.setGoogleUserId(googleUserId); - tud.setPassword(RandomStringUtils.random(10, true, true)); - } else if (isSet(microsoftUserId)) { - tud.setMicrosoftUserId(microsoftUserId); - tud.setPassword(RandomStringUtils.random(10, true, true)); - } else { - String password = teacherFields.get("password"); - if (!passwordService.isValid(password)) { - return ResponseEntityGenerator.createError(passwordService.getErrors(password)); - } else { - tud.setPassword(password); - } - } - String displayName = firstName + " " + lastName; - tud.setDisplayname(displayName); - tud.setEmailValid(true); - tud.setSchoollevel(Schoollevel.valueOf(teacherFields.get("schoolLevel"))); - tud.setSchoolname(teacherFields.get("schoolName")); - tud.setHowDidYouHearAboutUs(teacherFields.get("howDidYouHearAboutUs")); - Locale locale = request.getLocale(); - tud.setLanguage(locale.getLanguage()); - User createdUser = this.userService.createUser(tud); - String username = createdUser.getUserDetails().getUsername(); - String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); - Boolean iSendEmailEnabled = Boolean.valueOf(sendEmailEnabledStr); - boolean socialAccount = this.isSet(googleUserId) || this.isSet(microsoftUserId); - if (iSendEmailEnabled) { - sendCreateTeacherAccountEmail(email, displayName, username, socialAccount, locale, request); - } - return createRegisterSuccessResponse(username); - } - - private void sendCreateTeacherAccountEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, HttpServletRequest request) { - String fromEmail = appProperties.getProperty("portalemailaddress"); - String[] recipients = { email }; - String defaultSubject = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject", - null, Locale.US); - String subject = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject", - null, defaultSubject, locale); - String defaultBody = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", - new Object[] { username }, Locale.US); - String gettingStartedUrl = getGettingStartedUrl(request); - String message; - if (socialAccount) { - message = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername", - new Object[] { displayName, gettingStartedUrl }, defaultBody, locale); - } else { - message = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", - new Object[] { displayName, username, gettingStartedUrl }, defaultBody, locale); - } - try { - mailService.postMail(recipients, subject, message, fromEmail); - } catch (MessagingException e) { - e.printStackTrace(); - } - } - - private String getGettingStartedUrl(HttpServletRequest request) { - return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; - } - - private boolean isSet(String value) { - return value != null && !value.isEmpty(); - } - private List> getRunSharedOwnersList(Run run) { List> sharedOwners = new ArrayList>(); for (User sharedOwner : run.getSharedowners()) { diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java new file mode 100644 index 0000000000..d5dc363c56 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -0,0 +1,127 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import java.util.Locale; +import java.util.Map; + +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.Schoollevel; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.controllers.ControllerUtil; +import org.wise.portal.presentation.web.exception.InvalidNameException; +import org.wise.portal.presentation.web.response.ResponseEntityGenerator; +import org.wise.portal.service.authentication.DuplicateUsernameException; + + +@RestController +@RequestMapping("/api/teacher/register") +public class TeacherRegistrationAPIController extends TeacherAPIController { + + @PostMapping() + @Secured({ "ROLE_ANONYMOUS" }) + ResponseEntity> createTeacherAccount( + @RequestBody Map teacherFields, HttpServletRequest request) + throws DuplicateUsernameException, InvalidNameException { + if (ControllerUtil.isReCaptchaEnabled()) { + String token = teacherFields.get("token"); + if (!ControllerUtil.isReCaptchaResponseValid(token)) { + return ResponseEntityGenerator.createError("recaptchaResponseInvalid"); + } + } + TeacherUserDetails tud = new TeacherUserDetails(); + String firstName = teacherFields.get("firstName"); + String lastName = teacherFields.get("lastName"); + if (!isFirstNameAndLastNameValid(firstName, lastName)) { + String messageCode = this.getInvalidNameMessageCode(firstName, lastName); + throw new InvalidNameException(messageCode); + } + tud.setFirstname(firstName); + tud.setLastname(lastName); + String email = teacherFields.get("email"); + tud.setEmailAddress(email); + tud.setCity(teacherFields.get("city")); + tud.setState(teacherFields.get("state")); + tud.setCountry(teacherFields.get("country")); + String googleUserId = teacherFields.get("googleUserId"); + String microsoftUserId = teacherFields.get("microsoftUserId"); + if (isSet(googleUserId)) { + tud.setGoogleUserId(googleUserId); + tud.setPassword(RandomStringUtils.random(10, true, true)); + } else if (isSet(microsoftUserId)) { + tud.setMicrosoftUserId(microsoftUserId); + tud.setPassword(RandomStringUtils.random(10, true, true)); + } else { + String password = teacherFields.get("password"); + if (!passwordService.isValid(password)) { + return ResponseEntityGenerator.createError(passwordService.getErrors(password)); + } else { + tud.setPassword(password); + } + } + String displayName = firstName + " " + lastName; + tud.setDisplayname(displayName); + tud.setEmailValid(true); + tud.setSchoollevel(Schoollevel.valueOf(teacherFields.get("schoolLevel"))); + tud.setSchoolname(teacherFields.get("schoolName")); + tud.setHowDidYouHearAboutUs(teacherFields.get("howDidYouHearAboutUs")); + Locale locale = request.getLocale(); + tud.setLanguage(locale.getLanguage()); + User createdUser = this.userService.createUser(tud); + String username = createdUser.getUserDetails().getUsername(); + String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); + Boolean iSendEmailEnabled = Boolean.valueOf(sendEmailEnabledStr); + boolean socialAccount = this.isSet(googleUserId) || this.isSet(microsoftUserId); + if (iSendEmailEnabled) { + sendCreateTeacherAccountEmail(email, displayName, username, socialAccount, locale, request); + } + return createRegisterSuccessResponse(username); + } + + private void sendCreateTeacherAccountEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request) { + String fromEmail = appProperties.getProperty("portalemailaddress"); + String[] recipients = { email }; + String defaultSubject = messageSource.getMessage( + "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject", + null, Locale.US); + String subject = messageSource.getMessage( + "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject", + null, defaultSubject, locale); + String defaultBody = messageSource.getMessage( + "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", + new Object[] { username }, Locale.US); + String gettingStartedUrl = getGettingStartedUrl(request); + String message; + if (socialAccount) { + message = messageSource.getMessage( + "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername", + new Object[] { displayName, gettingStartedUrl }, defaultBody, locale); + } else { + message = messageSource.getMessage( + "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", + new Object[] { displayName, username, gettingStartedUrl }, defaultBody, locale); + } + try { + mailService.postMail(recipients, subject, message, fromEmail); + } catch (MessagingException e) { + e.printStackTrace(); + } + } + + private String getGettingStartedUrl(HttpServletRequest request) { + return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; + } + + private boolean isSet(String value) { + return value != null && !value.isEmpty(); + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java index d070760700..76902310e4 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java @@ -16,7 +16,6 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import org.easymock.EasyMockExtension; @@ -25,8 +24,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.test.util.ReflectionTestUtils; import org.wise.portal.dao.ObjectNotFoundException; import org.wise.portal.domain.PeriodNotFoundException; @@ -35,9 +32,7 @@ import org.wise.portal.domain.run.Run; import org.wise.portal.domain.user.User; import org.wise.portal.presentation.web.controllers.APIControllerTest; -import org.wise.portal.presentation.web.exception.InvalidNameException; import org.wise.portal.presentation.web.response.SimpleResponse; -import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; import org.wise.portal.service.password.impl.PasswordServiceImpl; import org.wise.portal.service.usertags.UserTagsService; @@ -375,33 +370,6 @@ public void createRun_ThreePeriods_CreateRun() throws Exception { verify(runService); } - @Test - public void createTeacherAccount_InvalidPassword_ReturnError() - throws DuplicateUsernameException, InvalidNameException { - HashMap teacherFields = createDefaultTeacherFields(); - teacherFields.put("password", PasswordServiceImpl.INVALID_PASSWORD_TOO_SHORT); - ResponseEntity> response = teacherAPIController - .createTeacherAccount(teacherFields, request); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); - assertEquals("invalidPassword", response.getBody().get("messageCode")); - } - - @Test - public void createTeacherAccount_WithGoogleUserId_CreateUser() - throws DuplicateUsernameException, InvalidNameException { - HashMap teacherFields = createDefaultTeacherFields(); - teacherFields.put("googleUserId", "123456789"); - expect(request.getLocale()).andReturn(Locale.US); - replay(request); - expect(userService.createUser(isA(TeacherUserDetails.class))).andReturn(teacher1); - replay(userService); - ResponseEntity> response = teacherAPIController - .createTeacherAccount(teacherFields, request); - assertEquals(TEACHER_USERNAME, response.getBody().get("username")); - verify(request); - verify(userService); - } - @Test public void editRunEndTime_WithNullEndTime_ChangeEndTime() throws ObjectNotFoundException { expect(userService.retrieveUserByUsername(teacherAuth.getName())).andReturn(teacher1); @@ -456,17 +424,6 @@ public void editRunIsLockedAfterEndDate_WithFalse_ChangeValue() throws ObjectNot verify(runService); } - private HashMap createDefaultTeacherFields() { - HashMap fields = new HashMap(); - fields.put("firstName", TEACHER_FIRSTNAME); - fields.put("lastName", TEACHER_LASTNAME); - fields.put("schoolLevel", "COLLEGE"); - fields.put("birthMonth", "1"); - fields.put("birthDay", "1"); - fields.put("gender", "MALE"); - return fields; - } - private void expectGetRunMapToBeCalled() { expect(projectService.getProjectPath(isA(Project.class))).andReturn("/1/project.json"); expect(projectService.getProjectSharedOwnersList(isA(Project.class))) diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java new file mode 100644 index 0000000000..032b815512 --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -0,0 +1,77 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.isA; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.easymock.EasyMockExtension; +import org.easymock.Mock; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.presentation.web.controllers.APIControllerTest; +import org.wise.portal.presentation.web.exception.InvalidNameException; +import org.wise.portal.service.authentication.DuplicateUsernameException; +import org.wise.portal.service.authentication.UserDetailsService; +import org.wise.portal.service.password.impl.PasswordServiceImpl; +import org.wise.portal.service.usertags.UserTagsService; + +@ExtendWith(EasyMockExtension.class) +public class TeacherRegistrationAPIControllerTest extends APIControllerTest { + + @TestSubject + private TeacherRegistrationAPIController teacherRegistrationAPIController = new TeacherRegistrationAPIController(); + + @Mock + private UserDetailsService userDetailsService; + + @Mock + private UserTagsService userTagsService; + + @Test + public void createTeacherAccount_InvalidPassword_ReturnError() + throws DuplicateUsernameException, InvalidNameException { + HashMap teacherFields = createDefaultTeacherFields(); + teacherFields.put("password", PasswordServiceImpl.INVALID_PASSWORD_TOO_SHORT); + ResponseEntity> response = teacherRegistrationAPIController + .createTeacherAccount(teacherFields, request); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("invalidPassword", response.getBody().get("messageCode")); + } + + @Test + public void createTeacherAccount_WithGoogleUserId_CreateUser() + throws DuplicateUsernameException, InvalidNameException { + HashMap teacherFields = createDefaultTeacherFields(); + teacherFields.put("googleUserId", "123456789"); + expect(request.getLocale()).andReturn(Locale.US); + replay(request); + expect(userService.createUser(isA(TeacherUserDetails.class))).andReturn(teacher1); + replay(userService); + ResponseEntity> response = teacherRegistrationAPIController + .createTeacherAccount(teacherFields, request); + assertEquals(TEACHER_USERNAME, response.getBody().get("username")); + verify(request); + verify(userService); + } + + private HashMap createDefaultTeacherFields() { + HashMap fields = new HashMap(); + fields.put("firstName", TEACHER_FIRSTNAME); + fields.put("lastName", TEACHER_LASTNAME); + fields.put("schoolLevel", "COLLEGE"); + fields.put("birthMonth", "1"); + fields.put("birthDay", "1"); + fields.put("gender", "MALE"); + return fields; + } +} From 907d6836eb5f95cb23e0fa88e878923b54eea9ef Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 09:58:48 -0700 Subject: [PATCH 08/42] Refactor teacher registration --- .../TeacherRegistrationAPIController.java | 185 +++++++++++------- 1 file changed, 115 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index d5dc363c56..3f3fe84582 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -18,110 +18,155 @@ import org.wise.portal.domain.user.User; import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.presentation.web.exception.InvalidNameException; +import org.wise.portal.presentation.web.exception.InvalidPasswordException; +import org.wise.portal.presentation.web.exception.RecaptchaVerificationException; import org.wise.portal.presentation.web.response.ResponseEntityGenerator; import org.wise.portal.service.authentication.DuplicateUsernameException; - @RestController @RequestMapping("/api/teacher/register") public class TeacherRegistrationAPIController extends TeacherAPIController { + private final String emailCodePrefix = + "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmail"; + private final String welcomeBodyCode = this.emailCodePrefix + "Body"; + private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "BodyNoUsername"; + private final String welcomeSubjectCode = this.emailCodePrefix + "Subject"; + @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) ResponseEntity> createTeacherAccount( @RequestBody Map teacherFields, HttpServletRequest request) throws DuplicateUsernameException, InvalidNameException { + try { + validateTeacherFields(teacherFields); + } catch (RecaptchaVerificationException e) { + return ResponseEntityGenerator.createError("recaptchaResponseInvalid"); + } catch (InvalidPasswordException e) { + return ResponseEntityGenerator.createError( + passwordService.getErrors(teacherFields.get("password"))); + } + Locale locale = request.getLocale(); + TeacherUserDetails tud = createTeacherUserDetails(teacherFields, locale); + User createdUser = this.userService.createUser(tud); + String username = createdUser.getUserDetails().getUsername(); + if (isSendEmailEnabled()) { + boolean socialAccount = isSet(tud.getGoogleUserId()) || isSet(tud.getMicrosoftUserId()); + sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, + socialAccount, locale, request); + } + return createRegisterSuccessResponse(username); + } + + private Boolean isSendEmailEnabled() { + String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); + return Boolean.valueOf(sendEmailEnabledStr); + } + + private void sendWelcomeTeacherEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, + HttpServletRequest request) { + String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); + String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); + this.sendEmail(email, subject, body); + } + + private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { + String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); + return messageSource.getMessage(code, args, defaultMessage, locale); + } + + private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, + Locale locale, HttpServletRequest request) { + String gettingStartedUrl = getGettingStartedUrl(request); + String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; + Object[] args = socialAccount + ? new Object[] { displayName, gettingStartedUrl } + : new Object[] { displayName, username, gettingStartedUrl }; + return getEmailMessage(this.welcomeBodyCode, code, args, locale); + } + + private String getGettingStartedUrl(HttpServletRequest request) { + return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; + } + + private void sendEmail(String email, String subject, String body) { + String fromEmail = appProperties.getProperty("portalemailaddress"); + String[] recipients = { email }; + try { + mailService.postMail(recipients, subject, body, fromEmail); + } catch (MessagingException e) { + e.printStackTrace(); + } + } + + private void validateTeacherFields(Map teacherFields) + throws RecaptchaVerificationException, InvalidNameException, InvalidPasswordException { + validateReCaptcha(teacherFields.get("token")); + validateFirstAndLastName(teacherFields.get("firstName"), teacherFields.get("lastName")); + validatePassword(teacherFields.get("password")); + } + + private void validateReCaptcha(String token) throws RecaptchaVerificationException { if (ControllerUtil.isReCaptchaEnabled()) { - String token = teacherFields.get("token"); if (!ControllerUtil.isReCaptchaResponseValid(token)) { - return ResponseEntityGenerator.createError("recaptchaResponseInvalid"); + throw new RecaptchaVerificationException("Invalid ReCaptcha Response"); } } - TeacherUserDetails tud = new TeacherUserDetails(); - String firstName = teacherFields.get("firstName"); - String lastName = teacherFields.get("lastName"); + } + + private void validateFirstAndLastName(String firstName, String lastName) + throws InvalidNameException { if (!isFirstNameAndLastNameValid(firstName, lastName)) { String messageCode = this.getInvalidNameMessageCode(firstName, lastName); throw new InvalidNameException(messageCode); } - tud.setFirstname(firstName); - tud.setLastname(lastName); - String email = teacherFields.get("email"); - tud.setEmailAddress(email); + } + + private void validatePassword(String password) throws InvalidPasswordException { + if (password != null && !passwordService.isValid(password)) { + throw new InvalidPasswordException(); + } + } + + private TeacherUserDetails createTeacherUserDetails(Map teacherFields, + Locale locale) { + TeacherUserDetails tud = new TeacherUserDetails(); + tud.setFirstname(teacherFields.get("firstName")); + tud.setLastname(teacherFields.get("lastName")); + tud.setEmailAddress(teacherFields.get("email")); tud.setCity(teacherFields.get("city")); tud.setState(teacherFields.get("state")); tud.setCountry(teacherFields.get("country")); - String googleUserId = teacherFields.get("googleUserId"); - String microsoftUserId = teacherFields.get("microsoftUserId"); - if (isSet(googleUserId)) { - tud.setGoogleUserId(googleUserId); - tud.setPassword(RandomStringUtils.random(10, true, true)); - } else if (isSet(microsoftUserId)) { - tud.setMicrosoftUserId(microsoftUserId); - tud.setPassword(RandomStringUtils.random(10, true, true)); - } else { - String password = teacherFields.get("password"); - if (!passwordService.isValid(password)) { - return ResponseEntityGenerator.createError(passwordService.getErrors(password)); - } else { - tud.setPassword(password); - } - } - String displayName = firstName + " " + lastName; - tud.setDisplayname(displayName); - tud.setEmailValid(true); + tud.setDisplayname(tud.getFirstname() + " " + tud.getLastname()); tud.setSchoollevel(Schoollevel.valueOf(teacherFields.get("schoolLevel"))); tud.setSchoolname(teacherFields.get("schoolName")); tud.setHowDidYouHearAboutUs(teacherFields.get("howDidYouHearAboutUs")); - Locale locale = request.getLocale(); tud.setLanguage(locale.getLanguage()); - User createdUser = this.userService.createUser(tud); - String username = createdUser.getUserDetails().getUsername(); - String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); - Boolean iSendEmailEnabled = Boolean.valueOf(sendEmailEnabledStr); - boolean socialAccount = this.isSet(googleUserId) || this.isSet(microsoftUserId); - if (iSendEmailEnabled) { - sendCreateTeacherAccountEmail(email, displayName, username, socialAccount, locale, request); - } - return createRegisterSuccessResponse(username); + setPassword(teacherFields, tud); + tud.setEmailValid(true); + return tud; } - private void sendCreateTeacherAccountEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, HttpServletRequest request) { - String fromEmail = appProperties.getProperty("portalemailaddress"); - String[] recipients = { email }; - String defaultSubject = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject", - null, Locale.US); - String subject = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject", - null, defaultSubject, locale); - String defaultBody = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", - new Object[] { username }, Locale.US); - String gettingStartedUrl = getGettingStartedUrl(request); - String message; - if (socialAccount) { - message = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername", - new Object[] { displayName, gettingStartedUrl }, defaultBody, locale); + private void setPassword(Map teacherFields, TeacherUserDetails tud) { + String googleUserId = teacherFields.get("googleUserId"); + String microsoftUserId = teacherFields.get("microsoftUserId"); + if (isSet(googleUserId)) { + tud.setGoogleUserId(googleUserId); + setRandomPassword(tud); + } else if (isSet(microsoftUserId)) { + tud.setMicrosoftUserId(microsoftUserId); + setRandomPassword(tud); } else { - message = messageSource.getMessage( - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody", - new Object[] { displayName, username, gettingStartedUrl }, defaultBody, locale); - } - try { - mailService.postMail(recipients, subject, message, fromEmail); - } catch (MessagingException e) { - e.printStackTrace(); + tud.setPassword(teacherFields.get("password")); } } - private String getGettingStartedUrl(HttpServletRequest request) { - return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; - } - private boolean isSet(String value) { return value != null && !value.isEmpty(); } + + private void setRandomPassword(TeacherUserDetails tud) { + tud.setPassword(RandomStringUtils.random(10, true, true)); + } } From ad53a48f759567e18e5ee223921c0df5c2ac93a2 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 09:58:55 -0700 Subject: [PATCH 09/42] Fix tests --- .../TeacherRegistrationAPIControllerTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 032b815512..87bb71b758 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -22,12 +22,13 @@ import org.wise.portal.presentation.web.exception.InvalidNameException; import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; +import org.wise.portal.service.password.PasswordService; import org.wise.portal.service.password.impl.PasswordServiceImpl; import org.wise.portal.service.usertags.UserTagsService; @ExtendWith(EasyMockExtension.class) public class TeacherRegistrationAPIControllerTest extends APIControllerTest { - + @TestSubject private TeacherRegistrationAPIController teacherRegistrationAPIController = new TeacherRegistrationAPIController(); @@ -37,11 +38,19 @@ public class TeacherRegistrationAPIControllerTest extends APIControllerTest { @Mock private UserTagsService userTagsService; + @Mock + private PasswordService passwordService; + @Test public void createTeacherAccount_InvalidPassword_ReturnError() throws DuplicateUsernameException, InvalidNameException { HashMap teacherFields = createDefaultTeacherFields(); teacherFields.put("password", PasswordServiceImpl.INVALID_PASSWORD_TOO_SHORT); + expect(passwordService.isValid(PasswordServiceImpl.INVALID_PASSWORD_TOO_SHORT)).andReturn(false); + Map errors = new HashMap<>(); + errors.put("messageCode", "invalidPassword"); + expect(passwordService.getErrors(PasswordServiceImpl.INVALID_PASSWORD_TOO_SHORT)).andReturn(errors); + replay(passwordService); ResponseEntity> response = teacherRegistrationAPIController .createTeacherAccount(teacherFields, request); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); @@ -62,7 +71,7 @@ public void createTeacherAccount_WithGoogleUserId_CreateUser() assertEquals(TEACHER_USERNAME, response.getBody().get("username")); verify(request); verify(userService); - } + } private HashMap createDefaultTeacherFields() { HashMap fields = new HashMap(); From 92b951b18d07502a0ea83d5406a9b02809a4ed07 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 10:03:28 -0700 Subject: [PATCH 10/42] Minor change --- .../teacher/TeacherRegistrationAPIController.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 3f3fe84582..cc18cd734e 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -51,18 +51,21 @@ ResponseEntity> createTeacherAccount( User createdUser = this.userService.createUser(tud); String username = createdUser.getUserDetails().getUsername(); if (isSendEmailEnabled()) { - boolean socialAccount = isSet(tud.getGoogleUserId()) || isSet(tud.getMicrosoftUserId()); sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, - socialAccount, locale, request); + isSocialAccount(tud), locale, request); } return createRegisterSuccessResponse(username); } - private Boolean isSendEmailEnabled() { + private boolean isSendEmailEnabled() { String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); return Boolean.valueOf(sendEmailEnabledStr); } + private boolean isSocialAccount(TeacherUserDetails tud) { + return isSet(tud.getGoogleUserId()) || isSet(tud.getMicrosoftUserId()); + } + private void sendWelcomeTeacherEmail(String email, String displayName, String username, boolean socialAccount, Locale locale, HttpServletRequest request) { From 7a8f57ae865144bd6d585958aab18f7a8bcd1c6f Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 11:38:25 -0700 Subject: [PATCH 11/42] Move contents of UserVerificationAPIController into more appropriate classes --- .../teacher/TeacherAPIController.java | 15 +++++ .../TeacherRegistrationAPIController.java | 22 +++++++ .../user/UserVerificationAPIController.java | 61 ------------------- 3 files changed, 37 insertions(+), 61 deletions(-) delete mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 1dd53d19ef..3fcfd083e7 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -327,4 +327,19 @@ HashMap editRunIsLockedAfterEndDate(Authentication authenticatio } return response; } + + @GetMapping("/is-verified") + public boolean isUserVerified(@RequestParam String username) { + User user = userService.retrieveTeacherByUsername(username); + return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts + } + + protected boolean isTeacher(User user) { + return user != null && !user.getRoles().contains("ROLE_STUDENT"); + } + + private boolean isTeacherVerified(User user) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + return tud.isVerified(); + } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index cc18cd734e..116881497a 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -172,4 +172,26 @@ private boolean isSet(String value) { private void setRandomPassword(TeacherUserDetails tud) { tud.setPassword(RandomStringUtils.random(10, true, true)); } + + @GetMapping("/verify") + public void openVerificationLink(@RequestParam String code, @RequestParam String username, + HttpServletResponse response) throws IOException { + boolean verified = this.verifyTeacherAccount(code); + response.sendRedirect("/login?verified=" + verified + "&username=" + username); + } + + private boolean verifyTeacherAccount(String verificationCode) { + User user = userService.retrieveTeacherByVerificationCode(verificationCode); + if (this.isTeacher(user)) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (!tud.isVerified()) { + tud.setVerified(true); + userService.updateUser(user); + return true; + } + } + return false; + } + + } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java deleted file mode 100644 index 207c3380eb..0000000000 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserVerificationAPIController.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.wise.portal.presentation.web.controllers.user; - -import org.springframework.web.bind.annotation.RestController; -import org.wise.portal.domain.authentication.impl.TeacherUserDetails; -import org.wise.portal.domain.user.User; -import org.wise.portal.service.user.UserService; - -import java.io.IOException; - -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; - - -@RestController -@RequestMapping("/api/user") -public class UserVerificationAPIController { - - private final UserService userService; - - public UserVerificationAPIController(UserService userService) { - this.userService = userService; - } - - @GetMapping("/verify") - public void openVerificationLink(@RequestParam String code, @RequestParam String username, - HttpServletResponse response) throws IOException { - boolean verified = this.verifyTeacherAccount(code); - response.sendRedirect("/login?verified=" + verified + "&username=" + username); - } - - private boolean verifyTeacherAccount(String verificationCode) { - User user = userService.retrieveTeacherByVerificationCode(verificationCode); - if (this.isTeacher(user)) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - if (!tud.isVerified()) { - tud.setVerified(true); - userService.updateUser(user); - return true; - } - } - return false; - } - - private boolean isTeacher(User user) { - return user != null && !user.getRoles().contains("ROLE_STUDENT"); - } - - @GetMapping("/is-verified") - public boolean isUserVerified(@RequestParam String username) { - User user = userService.retrieveTeacherByUsername(username); - return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts - } - - private boolean isTeacherVerified(User user) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - return tud.isVerified(); - } -} From 9d941f70776d51a6daa9c712f41db607e67d69e1 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 12:20:58 -0700 Subject: [PATCH 12/42] Welcome email only after verifying account --- .../teacher/TeacherAPIController.java | 1 + .../TeacherRegistrationAPIController.java | 81 +++++++++++++++---- src/main/resources/i18n/i18n.properties | 7 +- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 3fcfd083e7..02fe6992ce 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -329,6 +329,7 @@ HashMap editRunIsLockedAfterEndDate(Authentication authenticatio } @GetMapping("/is-verified") + @Secured({ "ROLE_ANONYMOUS" }) public boolean isUserVerified(@RequestParam String username) { User user = userService.retrieveTeacherByUsername(username); return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 116881497a..99ac519811 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -1,17 +1,22 @@ package org.wise.portal.presentation.web.controllers.teacher; +import java.io.IOException; import java.util.Locale; import java.util.Map; +import java.util.UUID; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.wise.portal.domain.authentication.Schoollevel; import org.wise.portal.domain.authentication.impl.TeacherUserDetails; @@ -28,10 +33,12 @@ public class TeacherRegistrationAPIController extends TeacherAPIController { private final String emailCodePrefix = - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmail"; - private final String welcomeBodyCode = this.emailCodePrefix + "Body"; - private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "BodyNoUsername"; - private final String welcomeSubjectCode = this.emailCodePrefix + "Subject"; + "presentation.web.controllers.teacher.registerTeacherController."; + private final String verifyBodyCode = this.emailCodePrefix + "verifyTeacherEmailBody"; + private final String verifySubjectCode = this.emailCodePrefix + "verifyTeacherEmailSubject"; + private final String welcomeBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBody"; + private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBodyNoUsername"; + private final String welcomeSubjectCode = this.emailCodePrefix + "welcomeTeacherEmailSubject"; @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) @@ -47,12 +54,17 @@ ResponseEntity> createTeacherAccount( passwordService.getErrors(teacherFields.get("password"))); } Locale locale = request.getLocale(); - TeacherUserDetails tud = createTeacherUserDetails(teacherFields, locale); + boolean isSocialAccount = isSocialAccount(teacherFields); + TeacherUserDetails tud = createTeacherUserDetails(teacherFields, isSocialAccount, locale); User createdUser = this.userService.createUser(tud); String username = createdUser.getUserDetails().getUsername(); if (isSendEmailEnabled()) { - sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, - isSocialAccount(tud), locale, request); + if (isSocialAccount) { + sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, + true, locale, request); + } else { + sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); + } } return createRegisterSuccessResponse(username); } @@ -62,8 +74,8 @@ private boolean isSendEmailEnabled() { return Boolean.valueOf(sendEmailEnabledStr); } - private boolean isSocialAccount(TeacherUserDetails tud) { - return isSet(tud.getGoogleUserId()) || isSet(tud.getMicrosoftUserId()); + private boolean isSocialAccount(Map teacherFields) { + return isSet(teacherFields.get("googleUserId")) || isSet(tud.getMicrosoftUserId()); } private void sendWelcomeTeacherEmail(String email, String displayName, String username, @@ -93,6 +105,20 @@ private String getGettingStartedUrl(HttpServletRequest request) { return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; } + private void sendVerifyTeacherEmail(String email, String verificationCode, + Locale locale, HttpServletRequest request) { + String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); + String verificationUrl = getVerificationUrl(verificationCode, request); + Object[] args = new Object[] { verificationUrl }; + String body = getEmailMessage(this.verifyBodyCode, this.verifyBodyCode, args, locale); + this.sendEmail(email, subject, body); + } + + private String getVerificationUrl(String verificationCode, HttpServletRequest request) { + return String.format("%s/api/teacher/register/verify?code=%s", + ControllerUtil.getPortalUrlString(request), verificationCode); + } + private void sendEmail(String email, String subject, String body) { String fromEmail = appProperties.getProperty("portalemailaddress"); String[] recipients = { email }; @@ -133,7 +159,7 @@ private void validatePassword(String password) throws InvalidPasswordException { } private TeacherUserDetails createTeacherUserDetails(Map teacherFields, - Locale locale) { + boolean isSocialAccount, Locale locale) { TeacherUserDetails tud = new TeacherUserDetails(); tud.setFirstname(teacherFields.get("firstName")); tud.setLastname(teacherFields.get("lastName")); @@ -148,6 +174,8 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF tud.setLanguage(locale.getLanguage()); setPassword(teacherFields, tud); tud.setEmailValid(true); + tud.setVerified(isSocialAccount); + tud.setVerificationCode(UUID.randomUUID().toString()); return tud; } @@ -174,24 +202,45 @@ private void setRandomPassword(TeacherUserDetails tud) { } @GetMapping("/verify") - public void openVerificationLink(@RequestParam String code, @RequestParam String username, - HttpServletResponse response) throws IOException { - boolean verified = this.verifyTeacherAccount(code); - response.sendRedirect("/login?verified=" + verified + "&username=" + username); + @Secured({ "ROLE_ANONYMOUS" }) + public void openVerificationLink(@RequestParam String code, HttpServletResponse response, + HttpServletRequest request) throws IOException { + boolean verified = this.verifyTeacherAccount(code, request); + User user = userService.retrieveTeacherByVerificationCode(code); + response.sendRedirect("/login?verified=" + verified + "&username=" + user.getUserDetails().getUsername()); } - private boolean verifyTeacherAccount(String verificationCode) { + private boolean verifyTeacherAccount(String verificationCode, HttpServletRequest request) { User user = userService.retrieveTeacherByVerificationCode(verificationCode); if (this.isTeacher(user)) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); if (!tud.isVerified()) { tud.setVerified(true); userService.updateUser(user); + sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), + false, request.getLocale(), request); return true; } } return false; } - + @PostMapping("send-verify-email") + @Secured({ "ROLE_ANONYMOUS" }) + ResponseEntity> sendVerificationEmail(@RequestParam String username, + HttpServletRequest request) { + User user = userService.retrieveTeacherByUsername(username); + if (this.isTeacher(user)) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (tud.isVerified()) { + return ResponseEntityGenerator.createError("Teacher already verified"); //TODO: error message codes point where? + } else { + sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), + request.getLocale(), request); + return createRegisterSuccessResponse(username); + } + } else { + return ResponseEntityGenerator.createError("Not a teacher"); //TODO: error message codes point where? + } + } } diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties index ec43dd3f67..270f3a530a 100644 --- a/src/main/resources/i18n/i18n.properties +++ b/src/main/resources/i18n/i18n.properties @@ -3754,9 +3754,14 @@ presentation.web.controllers.teacher.project.customized.ShareProjectController.s presentation.web.controllers.teacher.project.customized.ShareProjectController.shareProjectConfirmationEmailBody={0} shared a project with you on WISE:\n\nProject Name: {1}\nProject ID: {2}\nShared with username: {3}\nDate this project was shared: {4}\n\nGo to this URL to preview this project: {5}\n\nThanks,\nWISE Team presentation.web.controllers.teacher.project.customized.ShareProjectController.shareProjectConfirmationEmailBody.description=Message body of the email notifying user that someone shared a project with them. +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailSubject=Verify your WISE account +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailSubject.description=Subject line of the email including the link that teachers must be click to verify their account. +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailBody=Please click the following link to verify your email address and complete the registration process:\n{0}\n\nIf you did not make this request, you can ignore this email. +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailBody.description=Message body of the email including the link that teachers must be click to verify their account. + presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject=Welcome to WISE! presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject.description=Text "Welcome to WISE!" to welcome new teachers in a welcome email. -presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nPlease click the following link to verify your email address and sign in:\n{2}\n\nFor more information on how to get started, please visit our Getting Started page.\n{3}\n\nThank you for joining WISE,\nWISE Team +presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nFor more information on how to get started, please visit our Getting Started page.\n{2}\n\nThank you for joining WISE,\nWISE Team presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody.description=Body of the Welcome email message sent to new WISE teachers. presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername=Hi {0}!\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nFor more information on how to get started, please visit our Getting Started page.\n{1}\n\nThank you for joining WISE,\nWISE Team presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBodyNoUsername.description=Body of the Welcome email message sent to new WISE teachers that use Google ID to register. From 97e49d1ab0b12912887671df8b2b7202d522b303 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 12:33:30 -0700 Subject: [PATCH 13/42] Should keep trying to set verification code until unique constraint is satisfied --- .../authentication/impl/TeacherUserDetails.java | 2 +- .../teacher/TeacherRegistrationAPIController.java | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java index 52b53ff702..cd7babf46a 100644 --- a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java +++ b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java @@ -175,7 +175,7 @@ public class TeacherUserDetails extends PersistentUserDetails implements Mutable @Setter private boolean verified = false; - @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFICATION_CODE) + @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFICATION_CODE, unique = true) @Getter @Setter private String verificationCode; diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 99ac519811..b446307675 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -10,6 +10,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.GetMapping; @@ -175,7 +176,7 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF setPassword(teacherFields, tud); tud.setEmailValid(true); tud.setVerified(isSocialAccount); - tud.setVerificationCode(UUID.randomUUID().toString()); + setVerificationCode(tud); return tud; } @@ -192,6 +193,18 @@ private void setPassword(Map teacherFields, TeacherUserDetails t tud.setPassword(teacherFields.get("password")); } } + + private void setVerificationCode(TeacherUserDetails tud) { + boolean isCodeSet = false; + while (!isCodeSet) { + try { + tud.setVerificationCode(UUID.randomUUID().toString()); + isCodeSet = true; + } catch (DataIntegrityViolationException e) { + continue; + } + } + } private boolean isSet(String value) { return value != null && !value.isEmpty(); From 29095d9e73ec0d4e0ab9ad72a6fd95f4c6eb0a3a Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 17 Jul 2026 17:57:49 -0700 Subject: [PATCH 14/42] Minor bug --- .../controllers/teacher/TeacherRegistrationAPIController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index b446307675..f38344d031 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -76,7 +76,7 @@ private boolean isSendEmailEnabled() { } private boolean isSocialAccount(Map teacherFields) { - return isSet(teacherFields.get("googleUserId")) || isSet(tud.getMicrosoftUserId()); + return isSet(teacherFields.get("googleUserId")) || isSet(teacherFields.get("microsoftUserId")); } private void sendWelcomeTeacherEmail(String email, String displayName, String username, From d2b80422f6f38b290378af2b91e8dd5b0c225805 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Mon, 20 Jul 2026 21:48:40 -0700 Subject: [PATCH 15/42] Redirect to login with an error flag if verification code doesn't match any users in database --- .../TeacherRegistrationAPIController.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index f38344d031..7a95fa42fa 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -216,15 +216,24 @@ private void setRandomPassword(TeacherUserDetails tud) { @GetMapping("/verify") @Secured({ "ROLE_ANONYMOUS" }) - public void openVerificationLink(@RequestParam String code, HttpServletResponse response, - HttpServletRequest request) throws IOException { - boolean verified = this.verifyTeacherAccount(code, request); + public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, + HttpServletRequest request) throws IOException { User user = userService.retrieveTeacherByVerificationCode(code); - response.sendRedirect("/login?verified=" + verified + "&username=" + user.getUserDetails().getUsername()); + if (user == null) { + response.sendRedirect("/login?verified=error"); + } else { + boolean verified = this.verifyTeacherAccount(user, request); + String redirectLink = getRedirectLink(user, verified); + response.sendRedirect(redirectLink); + } + } + + private String getRedirectLink(User user, boolean verified) { + return String.format("/login?verified=%s&username=%s", + verified, user.getUserDetails().getUsername()); } - private boolean verifyTeacherAccount(String verificationCode, HttpServletRequest request) { - User user = userService.retrieveTeacherByVerificationCode(verificationCode); + private boolean verifyTeacherAccount(User user, HttpServletRequest request) { if (this.isTeacher(user)) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); if (!tud.isVerified()) { From 896a3bcca995364184b5c8abeccfa3fdb9b5e3d4 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Mon, 20 Jul 2026 23:19:47 -0700 Subject: [PATCH 16/42] Move email specific code to MailService --- .../dao/user/impl/HibernateUserDao.java | 8 +- .../TeacherRegistrationAPIController.java | 124 +++++------------- .../wise/portal/service/mail/IMailFacade.java | 10 ++ .../wise/portal/service/mail/MailService.java | 80 +++++++++++ 4 files changed, 129 insertions(+), 93 deletions(-) diff --git a/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java b/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java index e6d090b429..8a8f64f0de 100644 --- a/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java +++ b/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java @@ -151,10 +151,6 @@ public User retrieveTeacherByUsername(String username) { return retrieveTeacherByFieldValue("username", username); } - public User retrieveTeacherByVerificationCode(String verificationCode) { - return retrieveTeacherByFieldValue("verificationCode", verificationCode); - } - public List retrieveTeachersByDisplayName(String displayName) { return retrieveTeachersByFieldValue("displayname", displayName); } @@ -179,6 +175,10 @@ public List retrieveTeachersByEmail(String emailAddress) { return retrieveTeachersByFieldValue("emailAddress", emailAddress); } + public User retrieveTeacherByVerificationCode(String verificationCode) { + return retrieveTeacherByFieldValue("verificationCode", verificationCode); + } + private User retrieveTeacherByFieldValue(String field, String value) { List resultList = retrieveTeachersByFieldValue(field, value); return resultList.isEmpty() ? null : resultList.get(0); diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 7a95fa42fa..beef5d7092 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.UUID; -import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,14 +31,6 @@ @RestController @RequestMapping("/api/teacher/register") public class TeacherRegistrationAPIController extends TeacherAPIController { - - private final String emailCodePrefix = - "presentation.web.controllers.teacher.registerTeacherController."; - private final String verifyBodyCode = this.emailCodePrefix + "verifyTeacherEmailBody"; - private final String verifySubjectCode = this.emailCodePrefix + "verifyTeacherEmailSubject"; - private final String welcomeBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBody"; - private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBodyNoUsername"; - private final String welcomeSubjectCode = this.emailCodePrefix + "welcomeTeacherEmailSubject"; @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) @@ -59,77 +50,24 @@ ResponseEntity> createTeacherAccount( TeacherUserDetails tud = createTeacherUserDetails(teacherFields, isSocialAccount, locale); User createdUser = this.userService.createUser(tud); String username = createdUser.getUserDetails().getUsername(); - if (isSendEmailEnabled()) { - if (isSocialAccount) { - sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, - true, locale, request); - } else { - sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); - } - } + sendNewTeacherEmail(request, locale, isSocialAccount, tud, username); return createRegisterSuccessResponse(username); } - private boolean isSendEmailEnabled() { - String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); - return Boolean.valueOf(sendEmailEnabledStr); + private void sendNewTeacherEmail(HttpServletRequest request, Locale locale, boolean isSocialAccount, + TeacherUserDetails tud, String username) { + if (isSocialAccount) { + this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, + true, locale, request); + } else { + this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); + } } private boolean isSocialAccount(Map teacherFields) { return isSet(teacherFields.get("googleUserId")) || isSet(teacherFields.get("microsoftUserId")); } - private void sendWelcomeTeacherEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, - HttpServletRequest request) { - String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); - String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); - this.sendEmail(email, subject, body); - } - - private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { - String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); - return messageSource.getMessage(code, args, defaultMessage, locale); - } - - private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, - Locale locale, HttpServletRequest request) { - String gettingStartedUrl = getGettingStartedUrl(request); - String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; - Object[] args = socialAccount - ? new Object[] { displayName, gettingStartedUrl } - : new Object[] { displayName, username, gettingStartedUrl }; - return getEmailMessage(this.welcomeBodyCode, code, args, locale); - } - - private String getGettingStartedUrl(HttpServletRequest request) { - return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; - } - - private void sendVerifyTeacherEmail(String email, String verificationCode, - Locale locale, HttpServletRequest request) { - String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); - String verificationUrl = getVerificationUrl(verificationCode, request); - Object[] args = new Object[] { verificationUrl }; - String body = getEmailMessage(this.verifyBodyCode, this.verifyBodyCode, args, locale); - this.sendEmail(email, subject, body); - } - - private String getVerificationUrl(String verificationCode, HttpServletRequest request) { - return String.format("%s/api/teacher/register/verify?code=%s", - ControllerUtil.getPortalUrlString(request), verificationCode); - } - - private void sendEmail(String email, String subject, String body) { - String fromEmail = appProperties.getProperty("portalemailaddress"); - String[] recipients = { email }; - try { - mailService.postMail(recipients, subject, body, fromEmail); - } catch (MessagingException e) { - e.printStackTrace(); - } - } - private void validateTeacherFields(Map teacherFields) throws RecaptchaVerificationException, InvalidNameException, InvalidPasswordException { validateReCaptcha(teacherFields.get("token")); @@ -219,12 +157,24 @@ private void setRandomPassword(TeacherUserDetails tud) { public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, HttpServletRequest request) throws IOException { User user = userService.retrieveTeacherByVerificationCode(code); + String link; if (user == null) { - response.sendRedirect("/login?verified=error"); + link = "/login?verified=error"; + } else if (!isTeacher(user)) { + link = getRedirectLink(user, false); } else { - boolean verified = this.verifyTeacherAccount(user, request); - String redirectLink = getRedirectLink(user, verified); - response.sendRedirect(redirectLink); + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + boolean verified = verifyTeacherAccount(user, tud, request); + sendWelcomeEmailIfNecessary(tud, verified, request); + link = getRedirectLink(user, verified); + } + response.sendRedirect(link); + } + + private void sendWelcomeEmailIfNecessary(TeacherUserDetails tud, boolean verified, HttpServletRequest request) { + if (verified) { + this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), + false, request.getLocale(), request); } } @@ -233,18 +183,14 @@ private String getRedirectLink(User user, boolean verified) { verified, user.getUserDetails().getUsername()); } - private boolean verifyTeacherAccount(User user, HttpServletRequest request) { - if (this.isTeacher(user)) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - if (!tud.isVerified()) { - tud.setVerified(true); - userService.updateUser(user); - sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), - false, request.getLocale(), request); - return true; - } + private boolean verifyTeacherAccount(User user, TeacherUserDetails tud, HttpServletRequest request) { + if (!tud.isVerified()) { + tud.setVerified(true); + userService.updateUser(user); + return true; + } else { + return false; } - return false; } @PostMapping("send-verify-email") @@ -252,13 +198,13 @@ private boolean verifyTeacherAccount(User user, HttpServletRequest request) { ResponseEntity> sendVerificationEmail(@RequestParam String username, HttpServletRequest request) { User user = userService.retrieveTeacherByUsername(username); - if (this.isTeacher(user)) { + if (isTeacher(user)) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); if (tud.isVerified()) { return ResponseEntityGenerator.createError("Teacher already verified"); //TODO: error message codes point where? } else { - sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), - request.getLocale(), request); + this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), + request.getLocale(), request); return createRegisterSuccessResponse(username); } } else { diff --git a/src/main/java/org/wise/portal/service/mail/IMailFacade.java b/src/main/java/org/wise/portal/service/mail/IMailFacade.java index 02af361d9b..8036ddd6ef 100644 --- a/src/main/java/org/wise/portal/service/mail/IMailFacade.java +++ b/src/main/java/org/wise/portal/service/mail/IMailFacade.java @@ -23,7 +23,12 @@ */ package org.wise.portal.service.mail; +import java.util.Locale; + import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; /** * @author Anthony Perritano @@ -46,4 +51,9 @@ void postMail(String recipients[], String subject, String message , String from) void postMail(String recipients[], String subject, String message , String from, String[] cc) throws MessagingException; + void sendWelcomeTeacherEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request); + + void sendVerifyTeacherEmail(String email, String verificationCode, Locale locale, + HttpServletRequest request); } diff --git a/src/main/java/org/wise/portal/service/mail/MailService.java b/src/main/java/org/wise/portal/service/mail/MailService.java index 9a9c764cfb..b6bda64f05 100644 --- a/src/main/java/org/wise/portal/service/mail/MailService.java +++ b/src/main/java/org/wise/portal/service/mail/MailService.java @@ -23,13 +23,20 @@ */ package org.wise.portal.service.mail; +import java.util.Locale; + import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.core.env.Environment; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.presentation.web.controllers.ControllerUtil; /** * Compose and send email using the JavaMail Framework @@ -38,9 +45,23 @@ @Service public class MailService implements IMailFacade { + private final String emailCodePrefix = + "presentation.web.controllers.teacher.registerTeacherController."; + private final String verifyBodyCode = this.emailCodePrefix + "verifyTeacherEmailBody"; + private final String verifySubjectCode = this.emailCodePrefix + "verifyTeacherEmailSubject"; + private final String welcomeBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBody"; + private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBodyNoUsername"; + private final String welcomeSubjectCode = this.emailCodePrefix + "welcomeTeacherEmailSubject"; + + @Autowired + protected Environment appProperties; + @Autowired private JavaMailSender javaMailSender; + @Autowired + private MessageSource messageSource; + public void postMail(String[] recipients, String subject, String message, String from) throws MessagingException { postMail(recipients, subject, message, from, null); @@ -65,4 +86,63 @@ public void postMail(String[] recipients, String subject, String message, } } } + + public void sendWelcomeTeacherEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request) { + if (isSendEmailEnabled()) { + String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); + String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); + this.sendEmail(email, subject, body); + } + } + + public void sendVerifyTeacherEmail(String email, String verificationCode, + Locale locale, HttpServletRequest request) { + if (isSendEmailEnabled()) { + String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); + String verificationUrl = getVerificationUrl(verificationCode, request); + Object[] args = new Object[] { verificationUrl }; + String body = getEmailMessage(this.verifyBodyCode, this.verifyBodyCode, args, locale); + this.sendEmail(email, subject, body); + } + } + + private boolean isSendEmailEnabled() { + String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); + return Boolean.valueOf(sendEmailEnabledStr); + } + + private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { + String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); + return messageSource.getMessage(code, args, defaultMessage, locale); + } + + private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, + Locale locale, HttpServletRequest request) { + String gettingStartedUrl = getGettingStartedUrl(request); + String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; + Object[] args = socialAccount + ? new Object[] { displayName, gettingStartedUrl } + : new Object[] { displayName, username, gettingStartedUrl }; + return getEmailMessage(this.welcomeBodyCode, code, args, locale); + } + + private String getGettingStartedUrl(HttpServletRequest request) { + return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; + } + + private String getVerificationUrl(String verificationCode, HttpServletRequest request) { + return String.format("%s/api/teacher/register/verify?code=%s", + ControllerUtil.getPortalUrlString(request), verificationCode); + } + + private void sendEmail(String email, String subject, String body) { + String fromEmail = appProperties.getProperty("portalemailaddress"); + String[] recipients = { email }; + try { + this.postMail(recipients, subject, body, fromEmail); + } catch (MessagingException e) { + e.printStackTrace(); + } + } } From dad7c6e4a0a0bf52785a870bc566171c3603e229 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 21 Jul 2026 10:48:42 -0700 Subject: [PATCH 17/42] Added tests (some untested) --- .../teacher/TeacherAPIController.java | 2 +- .../TeacherRegistrationAPIController.java | 4 +- .../dao/user/impl/HibernateUserDaoTest.java | 35 +++++- .../web/controllers/APIControllerTest.java | 13 ++- .../teacher/TeacherAPIControllerTest.java | 40 +++++++ .../TeacherRegistrationAPIControllerTest.java | 102 ++++++++++++++++++ 6 files changed, 184 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 02fe6992ce..a9355122f5 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -330,7 +330,7 @@ HashMap editRunIsLockedAfterEndDate(Authentication authenticatio @GetMapping("/is-verified") @Secured({ "ROLE_ANONYMOUS" }) - public boolean isUserVerified(@RequestParam String username) { + public boolean isVerifiedTeacherOrNonTeacher(@RequestParam String username) { User user = userService.retrieveTeacherByUsername(username); return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index beef5d7092..8b46edce3c 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -201,14 +201,14 @@ ResponseEntity> sendVerificationEmail(@RequestParam String u if (isTeacher(user)) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); if (tud.isVerified()) { - return ResponseEntityGenerator.createError("Teacher already verified"); //TODO: error message codes point where? + return ResponseEntityGenerator.createError("Teacher already verified"); } else { this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), request.getLocale(), request); return createRegisterSuccessResponse(username); } } else { - return ResponseEntityGenerator.createError("Not a teacher"); //TODO: error message codes point where? + return ResponseEntityGenerator.createError("Not a teacher"); } } } diff --git a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java index 641be7c6f0..d66d40e16a 100644 --- a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java +++ b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java @@ -32,8 +32,10 @@ import org.springframework.security.core.userdetails.UserDetails; import org.wise.portal.domain.authentication.Gender; import org.wise.portal.domain.authentication.Schoollevel; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; import org.wise.portal.domain.user.User; import org.wise.portal.junit.AbstractTransactionalDbTests; +import org.wise.portal.service.authentication.DuplicateUsernameException; /** * @author Geoffrey Kwan @@ -48,17 +50,30 @@ public class HibernateUserDaoTest extends AbstractTransactionalDbTests { @BeforeEach public void setUp() throws Exception { - teacher1 = createTeacherUser("Mrs", "Puff", "MrsPuff", "Mrs. Puff", "boat", "Bikini Bottom", + teacher1 = createVerificationTeacherUser("Mrs", "Puff", "MrsPuff", "Mrs. Puff", "boat", "Bikini Bottom", "Water State", "Pacific Ocean", "mrspuff@bikinibottom.com", "Boating School", - Schoollevel.COLLEGE, "1234567890"); - teacher2 = createTeacherUser("Mr", "Krabs", "MrKrabs", "Mr. Krabs", "restaurant", + Schoollevel.COLLEGE, "1234567890", "abcd1234", true); + teacher2 = createVerificationTeacherUser("Mr", "Krabs", "MrKrabs", "Mr. Krabs", "restaurant", "Bikini Bottom", "Water State", "Pacific Ocean", "mrkrabs@bikinibottom.com", "Krusty Krab", - Schoollevel.HIGH_SCHOOL, "abcdefghij"); + Schoollevel.HIGH_SCHOOL, "abcdefghij", "1234abcd", false); student1 = createStudentUser("Spongebob", "Squarepants", "SpongebobS0101", "burger", 1, 1, Gender.MALE); student2 = createStudentUser("Patrick", "Star", "PatrickS0101", "rock", 1, 1, Gender.MALE); } + public User createVerificationTeacherUser(String firstName, String lastName, String username, + String displayName, String password, String city, String state, String country, String email, + String schoolName, Schoollevel schoolLevel, String googleUserId, String verificationCode, + boolean isVerified) + throws DuplicateUsernameException { + User teacher = createTeacherUser(firstName, lastName, username, displayName, password, city, + state, country, email, schoolName, schoolLevel, googleUserId); + TeacherUserDetails tud = (TeacherUserDetails) teacher.getUserDetails(); + tud.setVerificationCode(verificationCode); + tud.setVerified(isVerified); + return teacher; + } + @Test public void retrieveByUserDetails_ShouldReturnTheUser() { UserDetails userDetails = teacher1.getUserDetails(); @@ -244,6 +259,18 @@ public void retrieveTeachersByEmail_WithExistingEmail_ShouldSucceed() { assertEquals(1, users.size()); } + @Test + public void retrieveTeacherByVerificationCode_WithNonExistingCode_ShouldNotReturnAnyUser() { + User user = userDao.retrieveTeacherByVerificationCode("wxyz6789"); + assertNull(user); + } + + @Test + public void retrieveTeacherByVerificationCode_WithExistingCode_ShouldSucceed() { + User user = userDao.retrieveTeacherByVerificationCode("abcd1234"); + assertEquals(teacher1.getId(), user.getId()); + } + @Test public void retrieveTeachersBySchoolLevel_WithNotUsedSchoolLevel_ShouldNotReturnAnyUser() { List users = userDao diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java index 4a9c0cf5f0..0780308806 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java @@ -136,7 +136,7 @@ public void setUp() { createWorkgroups(); } - private void createStudents() { + protected void createStudents() { student1UserDetails = createStudentUserDetails(STUDENT_FIRSTNAME, STUDENT_LASTNAME, STUDENT_USERNAME, Gender.MALE, 5, STUDENT1_GOOGLE_ID, STUDENT1_ACCOUNT_ANSWER); student1 = createStudent(student1Id, student1UserDetails); @@ -147,13 +147,13 @@ private void createStudents() { studentAuth2 = createAuthentication(student2UserDetails); } - private void createTeachers() { + protected void createTeachers() { teacher1UserDetails = createTeacherUserDetails(TEACHER_FIRSTNAME, TEACHER_LASTNAME, - TEACHER_USERNAME, Schoollevel.COLLEGE, 5); + TEACHER_USERNAME, Schoollevel.COLLEGE, 5, true, "abcd1234"); teacher1 = createTeacher(teacher1Id, teacher1UserDetails); teacherAuth = createAuthentication(teacher1UserDetails); teacher2UserDetails = createTeacherUserDetails(TEACHER2_FIRSTNAME, TEACHER2_LASTNAME, - TEACHER2_USERNAME, Schoollevel.COLLEGE, 5); + TEACHER2_USERNAME, Schoollevel.COLLEGE, 5, false, "efgh5678"); teacher2 = createTeacher(teacher2Id, teacher2UserDetails); teacherAuth2 = createAuthentication(teacher2UserDetails); } @@ -262,13 +262,16 @@ protected User createTeacher(Long id, TeacherUserDetails teacherUserDetails) { } protected TeacherUserDetails createTeacherUserDetails(String firstName, String lastName, - String username, Schoollevel schoolLevel, Integer numberOfLogins) { + String username, Schoollevel schoolLevel, Integer numberOfLogins, boolean isVerified, + String verificationCode) { TeacherUserDetails teacherUserDetails = new TeacherUserDetails(); teacherUserDetails.setFirstname(firstName); teacherUserDetails.setLastname(lastName); teacherUserDetails.setUsername(username); teacherUserDetails.setSchoollevel(schoolLevel); teacherUserDetails.setNumberOfLogins(numberOfLogins); + teacherUserDetails.setVerified(isVerified); + teacherUserDetails.setVerificationCode(verificationCode); PersistentGrantedAuthority teacherAuthority = new PersistentGrantedAuthority(); teacherAuthority.setAuthority(UserDetailsService.TEACHER_ROLE); teacherUserDetails.setAuthorities(new GrantedAuthority[] { teacherAuthority }); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java index 76902310e4..dfb5a8b0a7 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java @@ -6,7 +6,9 @@ import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; @@ -433,4 +435,42 @@ private void expectGetRunMapToBeCalled() { expect(projectService.getLicensePath(isA(Project.class))) .andReturn("http://localhost:8080/curriculum/1/license.txt"); } + + @Test + public void isVerifiedTeacherOrNonTeacher_UserIsAStudent_ReturnTrue() { + expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); + replay(userService); + boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(STUDENT_USERNAME); + assertTrue(isVerifiedTeacher); + verify(userService); + } + + @Test + public void isVerifiedTeacherOrNonTeacher_UserDoesNotExist_ReturnTrue() { + expect(userService.retrieveTeacherByUsername("")).andReturn(null); + replay(userService); + boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(""); + assertTrue(isVerifiedTeacher); + verify(userService); + } + + @Test + public void isVerifiedTeacherOrNonTeacher_TeacherIsVerified_ReturnTrue() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(TEACHER_USERNAME); + assertTrue(isVerifiedTeacher); + verify(userService); + } + + @Test + public void isVerifiedTeacherOrNonTeacher_TeacherIsUnverified_ReturnFalse() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); + replay(userService); + boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(TEACHER2_USERNAME); + assertFalse(isVerifiedTeacher); + verify(userService); + } } diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 87bb71b758..9e94e56af3 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -1,20 +1,25 @@ package org.wise.portal.presentation.web.controllers.teacher; import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.isA; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.easymock.TestSubject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.wise.portal.domain.authentication.impl.TeacherUserDetails; @@ -22,6 +27,7 @@ import org.wise.portal.presentation.web.exception.InvalidNameException; import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; +import org.wise.portal.service.mail.MailService; import org.wise.portal.service.password.PasswordService; import org.wise.portal.service.password.impl.PasswordServiceImpl; import org.wise.portal.service.usertags.UserTagsService; @@ -32,6 +38,15 @@ public class TeacherRegistrationAPIControllerTest extends APIControllerTest { @TestSubject private TeacherRegistrationAPIController teacherRegistrationAPIController = new TeacherRegistrationAPIController(); + @Mock + private HttpServletResponse response; + + @Mock + private MailService mailService; + + @Mock + private MessageSource messageSource; + @Mock private UserDetailsService userDetailsService; @@ -66,11 +81,16 @@ public void createTeacherAccount_WithGoogleUserId_CreateUser() replay(request); expect(userService.createUser(isA(TeacherUserDetails.class))).andReturn(teacher1); replay(userService); + mailService.sendWelcomeTeacherEmail("", TEACHER_FIRSTNAME + " " + TEACHER_LASTNAME, TEACHER_USERNAME, + true, Locale.US, request); + expectLastCall(); + replay(mailService); ResponseEntity> response = teacherRegistrationAPIController .createTeacherAccount(teacherFields, request); assertEquals(TEACHER_USERNAME, response.getBody().get("username")); verify(request); verify(userService); + verify(mailService); } private HashMap createDefaultTeacherFields() { @@ -83,4 +103,86 @@ private HashMap createDefaultTeacherFields() { fields.put("gender", "MALE"); return fields; } + + @Test + public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue() throws IOException { + createTeachers(); + expect(userService.retrieveTeacherByVerificationCode("efgh5678")).andReturn(teacher2); + userService.updateUser(teacher2); + expectLastCall(); + replay(userService); + TeacherUserDetails tud = (TeacherUserDetails) teacher2.getUserDetails(); + mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); + expectLastCall(); + replay(mailService); + response.sendRedirect("/login?verified=true&username=" + tud.getUsername()); + expectLastCall(); + replay(response); + teacherRegistrationAPIController.verifyTeacherAndRedirect("efgh5678", response, request); + verify(userService); + verify(mailService); + verify(response); + } + + @Test + public void verifyTeacherAndRedirect_TeacherAlreadyVerified_RedirectsWithVerifiedFalse() throws IOException { + createTeachers(); + expect(userService.retrieveTeacherByVerificationCode("abcd1234")).andReturn(teacher1); + replay(userService); + response.sendRedirect("/login?verified=false&username=" + teacher1.getUserDetails().getUsername()); + expectLastCall(); + replay(response); + teacherRegistrationAPIController.verifyTeacherAndRedirect("abcd1234", response, request); + verify(userService); + verify(response); + } + + @Test + public void verifyTeacherAndRedirect_InvalidVerificationCode_RedirectsWithVerificationError() throws IOException { + expect(userService.retrieveTeacherByVerificationCode("")).andReturn(null); + replay(userService); + response.sendRedirect("/login?verified=error"); + expectLastCall(); + replay(response); + teacherRegistrationAPIController.verifyTeacherAndRedirect("", response, request); + verify(userService); + verify(response); + } + + @Test + public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); + replay(userService); + mailService.sendVerifyTeacherEmail("", "efgh5678", null, request); + expectLastCall(); + replay(mailService); + ResponseEntity> response = + teacherRegistrationAPIController.sendVerificationEmail(TEACHER2_USERNAME, request); + assertEquals(TEACHER2_USERNAME, response.getBody().get("username")); + verify(userService); + verify(mailService); + + } + + @Test + public void sendVerificationEmail_UserIsVerifiedTeacher_ReturnError() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + ResponseEntity> response = + teacherRegistrationAPIController.sendVerificationEmail(TEACHER_USERNAME, request); + assertEquals("Teacher already verified", response.getBody().get("messageCode")); + verify(userService); + } + + @Test + public void sendVerificationEmail_UserIsNotTeacher_ReturnError() { + expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); + replay(userService); + ResponseEntity> response = + teacherRegistrationAPIController.sendVerificationEmail(STUDENT_USERNAME, request); + assertEquals("Not a teacher", response.getBody().get("messageCode")); + verify(userService); + } } From a499a64b66e80da103178a6629087159d6f29c75 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 21 Jul 2026 12:50:14 -0700 Subject: [PATCH 18/42] Separate sending welcome email from getting login link to make steps of method more clear --- .../teacher/TeacherRegistrationAPIController.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 8b46edce3c..655e8bb6d1 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -157,6 +157,12 @@ private void setRandomPassword(TeacherUserDetails tud) { public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, HttpServletRequest request) throws IOException { User user = userService.retrieveTeacherByVerificationCode(code); + String link = verifyTeacherIfNecessaryAndGetLoginLink(request, user); + sendWelcomeEmailIfNecessary(user, link, request); + response.sendRedirect(link); + } + + private String verifyTeacherIfNecessaryAndGetLoginLink(HttpServletRequest request, User user) { String link; if (user == null) { link = "/login?verified=error"; @@ -165,14 +171,14 @@ public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletRespo } else { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); boolean verified = verifyTeacherAccount(user, tud, request); - sendWelcomeEmailIfNecessary(tud, verified, request); link = getRedirectLink(user, verified); } - response.sendRedirect(link); + return link; } - private void sendWelcomeEmailIfNecessary(TeacherUserDetails tud, boolean verified, HttpServletRequest request) { - if (verified) { + private void sendWelcomeEmailIfNecessary(User user, String link, HttpServletRequest request) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (link.contains("verified=true")) { this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, request.getLocale(), request); } From 6fc8b46270c920d63d69443876c469546cc9e793 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 21 Jul 2026 13:05:53 -0700 Subject: [PATCH 19/42] Can't get user details for null user bug --- .../controllers/teacher/TeacherRegistrationAPIController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 655e8bb6d1..9537dc4e27 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -177,8 +177,8 @@ private String verifyTeacherIfNecessaryAndGetLoginLink(HttpServletRequest reques } private void sendWelcomeEmailIfNecessary(User user, String link, HttpServletRequest request) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); if (link.contains("verified=true")) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, request.getLocale(), request); } From 0845082ee6550a56236d0bea4190a2c373aed4a2 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Tue, 21 Jul 2026 13:10:13 -0700 Subject: [PATCH 20/42] Changed old tests that are failing --- .../dao/authentication/impl/HibernateUserDetailsDaoTest.java | 2 +- .../org/wise/portal/dao/user/impl/HibernateUserDaoTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java b/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java index 796f3c981e..48099afcf2 100644 --- a/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java +++ b/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java @@ -77,7 +77,7 @@ public void retrieveByName_WithExistingStudentName_ShouldReturnUser() { @Test public void retrieveAllTeacherUsernames_WhenThereAreTeachers_ShouldReturnTeacherUsernames() { List usernames = userDetailsDao.retrieveAllTeacherUsernames(); - assertEquals(4, usernames.size()); + assertEquals(2, usernames.size()); } @Test diff --git a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java index d66d40e16a..92115478d9 100644 --- a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java +++ b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java @@ -136,7 +136,7 @@ public void retrieveDisabledUsers_WhenThereIsOneDisabledUser_ShouldReturnOneUser @Test public void retrieveAllTeachers_ShouldReturnAllTeachers() { List users = userDao.retrieveAllTeachers(); - assertEquals(4, users.size()); + assertEquals(2, users.size()); } @Test @@ -281,7 +281,7 @@ public void retrieveTeachersBySchoolLevel_WithNotUsedSchoolLevel_ShouldNotReturn @Test public void retrieveTeachersBySchoolLevel_WithExistingSchoolLevel_ShouldSucceed() { List users = userDao.retrieveTeachersBySchoolLevel(Schoollevel.COLLEGE.toString()); - assertEquals(3, users.size()); + assertEquals(1, users.size()); } @Test From 19bf231833aba3f3bc23ff8b88b39405a8b3553e Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sun, 26 Jul 2026 11:04:28 -0700 Subject: [PATCH 21/42] Set teachers as verified if sending mail is not enabled --- .../controllers/teacher/TeacherRegistrationAPIController.java | 2 +- src/main/java/org/wise/portal/service/mail/IMailFacade.java | 2 ++ src/main/java/org/wise/portal/service/mail/MailService.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 9537dc4e27..56959e5413 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -113,7 +113,7 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF tud.setLanguage(locale.getLanguage()); setPassword(teacherFields, tud); tud.setEmailValid(true); - tud.setVerified(isSocialAccount); + tud.setVerified(isSocialAccount || !mailService.isSendEmailEnabled()); setVerificationCode(tud); return tud; } diff --git a/src/main/java/org/wise/portal/service/mail/IMailFacade.java b/src/main/java/org/wise/portal/service/mail/IMailFacade.java index 8036ddd6ef..239a99f384 100644 --- a/src/main/java/org/wise/portal/service/mail/IMailFacade.java +++ b/src/main/java/org/wise/portal/service/mail/IMailFacade.java @@ -56,4 +56,6 @@ void sendWelcomeTeacherEmail(String email, String displayName, String username, void sendVerifyTeacherEmail(String email, String verificationCode, Locale locale, HttpServletRequest request); + + boolean isSendEmailEnabled(); } diff --git a/src/main/java/org/wise/portal/service/mail/MailService.java b/src/main/java/org/wise/portal/service/mail/MailService.java index b6bda64f05..dcef12e7b0 100644 --- a/src/main/java/org/wise/portal/service/mail/MailService.java +++ b/src/main/java/org/wise/portal/service/mail/MailService.java @@ -107,7 +107,7 @@ public void sendVerifyTeacherEmail(String email, String verificationCode, } } - private boolean isSendEmailEnabled() { + public boolean isSendEmailEnabled() { String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); return Boolean.valueOf(sendEmailEnabledStr); } From 97558238976968382e47e7924d6ca5ba2f51df47 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sun, 26 Jul 2026 11:07:40 -0700 Subject: [PATCH 22/42] Added sql changes to database migrations --- .../resources/db/migration/V2__teacher_email_verification.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/main/resources/db/migration/V2__teacher_email_verification.sql diff --git a/src/main/resources/db/migration/V2__teacher_email_verification.sql b/src/main/resources/db/migration/V2__teacher_email_verification.sql new file mode 100644 index 0000000000..d3854cb5f8 --- /dev/null +++ b/src/main/resources/db/migration/V2__teacher_email_verification.sql @@ -0,0 +1,3 @@ +ALTER TABLE teacher_user_details +ADD COLUMN isVerified BIT(1) NOT NULL DEFAULT TRUE, +ADD COLUMN verificationCode VARCHAR(255) NULL; From efdae2c847b1bfc4dd67bd557a3361af289309f9 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 1 Aug 2026 19:59:46 -0700 Subject: [PATCH 23/42] Moved isVerified to separate controller --- .../teacher/TeacherAPIController.java | 13 ----- .../TeacherIsVerifiedAPIController.java | 26 +++++++++ .../teacher/TeacherAPIControllerTest.java | 40 ------------- .../TeacherIsVerifiedAPIControllerTest.java | 57 +++++++++++++++++++ 4 files changed, 83 insertions(+), 53 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index a9355122f5..912dfc36a3 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -6,7 +6,6 @@ import java.util.Locale; import java.util.Set; import java.util.TreeSet; -import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -328,19 +327,7 @@ HashMap editRunIsLockedAfterEndDate(Authentication authenticatio return response; } - @GetMapping("/is-verified") - @Secured({ "ROLE_ANONYMOUS" }) - public boolean isVerifiedTeacherOrNonTeacher(@RequestParam String username) { - User user = userService.retrieveTeacherByUsername(username); - return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts - } - protected boolean isTeacher(User user) { return user != null && !user.getRoles().contains("ROLE_STUDENT"); } - - private boolean isTeacherVerified(User user) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - return tud.isVerified(); - } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java new file mode 100644 index 0000000000..538b55914b --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java @@ -0,0 +1,26 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; + +@RestController +@RequestMapping("/api/teacher/is-verified") +public class TeacherIsVerifiedAPIController extends TeacherAPIController { + + @GetMapping() + @Secured({ "ROLE_ANONYMOUS" }) + public boolean isVerifiedTeacherOrNonTeacher(@RequestParam String username) { + User user = userService.retrieveTeacherByUsername(username); + return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts + } + + private boolean isTeacherVerified(User user) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + return tud.isVerified(); + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java index dfb5a8b0a7..76902310e4 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIControllerTest.java @@ -6,9 +6,7 @@ import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; @@ -435,42 +433,4 @@ private void expectGetRunMapToBeCalled() { expect(projectService.getLicensePath(isA(Project.class))) .andReturn("http://localhost:8080/curriculum/1/license.txt"); } - - @Test - public void isVerifiedTeacherOrNonTeacher_UserIsAStudent_ReturnTrue() { - expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); - replay(userService); - boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(STUDENT_USERNAME); - assertTrue(isVerifiedTeacher); - verify(userService); - } - - @Test - public void isVerifiedTeacherOrNonTeacher_UserDoesNotExist_ReturnTrue() { - expect(userService.retrieveTeacherByUsername("")).andReturn(null); - replay(userService); - boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(""); - assertTrue(isVerifiedTeacher); - verify(userService); - } - - @Test - public void isVerifiedTeacherOrNonTeacher_TeacherIsVerified_ReturnTrue() { - this.createTeachers(); - expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); - replay(userService); - boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(TEACHER_USERNAME); - assertTrue(isVerifiedTeacher); - verify(userService); - } - - @Test - public void isVerifiedTeacherOrNonTeacher_TeacherIsUnverified_ReturnFalse() { - this.createTeachers(); - expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); - replay(userService); - boolean isVerifiedTeacher = teacherAPIController.isVerifiedTeacherOrNonTeacher(TEACHER2_USERNAME); - assertFalse(isVerifiedTeacher); - verify(userService); - } } diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java new file mode 100644 index 0000000000..bd9df025fc --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java @@ -0,0 +1,57 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.easymock.EasyMockExtension; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.wise.portal.presentation.web.controllers.APIControllerTest; + +@ExtendWith(EasyMockExtension.class) +public class TeacherIsVerifiedAPIControllerTest extends APIControllerTest { + @TestSubject + private TeacherIsVerifiedAPIController teacherIsVerifiedAPIController = new TeacherIsVerifiedAPIController(); + + @Test + public void isVerifiedTeacherOrNonTeacher_UserIsAStudent_ReturnTrue() { + expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); + replay(userService); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(STUDENT_USERNAME); + assertTrue(isVerifiedTeacher); + verify(userService); + } + + @Test + public void isVerifiedTeacherOrNonTeacher_UserDoesNotExist_ReturnTrue() { + expect(userService.retrieveTeacherByUsername("")).andReturn(null); + replay(userService); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(""); + assertTrue(isVerifiedTeacher); + verify(userService); + } + + @Test + public void isVerifiedTeacherOrNonTeacher_TeacherIsVerified_ReturnTrue() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(TEACHER_USERNAME); + assertTrue(isVerifiedTeacher); + verify(userService); + } + + @Test + public void isVerifiedTeacherOrNonTeacher_TeacherIsUnverified_ReturnFalse() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); + replay(userService); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(TEACHER2_USERNAME); + assertFalse(isVerifiedTeacher); + verify(userService); + } +} From fb967d3ec475ab2a970c31dcf47e80baa4a1ac2e Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 1 Aug 2026 20:14:29 -0700 Subject: [PATCH 24/42] Moved verifyTeacher to separate controller --- .../web/TeacherVerificationAPIController.java | 67 ++++++++++++++++ .../TeacherRegistrationAPIController.java | 50 ------------ .../TeacherRegistrationAPIControllerTest.java | 46 ----------- .../TeacherVerificationAPIControllerTest.java | 78 +++++++++++++++++++ 4 files changed, 145 insertions(+), 96 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java new file mode 100644 index 0000000000..e2a4b8d785 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java @@ -0,0 +1,67 @@ +package org.wise.portal.presentation.web; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.controllers.teacher.TeacherAPIController; + +@RestController +@RequestMapping("/api/teacher/verify") +public class TeacherVerificationAPIController extends TeacherAPIController { + + @GetMapping() + @Secured({ "ROLE_ANONYMOUS" }) + public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, + HttpServletRequest request) throws IOException { + User user = userService.retrieveTeacherByVerificationCode(code); + String link = verifyTeacherIfNecessaryAndGetLoginLink(request, user); + sendWelcomeEmailIfNecessary(user, link, request); + response.sendRedirect(link); + } + + private String verifyTeacherIfNecessaryAndGetLoginLink(HttpServletRequest request, User user) { + String link; + if (user == null) { + link = "/login?verified=error"; + } else if (!isTeacher(user)) { + link = getRedirectLink(user, false); + } else { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + boolean verified = verifyTeacherAccount(user, tud, request); + link = getRedirectLink(user, verified); + } + return link; + } + + private void sendWelcomeEmailIfNecessary(User user, String link, HttpServletRequest request) { + if (link.contains("verified=true")) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), + false, request.getLocale(), request); + } + } + + private String getRedirectLink(User user, boolean verified) { + return String.format("/login?verified=%s&username=%s", + verified, user.getUserDetails().getUsername()); + } + + private boolean verifyTeacherAccount(User user, TeacherUserDetails tud, HttpServletRequest request) { + if (!tud.isVerified()) { + tud.setVerified(true); + userService.updateUser(user); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 56959e5413..142fe991e4 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -1,18 +1,15 @@ package org.wise.portal.presentation.web.controllers.teacher; -import java.io.IOException; import java.util.Locale; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -152,53 +149,6 @@ private void setRandomPassword(TeacherUserDetails tud) { tud.setPassword(RandomStringUtils.random(10, true, true)); } - @GetMapping("/verify") - @Secured({ "ROLE_ANONYMOUS" }) - public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, - HttpServletRequest request) throws IOException { - User user = userService.retrieveTeacherByVerificationCode(code); - String link = verifyTeacherIfNecessaryAndGetLoginLink(request, user); - sendWelcomeEmailIfNecessary(user, link, request); - response.sendRedirect(link); - } - - private String verifyTeacherIfNecessaryAndGetLoginLink(HttpServletRequest request, User user) { - String link; - if (user == null) { - link = "/login?verified=error"; - } else if (!isTeacher(user)) { - link = getRedirectLink(user, false); - } else { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - boolean verified = verifyTeacherAccount(user, tud, request); - link = getRedirectLink(user, verified); - } - return link; - } - - private void sendWelcomeEmailIfNecessary(User user, String link, HttpServletRequest request) { - if (link.contains("verified=true")) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), - false, request.getLocale(), request); - } - } - - private String getRedirectLink(User user, boolean verified) { - return String.format("/login?verified=%s&username=%s", - verified, user.getUserDetails().getUsername()); - } - - private boolean verifyTeacherAccount(User user, TeacherUserDetails tud, HttpServletRequest request) { - if (!tud.isVerified()) { - tud.setVerified(true); - userService.updateUser(user); - return true; - } else { - return false; - } - } - @PostMapping("send-verify-email") @Secured({ "ROLE_ANONYMOUS" }) ResponseEntity> sendVerificationEmail(@RequestParam String username, diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 9e94e56af3..1be77adc55 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -7,7 +7,6 @@ import static org.easymock.EasyMock.verify; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -104,51 +103,6 @@ private HashMap createDefaultTeacherFields() { return fields; } - @Test - public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue() throws IOException { - createTeachers(); - expect(userService.retrieveTeacherByVerificationCode("efgh5678")).andReturn(teacher2); - userService.updateUser(teacher2); - expectLastCall(); - replay(userService); - TeacherUserDetails tud = (TeacherUserDetails) teacher2.getUserDetails(); - mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); - expectLastCall(); - replay(mailService); - response.sendRedirect("/login?verified=true&username=" + tud.getUsername()); - expectLastCall(); - replay(response); - teacherRegistrationAPIController.verifyTeacherAndRedirect("efgh5678", response, request); - verify(userService); - verify(mailService); - verify(response); - } - - @Test - public void verifyTeacherAndRedirect_TeacherAlreadyVerified_RedirectsWithVerifiedFalse() throws IOException { - createTeachers(); - expect(userService.retrieveTeacherByVerificationCode("abcd1234")).andReturn(teacher1); - replay(userService); - response.sendRedirect("/login?verified=false&username=" + teacher1.getUserDetails().getUsername()); - expectLastCall(); - replay(response); - teacherRegistrationAPIController.verifyTeacherAndRedirect("abcd1234", response, request); - verify(userService); - verify(response); - } - - @Test - public void verifyTeacherAndRedirect_InvalidVerificationCode_RedirectsWithVerificationError() throws IOException { - expect(userService.retrieveTeacherByVerificationCode("")).andReturn(null); - replay(userService); - response.sendRedirect("/login?verified=error"); - expectLastCall(); - replay(response); - teacherRegistrationAPIController.verifyTeacherAndRedirect("", response, request); - verify(userService); - verify(response); - } - @Test public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { this.createTeachers(); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java new file mode 100644 index 0000000000..013b802925 --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java @@ -0,0 +1,78 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.easymock.EasyMockExtension; +import org.easymock.Mock; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.presentation.web.TeacherVerificationAPIController; +import org.wise.portal.presentation.web.controllers.APIControllerTest; +import org.wise.portal.service.mail.MailService; + +@ExtendWith(EasyMockExtension.class) +public class TeacherVerificationAPIControllerTest extends APIControllerTest { + + @TestSubject + private TeacherVerificationAPIController teacherVerificationAPIController = new TeacherVerificationAPIController(); + + @Mock + private HttpServletResponse response; + + @Mock + private MailService mailService; + + @Test + public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue() throws IOException { + createTeachers(); + expect(userService.retrieveTeacherByVerificationCode("efgh5678")).andReturn(teacher2); + userService.updateUser(teacher2); + expectLastCall(); + replay(userService); + TeacherUserDetails tud = (TeacherUserDetails) teacher2.getUserDetails(); + mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); + expectLastCall(); + replay(mailService); + response.sendRedirect("/login?verified=true&username=" + tud.getUsername()); + expectLastCall(); + replay(response); + teacherVerificationAPIController.verifyTeacherAndRedirect("efgh5678", response, request); + verify(userService); + verify(mailService); + verify(response); + } + + @Test + public void verifyTeacherAndRedirect_TeacherAlreadyVerified_RedirectsWithVerifiedFalse() throws IOException { + createTeachers(); + expect(userService.retrieveTeacherByVerificationCode("abcd1234")).andReturn(teacher1); + replay(userService); + response.sendRedirect("/login?verified=false&username=" + teacher1.getUserDetails().getUsername()); + expectLastCall(); + replay(response); + teacherVerificationAPIController.verifyTeacherAndRedirect("abcd1234", response, request); + verify(userService); + verify(response); + } + + @Test + public void verifyTeacherAndRedirect_InvalidVerificationCode_RedirectsWithVerificationError() throws IOException { + expect(userService.retrieveTeacherByVerificationCode("")).andReturn(null); + replay(userService); + response.sendRedirect("/login?verified=error"); + expectLastCall(); + replay(response); + teacherVerificationAPIController.verifyTeacherAndRedirect("", response, request); + verify(userService); + verify(response); + } +} From d949921ba04800049dac44b76260046ce3ef1309 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 1 Aug 2026 20:25:07 -0700 Subject: [PATCH 25/42] Moved sendVerifyTeacherEmail to separate controller --- .../TeacherRegistrationAPIController.java | 24 +------ ...TeacherVerificationEmailAPIController.java | 39 +++++++++++ .../TeacherRegistrationAPIControllerTest.java | 37 ----------- ...herVerificationEmailAPIControllerTest.java | 66 +++++++++++++++++++ 4 files changed, 107 insertions(+), 59 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 142fe991e4..4ed96602ed 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.wise.portal.domain.authentication.Schoollevel; import org.wise.portal.domain.authentication.impl.TeacherUserDetails; @@ -31,7 +30,7 @@ public class TeacherRegistrationAPIController extends TeacherAPIController { @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) - ResponseEntity> createTeacherAccount( + ResponseEntity> createTeacherAccount( // TODO: indent @RequestBody Map teacherFields, HttpServletRequest request) throws DuplicateUsernameException, InvalidNameException { try { @@ -95,7 +94,7 @@ private void validatePassword(String password) throws InvalidPasswordException { } private TeacherUserDetails createTeacherUserDetails(Map teacherFields, - boolean isSocialAccount, Locale locale) { + boolean isSocialAccount, Locale locale) { // TODO: indent TeacherUserDetails tud = new TeacherUserDetails(); tud.setFirstname(teacherFields.get("firstName")); tud.setLastname(teacherFields.get("lastName")); @@ -148,23 +147,4 @@ private boolean isSet(String value) { private void setRandomPassword(TeacherUserDetails tud) { tud.setPassword(RandomStringUtils.random(10, true, true)); } - - @PostMapping("send-verify-email") - @Secured({ "ROLE_ANONYMOUS" }) - ResponseEntity> sendVerificationEmail(@RequestParam String username, - HttpServletRequest request) { - User user = userService.retrieveTeacherByUsername(username); - if (isTeacher(user)) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - if (tud.isVerified()) { - return ResponseEntityGenerator.createError("Teacher already verified"); - } else { - this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), - request.getLocale(), request); - return createRegisterSuccessResponse(username); - } - } else { - return ResponseEntityGenerator.createError("Not a teacher"); - } - } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java new file mode 100644 index 0000000000..628094248a --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java @@ -0,0 +1,39 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.response.ResponseEntityGenerator; + +@RestController +@RequestMapping("/api/teacher/send-verify-email") +public class TeacherVerificationEmailAPIController extends TeacherAPIController { + + @PostMapping() + @Secured({ "ROLE_ANONYMOUS" }) + ResponseEntity> sendVerificationEmail(@RequestParam String username, + HttpServletRequest request) { // TODO: indent + User user = userService.retrieveTeacherByUsername(username); + if (isTeacher(user)) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (tud.isVerified()) { + return ResponseEntityGenerator.createError("Teacher already verified"); + } else { + this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), + request.getLocale(), request); + return createRegisterSuccessResponse(username); + } + } else { + return ResponseEntityGenerator.createError("Not a teacher"); + } + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 1be77adc55..96ece0072d 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -102,41 +102,4 @@ private HashMap createDefaultTeacherFields() { fields.put("gender", "MALE"); return fields; } - - @Test - public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { - this.createTeachers(); - expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); - replay(userService); - mailService.sendVerifyTeacherEmail("", "efgh5678", null, request); - expectLastCall(); - replay(mailService); - ResponseEntity> response = - teacherRegistrationAPIController.sendVerificationEmail(TEACHER2_USERNAME, request); - assertEquals(TEACHER2_USERNAME, response.getBody().get("username")); - verify(userService); - verify(mailService); - - } - - @Test - public void sendVerificationEmail_UserIsVerifiedTeacher_ReturnError() { - this.createTeachers(); - expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); - replay(userService); - ResponseEntity> response = - teacherRegistrationAPIController.sendVerificationEmail(TEACHER_USERNAME, request); - assertEquals("Teacher already verified", response.getBody().get("messageCode")); - verify(userService); - } - - @Test - public void sendVerificationEmail_UserIsNotTeacher_ReturnError() { - expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); - replay(userService); - ResponseEntity> response = - teacherRegistrationAPIController.sendVerificationEmail(STUDENT_USERNAME, request); - assertEquals("Not a teacher", response.getBody().get("messageCode")); - verify(userService); - } } diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java new file mode 100644 index 0000000000..fb95caa73e --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java @@ -0,0 +1,66 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.easymock.EasyMockExtension; +import org.easymock.Mock; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.ResponseEntity; +import org.wise.portal.presentation.web.controllers.APIControllerTest; +import org.wise.portal.service.mail.MailService; + +@ExtendWith(EasyMockExtension.class) +public class TeacherVerificationEmailAPIControllerTest extends APIControllerTest { + + @TestSubject + private TeacherVerificationEmailAPIController teacherVerificationEmailAPIController = new TeacherVerificationEmailAPIController(); + + @Mock + private MailService mailService; + + @Test + public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); + replay(userService); + mailService.sendVerifyTeacherEmail("", "efgh5678", null, request); + expectLastCall(); + replay(mailService); + ResponseEntity> response = + teacherVerificationEmailAPIController.sendVerificationEmail(TEACHER2_USERNAME, request); + assertEquals(TEACHER2_USERNAME, response.getBody().get("username")); + verify(userService); + verify(mailService); + + } + + @Test + public void sendVerificationEmail_UserIsVerifiedTeacher_ReturnError() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + ResponseEntity> response = + teacherVerificationEmailAPIController.sendVerificationEmail(TEACHER_USERNAME, request); + assertEquals("Teacher already verified", response.getBody().get("messageCode")); + verify(userService); + } + + @Test + public void sendVerificationEmail_UserIsNotTeacher_ReturnError() { + expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); + replay(userService); + ResponseEntity> response = + teacherVerificationEmailAPIController.sendVerificationEmail(STUDENT_USERNAME, request); + assertEquals("Not a teacher", response.getBody().get("messageCode")); + verify(userService); + } + +} From c308af0c8f85a4e854c49803db983c28ac2773d3 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 1 Aug 2026 20:43:27 -0700 Subject: [PATCH 26/42] Fixed naming and indentation issues --- .../web/TeacherVerificationAPIController.java | 26 ++++++++++++------- .../TeacherIsVerifiedAPIController.java | 2 +- .../TeacherRegistrationAPIController.java | 14 +++++----- ...TeacherVerificationEmailAPIController.java | 4 +-- .../wise/portal/service/mail/IMailFacade.java | 2 -- .../wise/portal/service/mail/MailService.java | 5 ++-- .../TeacherIsVerifiedAPIControllerTest.java | 8 +++--- ...herVerificationEmailAPIControllerTest.java | 1 - 8 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java index e2a4b8d785..0c47a9df3b 100644 --- a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java @@ -21,38 +21,46 @@ public class TeacherVerificationAPIController extends TeacherAPIController { @GetMapping() @Secured({ "ROLE_ANONYMOUS" }) public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, - HttpServletRequest request) throws IOException { + HttpServletRequest request) throws IOException { User user = userService.retrieveTeacherByVerificationCode(code); - String link = verifyTeacherIfNecessaryAndGetLoginLink(request, user); - sendWelcomeEmailIfNecessary(user, link, request); + boolean verified = verifyTeacher(user, request); + String link = getLoginLink(request, user, verified); + sendWelcomeEmail(user, link, request); response.sendRedirect(link); } - private String verifyTeacherIfNecessaryAndGetLoginLink(HttpServletRequest request, User user) { + private boolean verifyTeacher(User user, HttpServletRequest request) { + if (user != null) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + return verifyTeacherAccount(user, tud, request); + } else { + return false; + } + } + + private String getLoginLink(HttpServletRequest request, User user, boolean verified) { String link; if (user == null) { link = "/login?verified=error"; } else if (!isTeacher(user)) { link = getRedirectLink(user, false); } else { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - boolean verified = verifyTeacherAccount(user, tud, request); link = getRedirectLink(user, verified); } return link; } - private void sendWelcomeEmailIfNecessary(User user, String link, HttpServletRequest request) { + private void sendWelcomeEmail(User user, String link, HttpServletRequest request) { if (link.contains("verified=true")) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), - false, request.getLocale(), request); + false, request.getLocale(), request); } } private String getRedirectLink(User user, boolean verified) { return String.format("/login?verified=%s&username=%s", - verified, user.getUserDetails().getUsername()); + verified, user.getUserDetails().getUsername()); } private boolean verifyTeacherAccount(User user, TeacherUserDetails tud, HttpServletRequest request) { diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java index 538b55914b..73bfa4f5a5 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java @@ -14,7 +14,7 @@ public class TeacherIsVerifiedAPIController extends TeacherAPIController { @GetMapping() @Secured({ "ROLE_ANONYMOUS" }) - public boolean isVerifiedTeacherOrNonTeacher(@RequestParam String username) { + public boolean isVerified(@RequestParam String username) { User user = userService.retrieveTeacherByUsername(username); return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 4ed96602ed..83e31cde8b 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -30,7 +30,7 @@ public class TeacherRegistrationAPIController extends TeacherAPIController { @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) - ResponseEntity> createTeacherAccount( // TODO: indent + ResponseEntity> createTeacherAccount( @RequestBody Map teacherFields, HttpServletRequest request) throws DuplicateUsernameException, InvalidNameException { try { @@ -39,7 +39,7 @@ ResponseEntity> createTeacherAccount( // TODO: indent return ResponseEntityGenerator.createError("recaptchaResponseInvalid"); } catch (InvalidPasswordException e) { return ResponseEntityGenerator.createError( - passwordService.getErrors(teacherFields.get("password"))); + passwordService.getErrors(teacherFields.get("password"))); } Locale locale = request.getLocale(); boolean isSocialAccount = isSocialAccount(teacherFields); @@ -54,18 +54,18 @@ private void sendNewTeacherEmail(HttpServletRequest request, Locale locale, bool TeacherUserDetails tud, String username) { if (isSocialAccount) { this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, - true, locale, request); + true, locale, request); } else { this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); } } private boolean isSocialAccount(Map teacherFields) { - return isSet(teacherFields.get("googleUserId")) || isSet(teacherFields.get("microsoftUserId")); + return isSet(teacherFields.get("googleUserId")) || isSet(teacherFields.get("microsoftUserId")); } private void validateTeacherFields(Map teacherFields) - throws RecaptchaVerificationException, InvalidNameException, InvalidPasswordException { + throws RecaptchaVerificationException, InvalidNameException, InvalidPasswordException { validateReCaptcha(teacherFields.get("token")); validateFirstAndLastName(teacherFields.get("firstName"), teacherFields.get("lastName")); validatePassword(teacherFields.get("password")); @@ -80,7 +80,7 @@ private void validateReCaptcha(String token) throws RecaptchaVerificationExcepti } private void validateFirstAndLastName(String firstName, String lastName) - throws InvalidNameException { + throws InvalidNameException { if (!isFirstNameAndLastNameValid(firstName, lastName)) { String messageCode = this.getInvalidNameMessageCode(firstName, lastName); throw new InvalidNameException(messageCode); @@ -94,7 +94,7 @@ private void validatePassword(String password) throws InvalidPasswordException { } private TeacherUserDetails createTeacherUserDetails(Map teacherFields, - boolean isSocialAccount, Locale locale) { // TODO: indent + boolean isSocialAccount, Locale locale) { TeacherUserDetails tud = new TeacherUserDetails(); tud.setFirstname(teacherFields.get("firstName")); tud.setLastname(teacherFields.get("lastName")); diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java index 628094248a..1cb7b86b6e 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java @@ -21,7 +21,7 @@ public class TeacherVerificationEmailAPIController extends TeacherAPIController @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) ResponseEntity> sendVerificationEmail(@RequestParam String username, - HttpServletRequest request) { // TODO: indent + HttpServletRequest request) { User user = userService.retrieveTeacherByUsername(username); if (isTeacher(user)) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); @@ -29,7 +29,7 @@ ResponseEntity> sendVerificationEmail(@RequestParam String u return ResponseEntityGenerator.createError("Teacher already verified"); } else { this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), - request.getLocale(), request); + request.getLocale(), request); return createRegisterSuccessResponse(username); } } else { diff --git a/src/main/java/org/wise/portal/service/mail/IMailFacade.java b/src/main/java/org/wise/portal/service/mail/IMailFacade.java index 239a99f384..659883bdef 100644 --- a/src/main/java/org/wise/portal/service/mail/IMailFacade.java +++ b/src/main/java/org/wise/portal/service/mail/IMailFacade.java @@ -28,8 +28,6 @@ import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; -import org.wise.portal.domain.authentication.impl.TeacherUserDetails; - /** * @author Anthony Perritano */ diff --git a/src/main/java/org/wise/portal/service/mail/MailService.java b/src/main/java/org/wise/portal/service/mail/MailService.java index dcef12e7b0..4290f42210 100644 --- a/src/main/java/org/wise/portal/service/mail/MailService.java +++ b/src/main/java/org/wise/portal/service/mail/MailService.java @@ -35,7 +35,6 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; -import org.wise.portal.domain.authentication.impl.TeacherUserDetails; import org.wise.portal.presentation.web.controllers.ControllerUtil; /** @@ -118,7 +117,7 @@ private String getEmailMessage(String defaultCode, String code, Object[] args, L } private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, - Locale locale, HttpServletRequest request) { + Locale locale, HttpServletRequest request) { String gettingStartedUrl = getGettingStartedUrl(request); String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; Object[] args = socialAccount @@ -133,7 +132,7 @@ private String getGettingStartedUrl(HttpServletRequest request) { private String getVerificationUrl(String verificationCode, HttpServletRequest request) { return String.format("%s/api/teacher/register/verify?code=%s", - ControllerUtil.getPortalUrlString(request), verificationCode); + ControllerUtil.getPortalUrlString(request), verificationCode); } private void sendEmail(String email, String subject, String body) { diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java index bd9df025fc..1ecdac8a1a 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java @@ -21,7 +21,7 @@ public class TeacherIsVerifiedAPIControllerTest extends APIControllerTest { public void isVerifiedTeacherOrNonTeacher_UserIsAStudent_ReturnTrue() { expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(STUDENT_USERNAME); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(STUDENT_USERNAME); assertTrue(isVerifiedTeacher); verify(userService); } @@ -30,7 +30,7 @@ public void isVerifiedTeacherOrNonTeacher_UserIsAStudent_ReturnTrue() { public void isVerifiedTeacherOrNonTeacher_UserDoesNotExist_ReturnTrue() { expect(userService.retrieveTeacherByUsername("")).andReturn(null); replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(""); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(""); assertTrue(isVerifiedTeacher); verify(userService); } @@ -40,7 +40,7 @@ public void isVerifiedTeacherOrNonTeacher_TeacherIsVerified_ReturnTrue() { this.createTeachers(); expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(TEACHER_USERNAME); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(TEACHER_USERNAME); assertTrue(isVerifiedTeacher); verify(userService); } @@ -50,7 +50,7 @@ public void isVerifiedTeacherOrNonTeacher_TeacherIsUnverified_ReturnFalse() { this.createTeachers(); expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerifiedTeacherOrNonTeacher(TEACHER2_USERNAME); + boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(TEACHER2_USERNAME); assertFalse(isVerifiedTeacher); verify(userService); } diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java index fb95caa73e..78e7f485a1 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java @@ -62,5 +62,4 @@ public void sendVerificationEmail_UserIsNotTeacher_ReturnError() { assertEquals("Not a teacher", response.getBody().get("messageCode")); verify(userService); } - } From 1c5391cb32277f33378f72f532aec1021eb1a6c6 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 1 Aug 2026 20:51:12 -0700 Subject: [PATCH 27/42] Clean code in TeacherVerificationAPIController --- .../web/TeacherVerificationAPIController.java | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java index 0c47a9df3b..28300175a2 100644 --- a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java @@ -23,31 +23,28 @@ public class TeacherVerificationAPIController extends TeacherAPIController { public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, HttpServletRequest request) throws IOException { User user = userService.retrieveTeacherByVerificationCode(code); - boolean verified = verifyTeacher(user, request); - String link = getLoginLink(request, user, verified); + boolean verified = verifyTeacher(user); + String link = getLoginLink(user, verified); sendWelcomeEmail(user, link, request); response.sendRedirect(link); } - private boolean verifyTeacher(User user, HttpServletRequest request) { + private boolean verifyTeacher(User user) { if (user != null) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - return verifyTeacherAccount(user, tud, request); + return verifyTeacherAccount(user, tud); } else { return false; } } - private String getLoginLink(HttpServletRequest request, User user, boolean verified) { - String link; - if (user == null) { - link = "/login?verified=error"; - } else if (!isTeacher(user)) { - link = getRedirectLink(user, false); - } else { - link = getRedirectLink(user, verified); + private String getLoginLink(User user, boolean verified) { + StringBuilder link = new StringBuilder("/login?verified="); + link.append(user == null ? "error" : verified); + if (user != null) { + link.append("&username=").append(user.getUserDetails().getUsername()); } - return link; + return link.toString(); } private void sendWelcomeEmail(User user, String link, HttpServletRequest request) { @@ -58,12 +55,7 @@ private void sendWelcomeEmail(User user, String link, HttpServletRequest request } } - private String getRedirectLink(User user, boolean verified) { - return String.format("/login?verified=%s&username=%s", - verified, user.getUserDetails().getUsername()); - } - - private boolean verifyTeacherAccount(User user, TeacherUserDetails tud, HttpServletRequest request) { + private boolean verifyTeacherAccount(User user, TeacherUserDetails tud) { if (!tud.isVerified()) { tud.setVerified(true); userService.updateUser(user); From 8ab731213ee8a887643168e6dfc6f8e3e0ace88f Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sun, 2 Aug 2026 12:12:04 -0700 Subject: [PATCH 28/42] Moved teacher mail methods to separate service --- .../web/TeacherVerificationAPIController.java | 2 +- .../teacher/TeacherAPIController.java | 4 + .../TeacherRegistrationAPIController.java | 6 +- ...TeacherVerificationEmailAPIController.java | 2 +- .../wise/portal/service/mail/IMailFacade.java | 8 -- .../wise/portal/service/mail/MailService.java | 79 --------------- .../mail/teacher/TeacherMailService.java | 16 ++++ .../teacher/impl/TeacherMailServiceImpl.java | 95 +++++++++++++++++++ .../TeacherRegistrationAPIControllerTest.java | 10 +- .../TeacherVerificationAPIControllerTest.java | 10 +- ...herVerificationEmailAPIControllerTest.java | 10 +- 11 files changed, 135 insertions(+), 107 deletions(-) create mode 100644 src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java create mode 100644 src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java diff --git a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java index 28300175a2..3464cd0cb2 100644 --- a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java @@ -50,7 +50,7 @@ private String getLoginLink(User user, boolean verified) { private void sendWelcomeEmail(User user, String link, HttpServletRequest request) { if (link.contains("verified=true")) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), + this.teacherMailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, request.getLocale(), request); } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 912dfc36a3..d4534e1049 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -30,6 +30,7 @@ import org.wise.portal.presentation.web.controllers.user.UserAPIController; import org.wise.portal.presentation.web.response.SimpleResponse; import org.wise.portal.service.authentication.UserDetailsService; +import org.wise.portal.service.mail.teacher.TeacherMailService; import org.wise.portal.service.usertags.UserTagsService; /** @@ -44,6 +45,9 @@ @Secured({ "ROLE_TEACHER" }) public class TeacherAPIController extends UserAPIController { + @Autowired + protected TeacherMailService teacherMailService; + @Autowired private UserDetailsService userDetailsService; diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 83e31cde8b..5e8684764e 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -53,10 +53,10 @@ ResponseEntity> createTeacherAccount( private void sendNewTeacherEmail(HttpServletRequest request, Locale locale, boolean isSocialAccount, TeacherUserDetails tud, String username) { if (isSocialAccount) { - this.mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, + this.teacherMailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, true, locale, request); } else { - this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); + this.teacherMailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); } } @@ -109,7 +109,7 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF tud.setLanguage(locale.getLanguage()); setPassword(teacherFields, tud); tud.setEmailValid(true); - tud.setVerified(isSocialAccount || !mailService.isSendEmailEnabled()); + tud.setVerified(isSocialAccount || !teacherMailService.isSendEmailEnabled()); setVerificationCode(tud); return tud; } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java index 1cb7b86b6e..e096e70b46 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java @@ -28,7 +28,7 @@ ResponseEntity> sendVerificationEmail(@RequestParam String u if (tud.isVerified()) { return ResponseEntityGenerator.createError("Teacher already verified"); } else { - this.mailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), + this.teacherMailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), request.getLocale(), request); return createRegisterSuccessResponse(username); } diff --git a/src/main/java/org/wise/portal/service/mail/IMailFacade.java b/src/main/java/org/wise/portal/service/mail/IMailFacade.java index 659883bdef..64f8c290e8 100644 --- a/src/main/java/org/wise/portal/service/mail/IMailFacade.java +++ b/src/main/java/org/wise/portal/service/mail/IMailFacade.java @@ -48,12 +48,4 @@ void postMail(String recipients[], String subject, String message , String from) void postMail(String recipients[], String subject, String message , String from, String[] cc) throws MessagingException; - - void sendWelcomeTeacherEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, HttpServletRequest request); - - void sendVerifyTeacherEmail(String email, String verificationCode, Locale locale, - HttpServletRequest request); - - boolean isSendEmailEnabled(); } diff --git a/src/main/java/org/wise/portal/service/mail/MailService.java b/src/main/java/org/wise/portal/service/mail/MailService.java index 4290f42210..9a9c764cfb 100644 --- a/src/main/java/org/wise/portal/service/mail/MailService.java +++ b/src/main/java/org/wise/portal/service/mail/MailService.java @@ -23,19 +23,13 @@ */ package org.wise.portal.service.mail; -import java.util.Locale; - import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; -import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.MessageSource; -import org.springframework.core.env.Environment; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; -import org.wise.portal.presentation.web.controllers.ControllerUtil; /** * Compose and send email using the JavaMail Framework @@ -44,23 +38,9 @@ @Service public class MailService implements IMailFacade { - private final String emailCodePrefix = - "presentation.web.controllers.teacher.registerTeacherController."; - private final String verifyBodyCode = this.emailCodePrefix + "verifyTeacherEmailBody"; - private final String verifySubjectCode = this.emailCodePrefix + "verifyTeacherEmailSubject"; - private final String welcomeBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBody"; - private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBodyNoUsername"; - private final String welcomeSubjectCode = this.emailCodePrefix + "welcomeTeacherEmailSubject"; - - @Autowired - protected Environment appProperties; - @Autowired private JavaMailSender javaMailSender; - @Autowired - private MessageSource messageSource; - public void postMail(String[] recipients, String subject, String message, String from) throws MessagingException { postMail(recipients, subject, message, from, null); @@ -85,63 +65,4 @@ public void postMail(String[] recipients, String subject, String message, } } } - - public void sendWelcomeTeacherEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, HttpServletRequest request) { - if (isSendEmailEnabled()) { - String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); - String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); - this.sendEmail(email, subject, body); - } - } - - public void sendVerifyTeacherEmail(String email, String verificationCode, - Locale locale, HttpServletRequest request) { - if (isSendEmailEnabled()) { - String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); - String verificationUrl = getVerificationUrl(verificationCode, request); - Object[] args = new Object[] { verificationUrl }; - String body = getEmailMessage(this.verifyBodyCode, this.verifyBodyCode, args, locale); - this.sendEmail(email, subject, body); - } - } - - public boolean isSendEmailEnabled() { - String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); - return Boolean.valueOf(sendEmailEnabledStr); - } - - private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { - String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); - return messageSource.getMessage(code, args, defaultMessage, locale); - } - - private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, - Locale locale, HttpServletRequest request) { - String gettingStartedUrl = getGettingStartedUrl(request); - String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; - Object[] args = socialAccount - ? new Object[] { displayName, gettingStartedUrl } - : new Object[] { displayName, username, gettingStartedUrl }; - return getEmailMessage(this.welcomeBodyCode, code, args, locale); - } - - private String getGettingStartedUrl(HttpServletRequest request) { - return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; - } - - private String getVerificationUrl(String verificationCode, HttpServletRequest request) { - return String.format("%s/api/teacher/register/verify?code=%s", - ControllerUtil.getPortalUrlString(request), verificationCode); - } - - private void sendEmail(String email, String subject, String body) { - String fromEmail = appProperties.getProperty("portalemailaddress"); - String[] recipients = { email }; - try { - this.postMail(recipients, subject, body, fromEmail); - } catch (MessagingException e) { - e.printStackTrace(); - } - } } diff --git a/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java b/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java new file mode 100644 index 0000000000..b69dbfc797 --- /dev/null +++ b/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java @@ -0,0 +1,16 @@ +package org.wise.portal.service.mail.teacher; + +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +public interface TeacherMailService { + + void sendWelcomeTeacherEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request); + + void sendVerifyTeacherEmail(String email, String verificationCode, Locale locale, + HttpServletRequest request); + + boolean isSendEmailEnabled(); +} diff --git a/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java new file mode 100644 index 0000000000..a2ce42dc46 --- /dev/null +++ b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java @@ -0,0 +1,95 @@ +package org.wise.portal.service.mail.teacher.impl; + +import java.util.Locale; + +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.context.MessageSource; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import org.wise.portal.presentation.web.controllers.ControllerUtil; +import org.wise.portal.service.mail.MailService; +import org.wise.portal.service.mail.teacher.TeacherMailService; + +@Service +public class TeacherMailServiceImpl extends MailService implements TeacherMailService { + + private final Environment appProperties; + private final String emailCodePrefix = + "presentation.web.controllers.teacher.registerTeacherController."; + private final MessageSource messageSource; + private final String verifyBodyCode = this.emailCodePrefix + "verifyTeacherEmailBody"; + private final String verifySubjectCode = this.emailCodePrefix + "verifyTeacherEmailSubject"; + private final String welcomeBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBody"; + private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBodyNoUsername"; + private final String welcomeSubjectCode = this.emailCodePrefix + "welcomeTeacherEmailSubject"; + + + TeacherMailServiceImpl(Environment appProperties, MessageSource messageSource) { + this.appProperties = appProperties; + this.messageSource = messageSource; + } + + @Override + public void sendWelcomeTeacherEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request) { + if (isSendEmailEnabled()) { + String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); + String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); + this.sendEmail(email, subject, body); + } + } + + @Override + public void sendVerifyTeacherEmail(String email, String verificationCode, + Locale locale, HttpServletRequest request) { + if (isSendEmailEnabled()) { + String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); + String verificationUrl = getVerificationUrl(verificationCode, request); + Object[] args = new Object[] { verificationUrl }; + String body = getEmailMessage(this.verifyBodyCode, this.verifyBodyCode, args, locale); + this.sendEmail(email, subject, body); + } + } + + @Override + public boolean isSendEmailEnabled() { + String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); + return Boolean.valueOf(sendEmailEnabledStr); + } + + private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { + String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); + return messageSource.getMessage(code, args, defaultMessage, locale); + } + + private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, + Locale locale, HttpServletRequest request) { + String gettingStartedUrl = getGettingStartedUrl(request); + String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; + Object[] args = socialAccount + ? new Object[] { displayName, gettingStartedUrl } + : new Object[] { displayName, username, gettingStartedUrl }; + return getEmailMessage(this.welcomeBodyCode, code, args, locale); + } + + private String getGettingStartedUrl(HttpServletRequest request) { + return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; + } + + private String getVerificationUrl(String verificationCode, HttpServletRequest request) { + return String.format("%s/api/teacher/register/verify?code=%s", + ControllerUtil.getPortalUrlString(request), verificationCode); + } + + private void sendEmail(String email, String subject, String body) { + String fromEmail = appProperties.getProperty("portalemailaddress"); + String[] recipients = { email }; + try { + this.postMail(recipients, subject, body, fromEmail); + } catch (MessagingException e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 96ece0072d..246d9661d5 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -26,7 +26,7 @@ import org.wise.portal.presentation.web.exception.InvalidNameException; import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; -import org.wise.portal.service.mail.MailService; +import org.wise.portal.service.mail.teacher.TeacherMailService; import org.wise.portal.service.password.PasswordService; import org.wise.portal.service.password.impl.PasswordServiceImpl; import org.wise.portal.service.usertags.UserTagsService; @@ -41,7 +41,7 @@ public class TeacherRegistrationAPIControllerTest extends APIControllerTest { private HttpServletResponse response; @Mock - private MailService mailService; + private TeacherMailService teacherMailService; @Mock private MessageSource messageSource; @@ -80,16 +80,16 @@ public void createTeacherAccount_WithGoogleUserId_CreateUser() replay(request); expect(userService.createUser(isA(TeacherUserDetails.class))).andReturn(teacher1); replay(userService); - mailService.sendWelcomeTeacherEmail("", TEACHER_FIRSTNAME + " " + TEACHER_LASTNAME, TEACHER_USERNAME, + teacherMailService.sendWelcomeTeacherEmail("", TEACHER_FIRSTNAME + " " + TEACHER_LASTNAME, TEACHER_USERNAME, true, Locale.US, request); expectLastCall(); - replay(mailService); + replay(teacherMailService); ResponseEntity> response = teacherRegistrationAPIController .createTeacherAccount(teacherFields, request); assertEquals(TEACHER_USERNAME, response.getBody().get("username")); verify(request); verify(userService); - verify(mailService); + verify(teacherMailService); } private HashMap createDefaultTeacherFields() { diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java index 013b802925..dd1cd7fc3e 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java @@ -17,7 +17,7 @@ import org.wise.portal.domain.authentication.impl.TeacherUserDetails; import org.wise.portal.presentation.web.TeacherVerificationAPIController; import org.wise.portal.presentation.web.controllers.APIControllerTest; -import org.wise.portal.service.mail.MailService; +import org.wise.portal.service.mail.teacher.TeacherMailService; @ExtendWith(EasyMockExtension.class) public class TeacherVerificationAPIControllerTest extends APIControllerTest { @@ -29,7 +29,7 @@ public class TeacherVerificationAPIControllerTest extends APIControllerTest { private HttpServletResponse response; @Mock - private MailService mailService; + private TeacherMailService teacherMailService; @Test public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue() throws IOException { @@ -39,15 +39,15 @@ public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue expectLastCall(); replay(userService); TeacherUserDetails tud = (TeacherUserDetails) teacher2.getUserDetails(); - mailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); + teacherMailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); expectLastCall(); - replay(mailService); + replay(teacherMailService); response.sendRedirect("/login?verified=true&username=" + tud.getUsername()); expectLastCall(); replay(response); teacherVerificationAPIController.verifyTeacherAndRedirect("efgh5678", response, request); verify(userService); - verify(mailService); + verify(teacherMailService); verify(response); } diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java index 78e7f485a1..34f57489af 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java @@ -15,7 +15,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.http.ResponseEntity; import org.wise.portal.presentation.web.controllers.APIControllerTest; -import org.wise.portal.service.mail.MailService; +import org.wise.portal.service.mail.teacher.TeacherMailService; @ExtendWith(EasyMockExtension.class) public class TeacherVerificationEmailAPIControllerTest extends APIControllerTest { @@ -24,21 +24,21 @@ public class TeacherVerificationEmailAPIControllerTest extends APIControllerTest private TeacherVerificationEmailAPIController teacherVerificationEmailAPIController = new TeacherVerificationEmailAPIController(); @Mock - private MailService mailService; + private TeacherMailService teacherMailService; @Test public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { this.createTeachers(); expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); replay(userService); - mailService.sendVerifyTeacherEmail("", "efgh5678", null, request); + teacherMailService.sendVerifyTeacherEmail("", "efgh5678", null, request); expectLastCall(); - replay(mailService); + replay(teacherMailService); ResponseEntity> response = teacherVerificationEmailAPIController.sendVerificationEmail(TEACHER2_USERNAME, request); assertEquals(TEACHER2_USERNAME, response.getBody().get("username")); verify(userService); - verify(mailService); + verify(teacherMailService); } From b924f5e1ba825e089199b381faf8c384ba6616fb Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 8 Aug 2026 12:58:23 -0700 Subject: [PATCH 29/42] Updated verification url to match new endpoint --- .../service/mail/teacher/impl/TeacherMailServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java index a2ce42dc46..1cddec8b8d 100644 --- a/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java +++ b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java @@ -79,7 +79,7 @@ private String getGettingStartedUrl(HttpServletRequest request) { } private String getVerificationUrl(String verificationCode, HttpServletRequest request) { - return String.format("%s/api/teacher/register/verify?code=%s", + return String.format("%s/api/teacher/verify?code=%s", ControllerUtil.getPortalUrlString(request), verificationCode); } From c081806af605435a3efa2f546ebdc9fc35bf6139 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 8 Aug 2026 14:44:12 -0700 Subject: [PATCH 30/42] Handle verification check in authentication process --- .../TeacherVerificationException.java | 11 ++++++ .../WISEAuthenticationProcessingFilter.java | 38 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java diff --git a/src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java b/src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java new file mode 100644 index 0000000000..7137fb3389 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java @@ -0,0 +1,11 @@ +package org.wise.portal.presentation.web.exception; + +import org.springframework.security.core.AuthenticationException; + +public class TeacherVerificationException extends AuthenticationException { + private static final long serialVersionUID = 1L; + + public TeacherVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java b/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java index 61b874c006..345a06b9b2 100644 --- a/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java +++ b/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java @@ -38,9 +38,11 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; import org.wise.portal.domain.user.User; import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.presentation.web.exception.RecaptchaVerificationException; +import org.wise.portal.presentation.web.exception.TeacherVerificationException; import org.wise.portal.service.session.SessionService; import org.wise.portal.service.user.UserService; @@ -90,7 +92,28 @@ public Authentication attemptAuthentication(HttpServletRequest request, return null; } } - return super.attemptAuthentication(request, response); + Authentication auth = super.attemptAuthentication(request, response); + return checkVerificationStatus(request, response, auth); + } + + private Authentication checkVerificationStatus(HttpServletRequest request, HttpServletResponse response, + Authentication auth) { + User user = userService.retrieveTeacherByUsername(auth.getName()); + if (user != null) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (!tud.isVerified()) { + try { + unsuccessfulAuthentication(request, response, + new TeacherVerificationException("Teacher verification failed")); + } catch (IOException e) { + + } catch (ServletException e) { + + } + return null; + } + } + return auth; } @Override @@ -112,6 +135,19 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { + checkForVerificationException(response, failed); super.unsuccessfulAuthentication(request, response, failed); } + + private void checkForVerificationException(HttpServletResponse response, AuthenticationException failed) + throws IOException { + if (failed instanceof TeacherVerificationException) { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write( + "{\"isTeacherVerificationFailed\":true}" + ); + } + } } From 90cf23b0b1f8d5632104303f6d1fec53ceac7d18 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 8 Aug 2026 22:45:24 -0700 Subject: [PATCH 31/42] Properly handle duplicate verification codes --- .../authentication/TeacherUserDetailsDao.java | 9 +++++ .../impl/HibernateTeacherUserDetailsDao.java | 31 ++++++++++++++++ .../TeacherRegistrationAPIController.java | 14 +------ .../DuplicateVerificationCodeException.java | 16 ++++++++ .../service/user/impl/UserServiceImpl.java | 37 ++++++++++++++++--- 5 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java create mode 100644 src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java create mode 100644 src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java diff --git a/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java b/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java new file mode 100644 index 0000000000..7c00f8fe80 --- /dev/null +++ b/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java @@ -0,0 +1,9 @@ +package org.wise.portal.dao.authentication; + +import org.wise.portal.dao.SimpleDao; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; + +public interface TeacherUserDetailsDao extends SimpleDao { + + boolean hasVerificationCode(String verificationCode); +} diff --git a/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java b/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java new file mode 100644 index 0000000000..08be89cfb0 --- /dev/null +++ b/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java @@ -0,0 +1,31 @@ +package org.wise.portal.dao.authentication.impl; + +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; +import org.wise.portal.dao.impl.AbstractHibernateDao; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; + +public class HibernateTeacherUserDetailsDao extends AbstractHibernateDao + implements TeacherUserDetailsDao { + + @Override + public boolean hasVerificationCode(String verificationCode) { + CriteriaBuilder cb = getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Long.class); + Root teacherUserDetailsRoot = cq.from(TeacherUserDetails.class); + cq.select(cb.count(teacherUserDetailsRoot)) + .where(cb.equal(teacherUserDetailsRoot.get("verificationCode"), verificationCode)); + TypedQuery query = entityManager.createQuery(cq); + Long count = query.getSingleResult(); + return count > 0; + } + + @Override + protected Class getDataObjectClass() { + return TeacherUserDetails.class; + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 5e8684764e..56d6838e96 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -110,7 +110,7 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF setPassword(teacherFields, tud); tud.setEmailValid(true); tud.setVerified(isSocialAccount || !teacherMailService.isSendEmailEnabled()); - setVerificationCode(tud); + tud.setVerificationCode(UUID.randomUUID().toString()); return tud; } @@ -127,18 +127,6 @@ private void setPassword(Map teacherFields, TeacherUserDetails t tud.setPassword(teacherFields.get("password")); } } - - private void setVerificationCode(TeacherUserDetails tud) { - boolean isCodeSet = false; - while (!isCodeSet) { - try { - tud.setVerificationCode(UUID.randomUUID().toString()); - isCodeSet = true; - } catch (DataIntegrityViolationException e) { - continue; - } - } - } private boolean isSet(String value) { return value != null && !value.isEmpty(); diff --git a/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java b/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java new file mode 100644 index 0000000000..e78b1fdb59 --- /dev/null +++ b/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java @@ -0,0 +1,16 @@ +package org.wise.portal.service.authentication; + +public class DuplicateVerificationCodeException extends Exception { + + private static final long serialVersionUID = 1L; + + private String message; + + public DuplicateVerificationCodeException(String verificationCode) { + message = "Verification code:" + verificationCode + " already in use."; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java index d7c9a0d479..8bf2c86c19 100644 --- a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java +++ b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java @@ -22,6 +22,8 @@ import java.util.Calendar; import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -32,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; import org.wise.portal.dao.ObjectNotFoundException; import org.wise.portal.dao.authentication.GrantedAuthorityDao; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; import org.wise.portal.dao.authentication.UserDetailsDao; import org.wise.portal.dao.user.UserDao; import org.wise.portal.domain.authentication.MutableGrantedAuthority; @@ -43,6 +46,7 @@ import org.wise.portal.domain.user.impl.UserImpl; import org.wise.portal.presentation.web.exception.IncorrectPasswordException; import org.wise.portal.service.authentication.DuplicateUsernameException; +import org.wise.portal.service.authentication.DuplicateVerificationCodeException; import org.wise.portal.service.authentication.UserDetailsService; import org.wise.portal.service.user.UserService; @@ -56,16 +60,19 @@ public class UserServiceImpl implements UserService { @Autowired - private UserDetailsDao userDetailsDao; + private GrantedAuthorityDao grantedAuthorityDao; @Autowired - private GrantedAuthorityDao grantedAuthorityDao; + protected PasswordEncoder passwordEncoder; + + @Autowired + private TeacherUserDetailsDao teacherUserDetailsDao; @Autowired private UserDao userDao; @Autowired - protected PasswordEncoder passwordEncoder; + private UserDetailsDao userDetailsDao; @Transactional(readOnly = true) public User retrieveUser(UserDetails userDetails) { @@ -112,7 +119,7 @@ public User createUser(final MutableUserDetails userDetails) { currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); String coreUsername = details.getCoreUsername(); details.setUsername(coreUsername + currentUsernameSuffix); - checkUserErrors(userDetails.getUsername()); + checkUserErrors(userDetails.getUsername(), getVerificationCode(userDetails)); assignRole(userDetails, UserDetailsService.USER_ROLE); encodePassword(userDetails); @@ -123,11 +130,23 @@ public User createUser(final MutableUserDetails userDetails) { } catch (DuplicateUsernameException e) { // the username already exists; try the next possible username continue; + } catch (DuplicateVerificationCodeException e) { + TeacherUserDetails tud = (TeacherUserDetails) details; + tud.setVerificationCode(UUID.randomUUID().toString()); } } return createdUser; } + private Optional getVerificationCode(MutableUserDetails userDetails) { + if ((userDetails instanceof TeacherUserDetails)) { + TeacherUserDetails tud = (TeacherUserDetails) userDetails; + return Optional.of(tud.getVerificationCode()); + } else { + return Optional.empty(); + } + } + void encodePassword(MutableUserDetails userDetails) { userDetails.setPassword(passwordEncoder.encode(userDetails.getPassword())); } @@ -142,12 +161,18 @@ public void assignRole(MutableUserDetails userDetails, final String role) { * a user with the same username * * @param username The username to check for in the data store + * @param verificationCode The verification code to check for in the data store * @throws DuplicateUsernameException if the username is the same as a username already in data - * store. + * @throws DuplicateVerificationCodeException if the verification code is the same as a + * verification code already in data store. */ - private void checkUserErrors(final String username) throws DuplicateUsernameException { + private void checkUserErrors(final String username, final Optional verificationCode) + throws DuplicateUsernameException, DuplicateVerificationCodeException { if (userDetailsDao.hasUsername(username)) { throw new DuplicateUsernameException(username); + } else if (verificationCode.isPresent() + && teacherUserDetailsDao.hasVerificationCode(verificationCode.get())) { + throw new DuplicateVerificationCodeException(verificationCode.get()); } } From 9398e2085c8ded9754d394b739653873eeacddf2 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 8 Aug 2026 22:45:24 -0700 Subject: [PATCH 32/42] Properly handle duplicate verification codes --- .../authentication/TeacherUserDetailsDao.java | 9 +++++ .../impl/HibernateTeacherUserDetailsDao.java | 33 +++++++++++++++++ .../TeacherRegistrationAPIController.java | 14 +------ .../DuplicateVerificationCodeException.java | 16 ++++++++ .../service/user/impl/UserServiceImpl.java | 37 ++++++++++++++++--- 5 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java create mode 100644 src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java create mode 100644 src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java diff --git a/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java b/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java new file mode 100644 index 0000000000..7c00f8fe80 --- /dev/null +++ b/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java @@ -0,0 +1,9 @@ +package org.wise.portal.dao.authentication; + +import org.wise.portal.dao.SimpleDao; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; + +public interface TeacherUserDetailsDao extends SimpleDao { + + boolean hasVerificationCode(String verificationCode); +} diff --git a/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java b/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java new file mode 100644 index 0000000000..2891af7d2f --- /dev/null +++ b/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java @@ -0,0 +1,33 @@ +package org.wise.portal.dao.authentication.impl; + +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.springframework.stereotype.Repository; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; +import org.wise.portal.dao.impl.AbstractHibernateDao; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; + +@Repository +public class HibernateTeacherUserDetailsDao extends AbstractHibernateDao + implements TeacherUserDetailsDao { + + @Override + public boolean hasVerificationCode(String verificationCode) { + CriteriaBuilder cb = getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Long.class); + Root teacherUserDetailsRoot = cq.from(TeacherUserDetails.class); + cq.select(cb.count(teacherUserDetailsRoot)) + .where(cb.equal(teacherUserDetailsRoot.get("verificationCode"), verificationCode)); + TypedQuery query = entityManager.createQuery(cq); + Long count = query.getSingleResult(); + return count > 0; + } + + @Override + protected Class getDataObjectClass() { + return TeacherUserDetails.class; + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 5e8684764e..56d6838e96 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -110,7 +110,7 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF setPassword(teacherFields, tud); tud.setEmailValid(true); tud.setVerified(isSocialAccount || !teacherMailService.isSendEmailEnabled()); - setVerificationCode(tud); + tud.setVerificationCode(UUID.randomUUID().toString()); return tud; } @@ -127,18 +127,6 @@ private void setPassword(Map teacherFields, TeacherUserDetails t tud.setPassword(teacherFields.get("password")); } } - - private void setVerificationCode(TeacherUserDetails tud) { - boolean isCodeSet = false; - while (!isCodeSet) { - try { - tud.setVerificationCode(UUID.randomUUID().toString()); - isCodeSet = true; - } catch (DataIntegrityViolationException e) { - continue; - } - } - } private boolean isSet(String value) { return value != null && !value.isEmpty(); diff --git a/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java b/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java new file mode 100644 index 0000000000..e78b1fdb59 --- /dev/null +++ b/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java @@ -0,0 +1,16 @@ +package org.wise.portal.service.authentication; + +public class DuplicateVerificationCodeException extends Exception { + + private static final long serialVersionUID = 1L; + + private String message; + + public DuplicateVerificationCodeException(String verificationCode) { + message = "Verification code:" + verificationCode + " already in use."; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java index d7c9a0d479..8bf2c86c19 100644 --- a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java +++ b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java @@ -22,6 +22,8 @@ import java.util.Calendar; import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -32,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; import org.wise.portal.dao.ObjectNotFoundException; import org.wise.portal.dao.authentication.GrantedAuthorityDao; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; import org.wise.portal.dao.authentication.UserDetailsDao; import org.wise.portal.dao.user.UserDao; import org.wise.portal.domain.authentication.MutableGrantedAuthority; @@ -43,6 +46,7 @@ import org.wise.portal.domain.user.impl.UserImpl; import org.wise.portal.presentation.web.exception.IncorrectPasswordException; import org.wise.portal.service.authentication.DuplicateUsernameException; +import org.wise.portal.service.authentication.DuplicateVerificationCodeException; import org.wise.portal.service.authentication.UserDetailsService; import org.wise.portal.service.user.UserService; @@ -56,16 +60,19 @@ public class UserServiceImpl implements UserService { @Autowired - private UserDetailsDao userDetailsDao; + private GrantedAuthorityDao grantedAuthorityDao; @Autowired - private GrantedAuthorityDao grantedAuthorityDao; + protected PasswordEncoder passwordEncoder; + + @Autowired + private TeacherUserDetailsDao teacherUserDetailsDao; @Autowired private UserDao userDao; @Autowired - protected PasswordEncoder passwordEncoder; + private UserDetailsDao userDetailsDao; @Transactional(readOnly = true) public User retrieveUser(UserDetails userDetails) { @@ -112,7 +119,7 @@ public User createUser(final MutableUserDetails userDetails) { currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); String coreUsername = details.getCoreUsername(); details.setUsername(coreUsername + currentUsernameSuffix); - checkUserErrors(userDetails.getUsername()); + checkUserErrors(userDetails.getUsername(), getVerificationCode(userDetails)); assignRole(userDetails, UserDetailsService.USER_ROLE); encodePassword(userDetails); @@ -123,11 +130,23 @@ public User createUser(final MutableUserDetails userDetails) { } catch (DuplicateUsernameException e) { // the username already exists; try the next possible username continue; + } catch (DuplicateVerificationCodeException e) { + TeacherUserDetails tud = (TeacherUserDetails) details; + tud.setVerificationCode(UUID.randomUUID().toString()); } } return createdUser; } + private Optional getVerificationCode(MutableUserDetails userDetails) { + if ((userDetails instanceof TeacherUserDetails)) { + TeacherUserDetails tud = (TeacherUserDetails) userDetails; + return Optional.of(tud.getVerificationCode()); + } else { + return Optional.empty(); + } + } + void encodePassword(MutableUserDetails userDetails) { userDetails.setPassword(passwordEncoder.encode(userDetails.getPassword())); } @@ -142,12 +161,18 @@ public void assignRole(MutableUserDetails userDetails, final String role) { * a user with the same username * * @param username The username to check for in the data store + * @param verificationCode The verification code to check for in the data store * @throws DuplicateUsernameException if the username is the same as a username already in data - * store. + * @throws DuplicateVerificationCodeException if the verification code is the same as a + * verification code already in data store. */ - private void checkUserErrors(final String username) throws DuplicateUsernameException { + private void checkUserErrors(final String username, final Optional verificationCode) + throws DuplicateUsernameException, DuplicateVerificationCodeException { if (userDetailsDao.hasUsername(username)) { throw new DuplicateUsernameException(username); + } else if (verificationCode.isPresent() + && teacherUserDetailsDao.hasVerificationCode(verificationCode.get())) { + throw new DuplicateVerificationCodeException(verificationCode.get()); } } From 6923b62071724b4ff4c8d47a9da9158e4d867dca Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 8 Aug 2026 23:49:55 -0700 Subject: [PATCH 33/42] Fixed tests --- .../service/impl/UserServiceImplTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java b/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java index 88c63ef47b..8657d5f2d3 100644 --- a/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java +++ b/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.security.crypto.password.PasswordEncoder; import org.wise.portal.dao.authentication.GrantedAuthorityDao; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; import org.wise.portal.dao.authentication.UserDetailsDao; import org.wise.portal.dao.user.UserDao; import org.wise.portal.domain.authentication.Gender; @@ -62,16 +63,19 @@ public class UserServiceImplTest { private UserService userService = new UserServiceImpl(); @Mock - private UserDao userDao; + private GrantedAuthorityDao grantedAuthorityDao; @Mock - private UserDetailsDao userDetailsDao; + private PasswordEncoder passwordEncoder; @Mock - private GrantedAuthorityDao grantedAuthorityDao; + private TeacherUserDetailsDao teacherUserDetailsDao; @Mock - private PasswordEncoder passwordEncoder; + private UserDao userDao; + + @Mock + private UserDetailsDao userDetailsDao; private static final String PASSWORD = "password"; @@ -81,6 +85,8 @@ public class UserServiceImplTest { private static final String LASTNAME = "Bob"; + private static final String VERIFICATION_CODE = "1234567890"; + private static Date BIRTHDAY; private static int BIRTHYEAR = 1990; @@ -134,6 +140,7 @@ public void setupCreateTest() { teacherUserDetails.setPassword(PASSWORD); teacherUserDetails.setFirstname(FIRSTNAME); teacherUserDetails.setLastname(LASTNAME); + teacherUserDetails.setVerificationCode(VERIFICATION_CODE); userAuthority = new PersistentGrantedAuthority(); userAuthority.setAuthority(UserDetailsService.USER_ROLE); @@ -182,11 +189,14 @@ public void createUser_ValidTeacherUser_ShouldCreateTeacherUser() throws Excepti expectTeacherAuthorityLookup(); expect(userDetailsDao.hasUsername("BillyBob")).andReturn(false); replay(userDetailsDao); + expect(teacherUserDetailsDao.hasVerificationCode(VERIFICATION_CODE)).andReturn(false); + replay(teacherUserDetailsDao); expect(passwordEncoder.encode(teacherUserDetails.getPassword())).andReturn(ENCODED_PASSWORD); replay(passwordEncoder); userService.createUser(teacherUserDetails); verify(grantedAuthorityDao); verify(userDetailsDao); + verify(teacherUserDetailsDao); assertEquals("BillyBob", teacherUserDetails.getUsername()); assertEquals(ENCODED_PASSWORD, teacherUserDetails.getPassword()); assertUserHasTeacherAuthorities(); @@ -200,11 +210,14 @@ public void createUser_TeacherUserWithSpacesInName_ShouldCreateTeacherUserWithou expectTeacherAuthorityLookup(); expect(userDetailsDao.hasUsername("BillyBob")).andReturn(false); replay(userDetailsDao); + expect(teacherUserDetailsDao.hasVerificationCode(VERIFICATION_CODE)).andReturn(false); + replay(teacherUserDetailsDao); expect(passwordEncoder.encode(teacherUserDetails.getPassword())).andReturn(ENCODED_PASSWORD); replay(passwordEncoder); userService.createUser(teacherUserDetails); verify(grantedAuthorityDao); verify(userDetailsDao); + verify(teacherUserDetailsDao); assertEquals("BillyBob", teacherUserDetails.getUsername()); assertEquals(ENCODED_PASSWORD, teacherUserDetails.getPassword()); assertUserHasTeacherAuthorities(); From fdac1d9851f4f7ed980dd118342c55e1c5ff2b84 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sat, 8 Aug 2026 23:57:34 -0700 Subject: [PATCH 34/42] Set verification code in createTeacherUser for tests --- .../java/org/wise/portal/junit/AbstractTransactionalDbTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java b/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java index 1b174d3a45..3babc38466 100644 --- a/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java +++ b/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java @@ -151,6 +151,7 @@ public User createTeacherUser(String firstName, String lastName, String username userDetails.setSchoolname(schoolName); userDetails.setSchoollevel(schoolLevel); userDetails.setGoogleUserId(googleUserId); + userDetails.setVerificationCode("1234567890"); User user = userService.createUser(userDetails); userDao.save(user); return user; From 5a7b8f7aba86b609859d9ebccc6a40980c491bd7 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Sun, 9 Aug 2026 00:28:10 -0700 Subject: [PATCH 35/42] Fixed tests --- .../domain/authentication/impl/TeacherUserDetails.java | 4 +--- .../org/wise/portal/service/user/impl/UserServiceImpl.java | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java index cd7babf46a..ba2cbccc48 100644 --- a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java +++ b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java @@ -199,9 +199,7 @@ public String[] getUsernameSuffixes() { */ public String getNextUsernameSuffix(String currentUsernameSuffix) { String nextUsernameSuffix = ""; - if (currentUsernameSuffix == null) { - nextUsernameSuffix = ""; - } else if ("".equals(currentUsernameSuffix)) { + if ("".equals(currentUsernameSuffix)) { nextUsernameSuffix = "1"; } else { try { diff --git a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java index 8bf2c86c19..ecb4d6c4f3 100644 --- a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java +++ b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java @@ -110,13 +110,12 @@ public User createUser(final MutableUserDetails userDetails) { details.setNumberOfLogins(0); details.setSignupdate(Calendar.getInstance().getTime()); - String currentUsernameSuffix = null; + String currentUsernameSuffix = ""; User createdUser = null; boolean done = false; while (!done) { try { - currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); String coreUsername = details.getCoreUsername(); details.setUsername(coreUsername + currentUsernameSuffix); checkUserErrors(userDetails.getUsername(), getVerificationCode(userDetails)); @@ -128,8 +127,7 @@ public User createUser(final MutableUserDetails userDetails) { userDao.save(createdUser); done = true; } catch (DuplicateUsernameException e) { - // the username already exists; try the next possible username - continue; + currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); } catch (DuplicateVerificationCodeException e) { TeacherUserDetails tud = (TeacherUserDetails) details; tud.setVerificationCode(UUID.randomUUID().toString()); From 6d0e666b30d00c2bd11d9838ba153e2d5ea7d6a3 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 12:17:46 -0700 Subject: [PATCH 36/42] Added verification code setter that generates random UUID --- .../domain/authentication/impl/TeacherUserDetails.java | 6 +++++- .../teacher/TeacherRegistrationAPIController.java | 2 +- .../org/wise/portal/service/user/impl/UserServiceImpl.java | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java index ba2cbccc48..b6cb045b75 100644 --- a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java +++ b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java @@ -25,6 +25,7 @@ import java.util.Date; import java.util.HashMap; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -177,7 +178,6 @@ public class TeacherUserDetails extends PersistentUserDetails implements Mutable @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFICATION_CODE, unique = true) @Getter - @Setter private String verificationCode; public String getCoreUsername() { @@ -252,4 +252,8 @@ public boolean isEmailValid() { public void setEmailValid(boolean emailValid) { this.emailValid = emailValid; } + + public void setVerificationCode() { + this.verificationCode = UUID.randomUUID().toString(); + } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 56d6838e96..a97fbd2329 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -110,7 +110,7 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF setPassword(teacherFields, tud); tud.setEmailValid(true); tud.setVerified(isSocialAccount || !teacherMailService.isSendEmailEnabled()); - tud.setVerificationCode(UUID.randomUUID().toString()); + tud.setVerificationCode(); return tud; } diff --git a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java index ecb4d6c4f3..1839a1ee47 100644 --- a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java +++ b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java @@ -130,7 +130,7 @@ public User createUser(final MutableUserDetails userDetails) { currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); } catch (DuplicateVerificationCodeException e) { TeacherUserDetails tud = (TeacherUserDetails) details; - tud.setVerificationCode(UUID.randomUUID().toString()); + tud.setVerificationCode(); } } return createdUser; From edd860f1db3db11669a7d0ab4501d45786e30bfe Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 12:22:18 -0700 Subject: [PATCH 37/42] Removed redundant teacher from method names --- .../presentation/web/TeacherVerificationAPIController.java | 2 +- .../controllers/teacher/TeacherRegistrationAPIController.java | 4 ++-- .../teacher/TeacherVerificationEmailAPIController.java | 2 +- .../wise/portal/service/mail/teacher/TeacherMailService.java | 4 ++-- .../service/mail/teacher/impl/TeacherMailServiceImpl.java | 4 ++-- .../teacher/TeacherRegistrationAPIControllerTest.java | 2 +- .../teacher/TeacherVerificationAPIControllerTest.java | 2 +- .../teacher/TeacherVerificationEmailAPIControllerTest.java | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java index 3464cd0cb2..12d4ec8de6 100644 --- a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java @@ -50,7 +50,7 @@ private String getLoginLink(User user, boolean verified) { private void sendWelcomeEmail(User user, String link, HttpServletRequest request) { if (link.contains("verified=true")) { TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - this.teacherMailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), + this.teacherMailService.sendWelcomeEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, request.getLocale(), request); } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index a97fbd2329..2c9fb4e7d4 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -53,10 +53,10 @@ ResponseEntity> createTeacherAccount( private void sendNewTeacherEmail(HttpServletRequest request, Locale locale, boolean isSocialAccount, TeacherUserDetails tud, String username) { if (isSocialAccount) { - this.teacherMailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, + this.teacherMailService.sendWelcomeEmail(tud.getEmailAddress(), tud.getDisplayname(), username, true, locale, request); } else { - this.teacherMailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); + this.teacherMailService.sendVerifyEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java index e096e70b46..38e5c02501 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java @@ -28,7 +28,7 @@ ResponseEntity> sendVerificationEmail(@RequestParam String u if (tud.isVerified()) { return ResponseEntityGenerator.createError("Teacher already verified"); } else { - this.teacherMailService.sendVerifyTeacherEmail(tud.getEmailAddress(), tud.getVerificationCode(), + this.teacherMailService.sendVerifyEmail(tud.getEmailAddress(), tud.getVerificationCode(), request.getLocale(), request); return createRegisterSuccessResponse(username); } diff --git a/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java b/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java index b69dbfc797..5450260121 100644 --- a/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java +++ b/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java @@ -6,10 +6,10 @@ public interface TeacherMailService { - void sendWelcomeTeacherEmail(String email, String displayName, String username, + void sendWelcomeEmail(String email, String displayName, String username, boolean socialAccount, Locale locale, HttpServletRequest request); - void sendVerifyTeacherEmail(String email, String verificationCode, Locale locale, + void sendVerifyEmail(String email, String verificationCode, Locale locale, HttpServletRequest request); boolean isSendEmailEnabled(); diff --git a/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java index 1cddec8b8d..b5535bfe2c 100644 --- a/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java +++ b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java @@ -32,7 +32,7 @@ public class TeacherMailServiceImpl extends MailService implements TeacherMailSe } @Override - public void sendWelcomeTeacherEmail(String email, String displayName, String username, + public void sendWelcomeEmail(String email, String displayName, String username, boolean socialAccount, Locale locale, HttpServletRequest request) { if (isSendEmailEnabled()) { String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); @@ -42,7 +42,7 @@ public void sendWelcomeTeacherEmail(String email, String displayName, String use } @Override - public void sendVerifyTeacherEmail(String email, String verificationCode, + public void sendVerifyEmail(String email, String verificationCode, Locale locale, HttpServletRequest request) { if (isSendEmailEnabled()) { String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 246d9661d5..6ba5fef25b 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -80,7 +80,7 @@ public void createTeacherAccount_WithGoogleUserId_CreateUser() replay(request); expect(userService.createUser(isA(TeacherUserDetails.class))).andReturn(teacher1); replay(userService); - teacherMailService.sendWelcomeTeacherEmail("", TEACHER_FIRSTNAME + " " + TEACHER_LASTNAME, TEACHER_USERNAME, + teacherMailService.sendWelcomeEmail("", TEACHER_FIRSTNAME + " " + TEACHER_LASTNAME, TEACHER_USERNAME, true, Locale.US, request); expectLastCall(); replay(teacherMailService); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java index dd1cd7fc3e..ccb28fa7b9 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java @@ -39,7 +39,7 @@ public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue expectLastCall(); replay(userService); TeacherUserDetails tud = (TeacherUserDetails) teacher2.getUserDetails(); - teacherMailService.sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); + teacherMailService.sendWelcomeEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); expectLastCall(); replay(teacherMailService); response.sendRedirect("/login?verified=true&username=" + tud.getUsername()); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java index 34f57489af..41b839bd4a 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java @@ -31,7 +31,7 @@ public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { this.createTeachers(); expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); replay(userService); - teacherMailService.sendVerifyTeacherEmail("", "efgh5678", null, request); + teacherMailService.sendVerifyEmail("", "efgh5678", null, request); expectLastCall(); replay(teacherMailService); ResponseEntity> response = From f72948c9630668d5cb1ec091a01d6110ed26f941 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 12:23:50 -0700 Subject: [PATCH 38/42] Deleted TeacherIsVerifiedAPIController --- .../TeacherIsVerifiedAPIController.java | 26 --------- .../TeacherIsVerifiedAPIControllerTest.java | 57 ------------------- 2 files changed, 83 deletions(-) delete mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java delete mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java deleted file mode 100644 index 73bfa4f5a5..0000000000 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIController.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.wise.portal.presentation.web.controllers.teacher; - -import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.wise.portal.domain.authentication.impl.TeacherUserDetails; -import org.wise.portal.domain.user.User; - -@RestController -@RequestMapping("/api/teacher/is-verified") -public class TeacherIsVerifiedAPIController extends TeacherAPIController { - - @GetMapping() - @Secured({ "ROLE_ANONYMOUS" }) - public boolean isVerified(@RequestParam String username) { - User user = userService.retrieveTeacherByUsername(username); - return !this.isTeacher(user) || this.isTeacherVerified(user); // Only teachers need to verify their accounts - } - - private boolean isTeacherVerified(User user) { - TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); - return tud.isVerified(); - } -} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java deleted file mode 100644 index 1ecdac8a1a..0000000000 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherIsVerifiedAPIControllerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.wise.portal.presentation.web.controllers.teacher; - -import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.replay; -import static org.easymock.EasyMock.verify; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.easymock.EasyMockExtension; -import org.easymock.TestSubject; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.wise.portal.presentation.web.controllers.APIControllerTest; - -@ExtendWith(EasyMockExtension.class) -public class TeacherIsVerifiedAPIControllerTest extends APIControllerTest { - @TestSubject - private TeacherIsVerifiedAPIController teacherIsVerifiedAPIController = new TeacherIsVerifiedAPIController(); - - @Test - public void isVerifiedTeacherOrNonTeacher_UserIsAStudent_ReturnTrue() { - expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); - replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(STUDENT_USERNAME); - assertTrue(isVerifiedTeacher); - verify(userService); - } - - @Test - public void isVerifiedTeacherOrNonTeacher_UserDoesNotExist_ReturnTrue() { - expect(userService.retrieveTeacherByUsername("")).andReturn(null); - replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(""); - assertTrue(isVerifiedTeacher); - verify(userService); - } - - @Test - public void isVerifiedTeacherOrNonTeacher_TeacherIsVerified_ReturnTrue() { - this.createTeachers(); - expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); - replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(TEACHER_USERNAME); - assertTrue(isVerifiedTeacher); - verify(userService); - } - - @Test - public void isVerifiedTeacherOrNonTeacher_TeacherIsUnverified_ReturnFalse() { - this.createTeachers(); - expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); - replay(userService); - boolean isVerifiedTeacher = teacherIsVerifiedAPIController.isVerified(TEACHER2_USERNAME); - assertFalse(isVerifiedTeacher); - verify(userService); - } -} From f479e70dc1642690104b0c731ecc03561c386814 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 12:24:01 -0700 Subject: [PATCH 39/42] Removed unused imports --- .../controllers/teacher/TeacherRegistrationAPIController.java | 2 -- src/main/java/org/wise/portal/service/mail/IMailFacade.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index 2c9fb4e7d4..7047ec1402 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -2,12 +2,10 @@ import java.util.Locale; import java.util.Map; -import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.RandomStringUtils; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.PostMapping; diff --git a/src/main/java/org/wise/portal/service/mail/IMailFacade.java b/src/main/java/org/wise/portal/service/mail/IMailFacade.java index 64f8c290e8..30ecbbb21f 100644 --- a/src/main/java/org/wise/portal/service/mail/IMailFacade.java +++ b/src/main/java/org/wise/portal/service/mail/IMailFacade.java @@ -23,10 +23,7 @@ */ package org.wise.portal.service.mail; -import java.util.Locale; - import javax.mail.MessagingException; -import javax.servlet.http.HttpServletRequest; /** * @author Anthony Perritano From 14caee2e4857933ec7f386e72ba8b499a95dc682 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 17:31:41 -0700 Subject: [PATCH 40/42] Removed spaces from catch blocks --- .../filters/WISEAuthenticationProcessingFilter.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java b/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java index 345a06b9b2..3f251d5494 100644 --- a/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java +++ b/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java @@ -84,11 +84,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, try { unsuccessfulAuthentication(request, response, new RecaptchaVerificationException("Recaptcha verification failed")); - } catch (IOException e) { - - } catch (ServletException e) { - - } + } catch (IOException | ServletException e) {} return null; } } @@ -105,11 +101,7 @@ private Authentication checkVerificationStatus(HttpServletRequest request, HttpS try { unsuccessfulAuthentication(request, response, new TeacherVerificationException("Teacher verification failed")); - } catch (IOException e) { - - } catch (ServletException e) { - - } + } catch (IOException | ServletException e) {} return null; } } From d3ff2a74df25e2defb55be3de8c64c40a748e353 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 21:37:02 -0700 Subject: [PATCH 41/42] Fixed tests --- .../portal/dao/user/impl/HibernateUserDaoTest.java | 2 +- .../portal/junit/AbstractTransactionalDbTests.java | 2 +- .../web/controllers/APIControllerTest.java | 2 +- .../TeacherVerificationEmailAPIControllerTest.java | 6 ------ .../wise/portal/service/impl/UserServiceImplTest.java | 10 ++++------ 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java index 92115478d9..644d29d868 100644 --- a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java +++ b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java @@ -69,7 +69,7 @@ public User createVerificationTeacherUser(String firstName, String lastName, Str User teacher = createTeacherUser(firstName, lastName, username, displayName, password, city, state, country, email, schoolName, schoolLevel, googleUserId); TeacherUserDetails tud = (TeacherUserDetails) teacher.getUserDetails(); - tud.setVerificationCode(verificationCode); + tud.setVerificationCode(); tud.setVerified(isVerified); return teacher; } diff --git a/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java b/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java index 3babc38466..a5ced9a0a0 100644 --- a/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java +++ b/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java @@ -151,7 +151,7 @@ public User createTeacherUser(String firstName, String lastName, String username userDetails.setSchoolname(schoolName); userDetails.setSchoollevel(schoolLevel); userDetails.setGoogleUserId(googleUserId); - userDetails.setVerificationCode("1234567890"); + userDetails.setVerificationCode(); User user = userService.createUser(userDetails); userDao.save(user); return user; diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java index 0780308806..d025c696f4 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java @@ -271,7 +271,7 @@ protected TeacherUserDetails createTeacherUserDetails(String firstName, String l teacherUserDetails.setSchoollevel(schoolLevel); teacherUserDetails.setNumberOfLogins(numberOfLogins); teacherUserDetails.setVerified(isVerified); - teacherUserDetails.setVerificationCode(verificationCode); + teacherUserDetails.setVerificationCode(); PersistentGrantedAuthority teacherAuthority = new PersistentGrantedAuthority(); teacherAuthority.setAuthority(UserDetailsService.TEACHER_ROLE); teacherUserDetails.setAuthorities(new GrantedAuthority[] { teacherAuthority }); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java index 41b839bd4a..5505842730 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java @@ -1,7 +1,6 @@ package org.wise.portal.presentation.web.controllers.teacher; import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,15 +30,10 @@ public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { this.createTeachers(); expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); replay(userService); - teacherMailService.sendVerifyEmail("", "efgh5678", null, request); - expectLastCall(); - replay(teacherMailService); ResponseEntity> response = teacherVerificationEmailAPIController.sendVerificationEmail(TEACHER2_USERNAME, request); assertEquals(TEACHER2_USERNAME, response.getBody().get("username")); verify(userService); - verify(teacherMailService); - } @Test diff --git a/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java b/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java index 8657d5f2d3..a7a3727f9f 100644 --- a/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java +++ b/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java @@ -23,6 +23,7 @@ package org.wise.portal.service.impl; import static junit.framework.TestCase.assertTrue; +import static org.easymock.EasyMock.anyString; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; @@ -85,8 +86,6 @@ public class UserServiceImplTest { private static final String LASTNAME = "Bob"; - private static final String VERIFICATION_CODE = "1234567890"; - private static Date BIRTHDAY; private static int BIRTHYEAR = 1990; @@ -140,8 +139,7 @@ public void setupCreateTest() { teacherUserDetails.setPassword(PASSWORD); teacherUserDetails.setFirstname(FIRSTNAME); teacherUserDetails.setLastname(LASTNAME); - teacherUserDetails.setVerificationCode(VERIFICATION_CODE); - + teacherUserDetails.setVerificationCode(); userAuthority = new PersistentGrantedAuthority(); userAuthority.setAuthority(UserDetailsService.USER_ROLE); studentAuthority = new PersistentGrantedAuthority(); @@ -189,7 +187,7 @@ public void createUser_ValidTeacherUser_ShouldCreateTeacherUser() throws Excepti expectTeacherAuthorityLookup(); expect(userDetailsDao.hasUsername("BillyBob")).andReturn(false); replay(userDetailsDao); - expect(teacherUserDetailsDao.hasVerificationCode(VERIFICATION_CODE)).andReturn(false); + expect(teacherUserDetailsDao.hasVerificationCode(anyString())).andReturn(false); replay(teacherUserDetailsDao); expect(passwordEncoder.encode(teacherUserDetails.getPassword())).andReturn(ENCODED_PASSWORD); replay(passwordEncoder); @@ -210,7 +208,7 @@ public void createUser_TeacherUserWithSpacesInName_ShouldCreateTeacherUserWithou expectTeacherAuthorityLookup(); expect(userDetailsDao.hasUsername("BillyBob")).andReturn(false); replay(userDetailsDao); - expect(teacherUserDetailsDao.hasVerificationCode(VERIFICATION_CODE)).andReturn(false); + expect(teacherUserDetailsDao.hasVerificationCode(anyString())).andReturn(false); replay(teacherUserDetailsDao); expect(passwordEncoder.encode(teacherUserDetails.getPassword())).andReturn(ENCODED_PASSWORD); replay(passwordEncoder); From 59997ac5842d857d5429b119ba3fe17defd94855 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Wed, 12 Aug 2026 21:53:22 -0700 Subject: [PATCH 42/42] Fixed tests --- .../portal/dao/user/impl/HibernateUserDaoTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java index 644d29d868..dd9d6dacc5 100644 --- a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java +++ b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java @@ -52,10 +52,10 @@ public class HibernateUserDaoTest extends AbstractTransactionalDbTests { public void setUp() throws Exception { teacher1 = createVerificationTeacherUser("Mrs", "Puff", "MrsPuff", "Mrs. Puff", "boat", "Bikini Bottom", "Water State", "Pacific Ocean", "mrspuff@bikinibottom.com", "Boating School", - Schoollevel.COLLEGE, "1234567890", "abcd1234", true); + Schoollevel.COLLEGE, "1234567890", true); teacher2 = createVerificationTeacherUser("Mr", "Krabs", "MrKrabs", "Mr. Krabs", "restaurant", "Bikini Bottom", "Water State", "Pacific Ocean", "mrkrabs@bikinibottom.com", "Krusty Krab", - Schoollevel.HIGH_SCHOOL, "abcdefghij", "1234abcd", false); + Schoollevel.HIGH_SCHOOL, "abcdefghij", false); student1 = createStudentUser("Spongebob", "Squarepants", "SpongebobS0101", "burger", 1, 1, Gender.MALE); student2 = createStudentUser("Patrick", "Star", "PatrickS0101", "rock", 1, 1, Gender.MALE); @@ -63,8 +63,7 @@ public void setUp() throws Exception { public User createVerificationTeacherUser(String firstName, String lastName, String username, String displayName, String password, String city, String state, String country, String email, - String schoolName, Schoollevel schoolLevel, String googleUserId, String verificationCode, - boolean isVerified) + String schoolName, Schoollevel schoolLevel, String googleUserId, boolean isVerified) throws DuplicateUsernameException { User teacher = createTeacherUser(firstName, lastName, username, displayName, password, city, state, country, email, schoolName, schoolLevel, googleUserId); @@ -267,7 +266,8 @@ public void retrieveTeacherByVerificationCode_WithNonExistingCode_ShouldNotRetur @Test public void retrieveTeacherByVerificationCode_WithExistingCode_ShouldSucceed() { - User user = userDao.retrieveTeacherByVerificationCode("abcd1234"); + TeacherUserDetails tud = (TeacherUserDetails) teacher1.getUserDetails(); + User user = userDao.retrieveTeacherByVerificationCode(tud.getVerificationCode()); assertEquals(teacher1.getId(), user.getId()); }