d33d0e1c19117a0347dbadb99a77e9f12483c735 max Tue Sep 1 06:56:09 2026 -0700 hgLogin: let users set or change their recovery email address, refs #38197 Adds a page where a signed-in user can set or change the recovery address on their account, offered in the account menu next to "Change email" -- both in the top right blue bar popup and on the session page. It is off by default: set login.recovEmailChange=on in hg.conf to offer it. The page also needs login.cookieSalt, working outbound mail and the recovEmailVerified column, and stays hidden where any of those is missing. The new address is confirmed by mail before it takes effect, so whatever is on the account keeps working until the link is opened and a typo costs the user nothing. An account that has a password must supply it, since a confirmed recovery address can sign in. Once the address does change, the account's main address is told, the same notice that a change of the main address already sends. One signature now covers both the address given at signup and a later change, so there is a single confirmation path rather than two. diff --git src/hg/hgLogin/hgLogin.c src/hg/hgLogin/hgLogin.c index fcec1851abd..ca8871633b2 100644 --- src/hg/hgLogin/hgLogin.c +++ src/hg/hgLogin/hgLogin.c @@ -36,55 +36,58 @@ #include "hCommon.h" #include "botDelay.h" #include "errCatch.h" #define EMAILSEP ";" /* ---- Global variables. ---- */ char msg[4096] = ""; char *incorrectUsernameOrPassword="The username or password you entered is incorrect."; char *incorrectUsername="The username you entered is incorrect."; /* The excludeVars are not saved to the cart. */ char *excludeVars[] = { "submit", "Submit", "debug", "fixMembers", "update", "hgLogin_password", "hgLogin_password2", "hgLogin_newPassword1", "hgLogin_newPassword2", "hgLogin_newEmail1", "hgLogin_newEmail2", "hgLogin_curPassword", "code", "state", "provider", "user", "token", - "newEmail", "recovEmail", "exp", "sig", NULL }; + "newEmail", "recovEmail", "exp", "sig", + "hgLogin_newRecovEmail1", "hgLogin_newRecovEmail2", NULL }; struct cart *cart; /* This holds cgi and other variables between clicks. */ char *database; /* Name of genome database - hg15, mm3, or the like. */ struct hash *oldCart; /* Old cart hash. */ char *errMsg = NULL; /* Error message to show user when form data rejected */ char brwName[64]; char brwAddr[256]; char signature[256]; char returnAddr[256]; char *hgLoginUrl = NULL; /* full absolute URL to hgLogin as seen from browser, e.g. http://genome.ucsc.edu/cgi-bin/hgLogin. Can be a relative URL /cgi-bin/hgLogin if hg.conf login.relativeLink is on. */ boolean pwdEyeIconEnabled = TRUE; /* show/hide eye icon on password fields; set from hg.conf login.pwdEyeIcon in doMiddle() */ boolean recovEmailVerifyOk = FALSE; /* TRUE when gbMembers has the recovEmailVerified column, so a confirmed recovery address can be told apart from one that was merely typed into the signup form. Set in doMiddle() after the auto-upgrade; FALSE on a mirror where the ALTER failed. */ /* for earlyBotCheck() function at the beginning of main() */ #define delayFraction 1.0 /* standard penalty is 1.0 for most CGIs */ /* Forward declarations for functions used before their definitions. */ static void printSocialButtons(boolean dividerAbove, boolean dividerBelow, char *action); static void printEmailLinkButton(); static boolean emailLinkEnabled(); +static boolean recovEmailChangeEnabled(); +void changeRecovEmailPage(struct sqlConnection *conn); static void printUsernameNote(); void emailLinkPage(struct sqlConnection *conn); void displayLoginPage(struct sqlConnection *conn); void displayAccHelpPage(struct sqlConnection *conn); void completeAccountPage(struct sqlConnection *conn); void sendEmailLink(struct sqlConnection *conn); /* ---- Global helper functions ---- */ char *browserName() /* Return the browser name like 'UCSC Genome Browser' */ { if isEmpty(cfgOption(CFG_LOGIN_BROWSER_NAME)) return cloneString("NULL_browserName"); else return cloneString(cfgOption(CFG_LOGIN_BROWSER_NAME)); @@ -1102,83 +1105,107 @@ * change was not theirs and can ask us to undo it. This is the notice that protects the current * owner -- confirming the new address only proves the new mailbox is reachable. */ { char subject[256]; safef(subject, sizeof(subject), "Your %s email address was changed", brwName); char *remoteAddr = getenv("REMOTE_ADDR"); char message[4096]; safef(message, sizeof(message), "The email address on the %s account \"%s\" was just changed to %s (request from IP address " "%s).\n\nIf you made this change, nothing more is needed. If you did NOT, please reply to " "this message right away so we can help you secure the account.\n\n%s\n%s", brwName, user, newEmail, emptyForNull(remoteAddr), signature, returnAddr); sendActMailOut(oldEmail, subject, message); } -static char *recovEmailSig(char *user, char *recovEmail, char *verified, char *expStr) -/* HMAC-MD5 over a pending recovery-address confirmation, keyed by the secret login.cookieSalt. - * It goes in the link mailed to the address, so that opening the link -- and only opening it -- - * marks the address confirmed, proving the mailbox really does reach the person who claimed it. - * verified is the account's recovEmailVerified value when the link was minted; because - * confirmRecovEmail recomputes the signature from the value currently on the account, a link - * stops validating once it has been used, so each link works exactly once. Result is allocd. */ +static char *recovEmailSig(char *user, char *newRecov, char *curRecov, char *curVerified, + char *expStr) +/* HMAC-MD5 over a pending recovery address, keyed by the secret login.cookieSalt. It goes in + * the link mailed to that address, so that opening the link -- and only opening it -- puts the + * address on the account and marks it confirmed, proving the mailbox really does reach the + * person who claimed it. One signature serves both cases: the address given at signup (where + * newRecov is already stored, unconfirmed) and a later change (where it is not stored at all + * until the link is opened, so a typo cannot cost the user a working recovery address). + * curRecov and curVerified are the account's stored address and flag when the link was minted; + * because confirmRecovEmail recomputes the signature from what is on the account now, a link + * stops validating once it has been used, so each link works exactly once and a stale link + * cannot quietly undo a newer change. Result is allocd. */ { char *salt = cfgOption(CFG_LOGIN_COOKIE_SALT); if (isEmpty(salt)) errAbort("Confirming a recovery email address requires %s in hg.conf, set to a secret random " "string. Without a secret we cannot sign the confirmation link.", CFG_LOGIN_COOKIE_SALT); char buf[1024]; -safef(buf, sizeof(buf), "recovEmail|%s|%s|%s|%s", - emptyForNull(user), emptyForNull(recovEmail), emptyForNull(verified), emptyForNull(expStr)); +safef(buf, sizeof(buf), "recovEmail|%s|%s|%s|%s|%s", + emptyForNull(user), emptyForNull(newRecov), emptyForNull(curRecov), + emptyForNull(curVerified), emptyForNull(expStr)); return hmacMd5(salt, buf); } -static void sendRecovEmailConfirmMail(char *recovEmail, char *user) -/* Email a one-time link to recovEmail that, when opened, marks it confirmed for account user. - * Until that happens the address counts for nothing: it cannot sign anyone in and it gets no - * copy of a password reset. The link lasts a week, like the account activation mail, because a - * recovery mailbox is often not the one its owner reads every day. */ +static void sendRecovEmailConfirmMail(char *recovEmail, char *user, char *curRecov, + char *curVerified) +/* Email a one-time link to recovEmail that, when opened, puts it on account user as a confirmed + * recovery address. Until that happens the address counts for nothing: it cannot sign anyone in + * and it gets no copy of a password reset. The link lasts a week, like the account activation + * mail, because a recovery mailbox is often not the one its owner reads every day. */ { char expStr[32]; safef(expStr, sizeof(expStr), "%ld", clock1() + 7*24*3600); // link good for a week -char *sig = recovEmailSig(user, recovEmail, "N", expStr); +char *sig = recovEmailSig(user, recovEmail, curRecov, curVerified, expStr); char url[1024]; safef(url, sizeof(url), "%s?hgLogin.do.confirmRecovEmail=1&user=%s&recovEmail=%s&exp=%s&sig=%s", hgLoginUrl, cgiEncode(user), cgiEncode(recovEmail), expStr, sig); char subject[256]; safef(subject, sizeof(subject), "Confirm your %s recovery email address", brwName); char *remoteAddr = getenv("REMOTE_ADDR"); char message[4096]; safef(message, sizeof(message), "Someone (probably you, from IP address %s) gave this address as the recovery email address " "for the %s account \"%s\".\nTo confirm that this mailbox is yours, open this link in your " "browser:\n\n%s\n\nThe link works once and expires in seven days. Until it is opened, this " "address cannot be used to sign in to that account and will not receive a password reset.\n\n" "If this is *not* you, do not open the link: someone typed your address by mistake, and " "ignoring this message is all it takes to keep them from using it.\n\n%s\n%s", emptyForNull(remoteAddr), brwName, user, url, signature, returnAddr); /* Not sendActMailOut(): that exits the CGI when the address will not take mail, which would * end the signup response after the account has already been created and its activation mail * sent. A recovery address is optional and easy to mistype, so a bad one must not derail * signing up -- the address simply stays unconfirmed, which is the safe state. */ if (mailViaPipeBounce(recovEmail, subject, message, returnAddr) == -1) fprintf(stderr, "hgLogin: could not mail recovery-address confirmation to %s for account " "%s\n", recovEmail, user); freeMem(sig); } +static void sendRecovEmailChangeAlertMail(char *email, char *user, char *newRecov) +/* Tell the account's main address that its recovery address just changed, so its owner finds + * out if the change was not theirs. A confirmed recovery address can sign in to the account, + * so moving it deserves the same notice as changing the main address itself. */ +{ +char subject[256]; +safef(subject, sizeof(subject), "Your %s recovery email address was changed", brwName); +char *remoteAddr = getenv("REMOTE_ADDR"); +char message[4096]; +safef(message, sizeof(message), + "The recovery email address on the %s account \"%s\" was just changed to %s (request from " + "IP address %s).\n\nIf you made this change, nothing more is needed. If you did NOT, please " + "reply to this message right away so we can help you secure the account.\n\n%s\n%s", + brwName, user, newRecov, emptyForNull(remoteAddr), signature, returnAddr); +sendActMailOut(email, subject, message); +} + void changeEmailPage(struct sqlConnection *conn) /* Draw the change-email page for the currently logged-in user. The account is taken from * the validated login cookie (wikiLinkUserName), never from a form field, so a user can only * change their own email. Where the account has a password we also ask for it here, so a * borrowed login cookie alone cannot change the address (and from there take over the account * via password recovery). Social-login accounts have no password and are asked for none; for * them the new address is instead confirmed by email before it takes effect (see changeEmail). */ { if (!emailLinkEnabled()) { displayLoginPage(conn); return; } char *user = wikiLinkUserName(); if (isEmpty(user)) @@ -1352,67 +1379,239 @@ if (!recovEmailVerifyOk || isEmpty(cfgOption(CFG_LOGIN_COOKIE_SALT))) { /* No column to record the answer in, or no secret to check the signature against, so the * link cannot have come from us. Checked before recovEmailSig(), which aborts without a * secret: a link is only ever minted where one is configured, so reaching here means a * hand-made URL and it deserves the ordinary refusal, not an error page. */ freez(&errMsg); errMsg = cloneString("This confirmation link is not valid."); displayLoginPage(conn); return; } char *user = cgiUsualString("user", ""); char *recovEmail = cgiUsualString("recovEmail", ""); char *expStr = cgiUsualString("exp", ""); char *sig = cgiUsualString("sig", ""); -/* Recompute the signature over the flag currently on the account. Once confirmed the flag is - * 'Y', so re-opening the same link no longer matches: the link works exactly once. */ +/* Recompute the signature over the address and flag currently on the account. Applying the + * link changes both, so re-opening it no longer matches: the link works exactly once. */ char query[1024]; sqlSafef(query, sizeof(query), - "SELECT recovEmailVerified FROM gbMembers WHERE userName='%s' AND recovEmail='%s'", - user, recovEmail); -char *verified = sqlQuickString(conn, query); -char *expected = recovEmailSig(user, recovEmail, emptyForNull(verified), expStr); + "SELECT recovEmail, recovEmailVerified FROM gbMembers WHERE userName='%s'", user); +struct sqlResult *sr = sqlGetResult(conn, query); +char **row = sqlNextRow(sr); +char *curRecov = (row != NULL) ? cloneString(emptyForNull(row[0])) : NULL; +char *curVerified = (row != NULL) ? cloneString(emptyForNull(row[1])) : NULL; +sqlFreeResult(&sr); +char *expected = recovEmailSig(user, recovEmail, emptyForNull(curRecov), + emptyForNull(curVerified), expStr); boolean sigOk = isNotEmpty(sig) && sameString(sig, expected); freeMem(expected); if (!sigOk || isEmpty(user) || spc_email_isvalid(recovEmail) == 0) { freez(&errMsg); errMsg = cloneString("This confirmation link is not valid or has already been used."); displayLoginPage(conn); return; } if (clock1() > atol(expStr)) { freez(&errMsg); errMsg = cloneString("This confirmation link has expired."); displayLoginPage(conn); return; } +/* Set the address as well as the flag: for a signup this rewrites the same value, and for a + * change this is the point at which the new address takes effect. */ sqlSafef(query, sizeof(query), - "UPDATE gbMembers SET recovEmailVerified='Y', lastUse=NOW() " - "WHERE userName='%s' AND recovEmail='%s'", user, recovEmail); + "UPDATE gbMembers SET recovEmail='%s', recovEmailVerified='Y', lastUse=NOW() " + "WHERE userName='%s'", recovEmail, user); sqlUpdate(conn, query); +/* A change, not a signup confirmation: tell the main address, so a hijack gets noticed. */ +if (isNotEmpty(curRecov) && differentWord(curRecov, recovEmail)) + { + sqlSafef(query, sizeof(query), "SELECT email FROM gbMembers WHERE userName='%s'", user); + char *email = sqlQuickString(conn, query); + if (isNotEmpty(email)) + sendRecovEmailChangeAlertMail(email, user, recovEmail); + } char *encEmail = htmlEncode(recovEmail); hPrintf("

