Skip to content

Commit

Permalink
fixed email verification flow when changing email address on profile …
Browse files Browse the repository at this point in the history
…page
  • Loading branch information
albogdano committed Feb 6, 2024
1 parent e5c7d19 commit 8dfcaaf
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 19 deletions.
36 changes: 26 additions & 10 deletions src/main/java/com/erudika/scoold/controllers/ProfileController.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public String edit(@PathVariable(required = false) String id, @RequestParam(requ
HttpServletRequest req, Model model) {
Profile authUser = utils.getAuthUser(req);
Profile showUser = getProfileForEditing(id, authUser);
String queryString = "";
if (showUser != null) {
boolean updateProfile = false;
if (!StringUtils.equals(showUser.getLocation(), location)) {
Expand All @@ -206,6 +207,7 @@ public String edit(@PathVariable(required = false) String id, @RequestParam(requ
changeEmail(showUser.getUser(), showUser, email);
} else {
updateProfile = updateProfile || sendConfirmationEmail(showUser.getUser(), showUser, email, req);
queryString = updateProfile ? "?code=signin.verify.start&success=true" : "?code=9&error=true";
}
}

Expand All @@ -216,7 +218,7 @@ public String edit(@PathVariable(required = false) String id, @RequestParam(requ
}
model.addAttribute("user", showUser);
}
return "redirect:" + PROFILELINK + (isMyid(authUser, id) ? "" : "/" + id);
return "redirect:" + PROFILELINK + (isMyid(authUser, id) ? "" : "/" + id) + queryString;
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -322,18 +324,28 @@ public ResponseEntity<?> toggleBadge(@PathVariable String id, @PathVariable Stri
@GetMapping(path = "/confirm-email")
public String confirmEmail(@RequestParam(name = "id", required = false) String id,
@RequestParam(name = "token", required = false) String token,
@RequestParam(name = "token2", required = false) String token2,
HttpServletRequest req, Model model) {
Profile authUser = utils.getAuthUser(req);
if (authUser == null || !CONF.passwordAuthEnabled()) {
return "redirect:" + SIGNINLINK + "?returnto=" + PROFILELINK;
}
if (id != null && token != null) {
if (id != null && (!StringUtils.isBlank(token) || !StringUtils.isBlank(token2))) {
User u = (User) pc.read(id);
Sysprop s = pc.read(u.getIdentifier());
if (s != null && token.equals(s.getProperty(Config._EMAIL_TOKEN))) {
if (s != null && StringUtils.equals(token, (String) s.getProperty(Config._EMAIL_TOKEN))) {
s.addProperty(Config._EMAIL_TOKEN, "");
pc.update(s);
changeEmail(u, authUser, authUser.getPendingEmail());
if (StringUtils.isBlank((String) s.getProperty(Config._EMAIL_TOKEN + "2"))) {
return changeEmail(u, authUser, authUser.getPendingEmail());
}
return "redirect:" + PROFILELINK + "?code=signin.verify.done&success=true";
} else if (s != null && StringUtils.equals(token2, (String) s.getProperty(Config._EMAIL_TOKEN + "2"))) {
s.addProperty(Config._EMAIL_TOKEN + "2", "");
pc.update(s);
if (StringUtils.isBlank((String) s.getProperty(Config._EMAIL_TOKEN))) {
return changeEmail(u, authUser, authUser.getPendingEmail());
}
return "redirect:" + PROFILELINK + "?code=signin.verify.done&success=true";
} else {
return "redirect:" + SIGNINLINK;
Expand All @@ -354,7 +366,7 @@ public String retryChangeEmail(HttpServletRequest req, Model model) {
return "redirect:" + PROFILELINK + "?code=7&error=true";
}
}
return "redirect:" + PROFILELINK + "?code=signin.verify.text&success=true";
return "redirect:" + PROFILELINK + "?code=signin.verify.start&success=true";
}

@PostMapping(path = "/cancel-change-email")
Expand All @@ -381,7 +393,7 @@ public String toggleEditorRole(HttpServletRequest req, Model model) {
return "redirect:" + HttpUtils.getBackToUrl(req, true);
}

private void changeEmail(User u, Profile showUser, String email) {
private String changeEmail(User u, Profile showUser, String email) {
boolean approvedDomain = utils.isEmailDomainApproved(email);
if (approvedDomain && canChangeEmail(u, email)) {
Sysprop s = pc.read(u.getEmail());
Expand All @@ -392,27 +404,31 @@ private void changeEmail(User u, Profile showUser, String email) {
u.setEmail(email);
showUser.setPendingEmail("");
pc.updateAll(List.of(u, showUser));
return "redirect:" + PROFILELINK + "?code=signin.verify.changed&success=true";
} else {
logger.info("Failed to change email for user {} - email {} has already been taken.", u.getId(), email);
return "redirect:" + PROFILELINK + "?code=1&error=true";
}
}
return "redirect:" + PROFILELINK + "?code=9&error=true";
}

private boolean sendConfirmationEmail(User user, Profile showUser, String email, HttpServletRequest req) {
if (pc.read(email) == null) {
showUser.setPendingEmail(email);
if (pc.read(email) == null && pc.findTerms(Utils.type(User.class), Map.of(Config._EMAIL, email), true).isEmpty()) {
Sysprop ident = pc.read(user.getEmail());
if (ident != null) {
if (!ident.hasProperty("confirmationTimestamp") || Utils.timestamp() >
((long) ident.getProperty("confirmationTimestamp") + TimeUnit.HOURS.toMillis(6))) {
utils.sendVerificationEmail(ident, PROFILELINK + "/confirm-email", req);
showUser.setPendingEmail(email);
utils.sendVerificationEmail(ident, email, PROFILELINK + "/confirm-email", req);
return true;
} else {
logger.warn("Failed to send email confirmation to '{}' - this can only be done once every 6h.", email);
}
}
} else {
logger.info("Failed to send confirmation email to user {} - email {} has already been taken.", user.getId(), email);
}
logger.info("Failed to send confirmation email to user {} - email {} has already been taken.", user.getId(), email);
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public String resend(@RequestParam String email, HttpServletRequest req, HttpSer
((long) ident.getProperty("confirmationTimestamp") + TimeUnit.HOURS.toMillis(6))) {
User u = pc.read(Utils.type(User.class), ident.getCreatorid());
if (u != null && !u.getActive()) {
utils.sendVerificationEmail(ident, "", req);
utils.sendVerificationEmail(ident, "", "", req);
}
} else {
logger.warn("Failed to send email confirmation to '{}' - this can only be done once every 6h.", email);
Expand Down
29 changes: 21 additions & 8 deletions src/main/java/com/erudika/scoold/utils/ScooldUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,27 +435,40 @@ public void sendWelcomeEmail(User user, boolean verifyEmail, HttpServletRequest
}
}

public void sendVerificationEmail(Sysprop identifier, String redirectUrl, HttpServletRequest req) {
public void sendVerificationEmail(Sysprop identifier, String newEmail, String redirectUrl, HttpServletRequest req) {
if (identifier != null) {
Map<String, Object> model = new HashMap<String, Object>();
Map<String, String> lang = getLang(req);
String subject = CONF.appName() + " - " + lang.get("msgcode.6");
String subject = CONF.appName() + " - " + (StringUtils.isBlank(newEmail) ? lang.get("msgcode.6") :
lang.get("signin.verify.change") + newEmail);
String body = getDefaultEmailSignature(CONF.emailsWelcomeText3(lang));
redirectUrl = StringUtils.isBlank(redirectUrl) ? SIGNINLINK + "/register" : redirectUrl;

String token = Utils.base64encURL(Utils.generateSecurityToken().getBytes());
identifier.addProperty(Config._EMAIL_TOKEN, token);
String token1 = Utils.base64encURL(Utils.generateSecurityToken().getBytes());
String token2 = Utils.base64encURL(Utils.generateSecurityToken().getBytes());
identifier.addProperty(Config._EMAIL_TOKEN, token1);
identifier.addProperty(Config._EMAIL_TOKEN + "2", token2);
identifier.addProperty("confirmationTimestamp", Utils.timestamp());
pc.update(identifier);
token = CONF.serverUrl() + CONF.serverContextPath() + redirectUrl + "?id=" +
identifier.getCreatorid() + "&token=" + token;
body = "<b><a href=\"" + token + "\">" + lang.get("signin.welcome.verify") + "</a></b><br><br>" + body;
token1 = CONF.serverUrl() + CONF.serverContextPath() + redirectUrl + "?id=" +
identifier.getCreatorid() + "&token=" + token1;
token2 = CONF.serverUrl() + CONF.serverContextPath() + redirectUrl + "?id=" +
identifier.getCreatorid() + "&token2=" + token2;

body = "<b><a href=\"{0}\">" + (StringUtils.isBlank(newEmail) ? lang.get("signin.welcome.verify") :
lang.get("signin.verify.change")) + " " + newEmail + "</a></b><br><br>" + body;

model.put("subject", escapeHtml(subject));
model.put("logourl", getSmallLogoUrl());
model.put("heading", lang.get("hello"));
model.put("body", body);
model.put("body", Utils.formatMessage(body, token1));

emailer.sendEmail(Arrays.asList(identifier.getId()), subject, compileEmailTemplate(model));

if (!StringUtils.isBlank(newEmail)) {
model.put("body", Utils.formatMessage(body, token2));
emailer.sendEmail(Arrays.asList(newEmail), subject, compileEmailTemplate(model));
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/lang_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ signin.welcome.title = Hello {0},
signin.welcome.body1 = You are now part of {0} - a friendly Q&A community where you can get answers to your questions.
signin.welcome.body2 = To get started, simply navigate to the "Ask question" page and ask a question. You can also help people by answering their questions and you will get reputation points.
signin.welcome.verify = Verify your email here!
signin.verify.start = Follow both confirmation links that were sent to your old and new email addresses.
signin.verify.change = Confirm email change to:
signin.verify.changed = Your email address was successfully changed.
signin.verify.text = Done! Check your email and follow the link to verify it.
signin.verify.done = Email verified!
signin.facebook = Continue with Facebook
Expand Down

0 comments on commit 8dfcaaf

Please sign in to comment.