%s

", brwName); hPrintf("

Your recovery email address has been confirmed.

"); hPrintf("

%s can now be used to sign in to your account and to recover your " "password.

", encEmail); freeMem(encEmail); returnToURL(1500); } +void changeRecovEmailPage(struct sqlConnection *conn) +/* Draw the set/change-recovery-address page for the currently logged-in user. As on the + * change-email page the account comes from the validated login cookie, never from a form + * field, and an account that has a password must supply it: a confirmed recovery address can + * sign in to the account, so a borrowed login cookie alone must not be able to point it + * somewhere new. */ +{ +if (!recovEmailChangeEnabled()) + { + displayLoginPage(conn); + return; + } +char *user = wikiLinkUserName(); +if (isEmpty(user)) + { + freez(&errMsg); + errMsg = cloneString("Please log in first to change your recovery email address."); + displayLoginPage(conn); + return; + } +char query[512]; +sqlSafef(query, sizeof(query), + "SELECT recovEmail, recovEmailVerified FROM gbMembers WHERE userName='%s'", user); +struct sqlResult *sr = sqlGetResult(conn, query); +char **row = sqlNextRow(sr); +char *curRecov = (row != NULL) ? cloneString(emptyForNull(row[0])) : cloneString(""); +boolean curConfirmed = (row != NULL) && sameWord(emptyForNull(row[1]), "Y"); +sqlFreeResult(&sr); +sqlSafef(query, sizeof(query), "SELECT password FROM gbMembers WHERE userName='%s'", user); +boolean hasPassword = isNotEmpty(sqlQuickString(conn, query)); +char *encUser = htmlEncode(user); +char *encCurRecov = htmlEncode(isNotEmpty(curRecov) ? curRecov : "(none)"); + +hPrintf("
" + "

%s

", brwName); +hPrintf("

Recovery Email

"); +hPrintf("

%s

", errMsg ? errMsg : ""); +hPrintf("
", hgLoginUrl); +hPrintf("

Signed in as %s.
Current recovery email address: %s%s

", + encUser, encCurRecov, + (isNotEmpty(curRecov) && !curConfirmed) ? " (waiting to be confirmed)" : ""); +hPrintf("

A second address you can use to get back into your " + "account: it can sign you in, including with the Google and ORCID buttons, and it receives " + "a copy of a password reset. We email it a link to confirm it, and it does nothing until " + "you open that link. Your current address keeps working until then.

"); +freeMem(encUser); +freeMem(encCurRecov); +if (hasPassword) + hPrintf("
" + "" + "" + "
"); +hPrintf("
" + "" + "" + "
"); +hPrintf("
" + "" + "" + "
"); +hPrintf("
" + "" + "  Cancel" + "
", getReturnToUrlForAttr()); +cartSaveSession(cart); +} + +void changeRecovEmail(struct sqlConnection *conn) +/* Process the set/change-recovery-address form for the currently logged-in user. */ +{ +if (!recovEmailChangeEnabled()) + { + displayLoginPage(conn); + return; + } +char *user = wikiLinkUserName(); +if (isEmpty(user)) + { + freez(&errMsg); + errMsg = cloneString("Please log in first to change your recovery email address."); + displayLoginPage(conn); + return; + } +char *recov1 = cartUsualString(cart, "hgLogin_newRecovEmail1", ""); +char *recov2 = cartUsualString(cart, "hgLogin_newRecovEmail2", ""); +if (isEmpty(recov1) || spc_email_isvalid(recov1) == 0) + { + freez(&errMsg); + errMsg = cloneString("Please enter a valid email address."); + changeRecovEmailPage(conn); + return; + } +if (differentString(recov1, recov2)) + { + freez(&errMsg); + errMsg = cloneString("Email addresses do not match."); + changeRecovEmailPage(conn); + return; + } +char query[512]; +/* Re-authenticate where we can: if the account has a password, require the current one. */ +sqlSafef(query, sizeof(query), "SELECT password FROM gbMembers WHERE userName='%s'", user); +char *curPwd = sqlQuickString(conn, query); +if (isNotEmpty(curPwd)) + { + char *given = cartUsualString(cart, "hgLogin_curPassword", ""); + if (isEmpty(given) || !checkPwd(given, curPwd)) + { + freez(&errMsg); + errMsg = cloneString("Please enter your current password."); + changeRecovEmailPage(conn); + return; + } + } +sqlSafef(query, sizeof(query), + "SELECT recovEmail, recovEmailVerified, email FROM gbMembers WHERE userName='%s'", user); +struct sqlResult *sr = sqlGetResult(conn, query); +char **row = sqlNextRow(sr); +char *curRecov = (row != NULL) ? cloneString(emptyForNull(row[0])) : cloneString(""); +char *curVerified = (row != NULL) ? cloneString(emptyForNull(row[1])) : cloneString(""); +char *curEmail = (row != NULL) ? cloneString(emptyForNull(row[2])) : cloneString(""); +sqlFreeResult(&sr); +/* An address the account already uses needs no second proof. */ +if (sameWord(recov1, curEmail)) + { + freez(&errMsg); + errMsg = cloneString("That is already the main address on this account."); + changeRecovEmailPage(conn); + return; + } +if (sameWord(recov1, curRecov) && sameWord(curVerified, "Y")) + { + freez(&errMsg); + errMsg = cloneString("That is already your confirmed recovery email address."); + changeRecovEmailPage(conn); + return; + } +/* Do not store the address yet: mail a one-time confirmation link and put it on the account + * only when that link is opened (see confirmRecovEmail). Until then the address the user has + * now keeps working, so a typo here costs them nothing. */ +sendRecovEmailConfirmMail(recov1, user, curRecov, curVerified); +cartRemove(cart, "hgLogin_newRecovEmail1"); +cartRemove(cart, "hgLogin_newRecovEmail2"); +cartRemove(cart, "hgLogin_curPassword"); +char *encRecov = htmlEncode(recov1); +hPrintf("

%s

", brwName); +hPrintf("

Almost done. Please check your email

"); +hPrintf("

We sent a confirmation link to %s. Open the link in that message to finish " + "setting your recovery email address. The link works once and expires in seven days. Until " + "then nothing about your account changes.

", encRecov); +freeMem(encRecov); +returnToURL(3000); +} + void signupPage(struct sqlConnection *conn) /* draw the signup page */ { hPrintf("
" "

%s

", brwName); hPrintf( "

Signing up enables you to save multiple sessions, share your sessions with others via short and stable session links and manage previously uploaded custom tracks and track hubs.

" "\n"); hPrintf("

Already have an account? " "Go to the login page.

", hgLoginUrl); printSocialButtons(FALSE, TRUE, "Sign up"); hPrintf("

Sign Up Using Email

" "
" "%s" "\n", hgLoginUrl, errMsg ? errMsg : ""); @@ -1621,31 +1820,31 @@ sqlDyStringPrintf(query2, ",recovEmail='%s'", recovEmail); if (confirmRecov) sqlDyStringPrintf(query2, ",recovEmailVerified='N'"); sqlUpdate(conn, dyStringContents(query2)); dyStringFree(&query2); if (sameWord(returnAddr, "NOEMAIL")) { redirectToLoginPage("hgLogin.do.displayLoginPage=1"); return; } setupNewAccount(conn, email, user); if (confirmRecov) - sendRecovEmailConfirmMail(recovEmail, user); + sendRecovEmailConfirmMail(recovEmail, user, recovEmail, "N"); /* send out activate code mail, and display the mail confirmation box */ cartRemove(cart, "hgLogin_email"); cartRemove(cart, "hgLogin_email2"); cartRemove(cart, "hgLogin_userName"); cartRemove(cart, "user"); cartRemove(cart, "token"); redirectToLoginPage("hgLogin.do.displayActMailSuccess=1"); } void accountHelp(struct sqlConnection *conn) /* email user username(s) or new password */ { char query[1024]; // room for an address-matching clause holding a long address twice char *email = cartUsualString(cart, "hgLogin_email", ""); char *username = cartUsualString(cart, "hgLogin_userName", ""); @@ -1859,30 +2058,43 @@ hPrintf("" "%s with %s", hgLoginUrl, cgiEncode(prov->name), action, oauthProviderLabel(prov->name)); if (dividerBelow) hPrintf("
or
"); hPrintf("
"); } static boolean emailLinkEnabled() /* Return TRUE if passwordless email-link login is turned on in hg.conf. It needs working * outbound email, so it is off unless the admin explicitly enables it with login.emailLink=on. */ { return cfgOptionBooleanDefault(CFG_LOGIN_EMAIL_LINK, FALSE); } +static boolean recovEmailChangeEnabled() +/* Return TRUE if users may set or change their own recovery email address. Needs working + * outbound mail to confirm the new address, a login.cookieSalt to sign the confirmation link, + * and the recovEmailVerified column to record the answer in, so all three are required on top + * of the admin turning it on with login.recovEmailChange=on in hg.conf. */ +{ +if (!cfgOptionBooleanDefault(CFG_LOGIN_RECOV_EMAIL_CHANGE, FALSE)) + return FALSE; +if (!recovEmailVerifyOk || isEmpty(cfgOption(CFG_LOGIN_COOKIE_SALT))) + return FALSE; +return !sameWord(returnAddr, "NOEMAIL"); +} + static void printEmailLinkButton() /* Print a grey button that opens the passwordless email-link login page, if enabled. */ { if (!emailLinkEnabled()) return; hPrintf("" "Email me a sign-in link", hgLoginUrl); } static void printUsernameNote() /* Print a short hint, shown wherever a new username is chosen, explaining that the username * shows up in every short link the user later creates, so it should be short and easy to type. */ { hPrintf("

Note: your username becomes part of every short link " "you create later (for example %s/s/username/MySession), so choose " @@ -2782,30 +2994,34 @@ sendEmailLink(conn); else if (cartVarExists(cart, "hgLogin.do.emailLogin")) emailLogin(conn); else if (cartVarExists(cart, "hgLogin.do.changePasswordPage")) changePasswordPage(conn); else if (cartVarExists(cart, "hgLogin.do.changePassword")) changePassword(conn); else if (cartVarExists(cart, "hgLogin.do.changeEmailPage")) changeEmailPage(conn); else if (cartVarExists(cart, "hgLogin.do.changeEmail")) changeEmail(conn); else if (cartVarExists(cart, "hgLogin.do.confirmChangeEmail")) confirmChangeEmail(conn); else if (cartVarExists(cart, "hgLogin.do.confirmRecovEmail")) confirmRecovEmail(conn); +else if (cartVarExists(cart, "hgLogin.do.changeRecovEmailPage")) + changeRecovEmailPage(conn); +else if (cartVarExists(cart, "hgLogin.do.changeRecovEmail")) + changeRecovEmail(conn); else if (cartVarExists(cart, "hgLogin.do.displayAccHelpPage")) displayAccHelpPage(conn); else if (cartVarExists(cart, "hgLogin.do.accountHelp")) accountHelp(conn); else if (cartVarExists(cart, "hgLogin.do.activateAccount")) activateAccount(conn); else if (cartVarExists(cart, "hgLogin.do.displayActMailSuccess")) displayActMailSuccess(); else if (cartVarExists(cart, "hgLogin.do.displayMailSuccess")) displayMailSuccess(); else if (cartVarExists(cart, "hgLogin.do.displayMailSuccessPwd")) displayMailSuccessPwd(); else if (cartVarExists(cart, "hgLogin.do.displayLoginPage")) displayLoginPage(conn); else if (cartVarExists(cart, "hgLogin.do.displayLogin"))