80bedbcf180ec262a2b7b9b941f2f80d71c1551b hiram Fri Jul 24 11:34:48 2026 -0700 add password reveal button to hgLogin form refs ##37921 diff --git src/hg/hgLogin/hgLogin.c src/hg/hgLogin/hgLogin.c index c10d1496378..cc67f339de7 100644 --- src/hg/hgLogin/hgLogin.c +++ src/hg/hgLogin/hgLogin.c @@ -42,30 +42,32 @@ /* The excludeVars are not saved to the cart. */ char *excludeVars[] = { "submit", "Submit", "debug", "fixMembers", "update", "hgLogin_password", "hgLogin_password2", "hgLogin_newPassword1", "hgLogin_newPassword2", 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 = FALSE; /* show/hide eye icon on password fields; + set from hg.conf login.pwdEyeIcon in doMiddle() */ /* for earlyBotCheck() function at the beginning of main() */ #define delayFraction 1.0 /* standard penalty is 1.0 for most CGIs */ /* ---- 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)); } char *browserAddr() @@ -302,31 +304,34 @@ } if (*c <= ' ' || *c >= 127) return 0; if (strchr(rfc822_specials, *c)) return 0; } while (*++c); return (count >= 1); } struct dyString *getLoginCookieJS(char *userName, uint idx) /* returns javascript statements that set the cookies associated with * logging in as a particular user */ { struct dyString *result = dyStringNew(1024); struct slName *newCookies = loginLoginUser(userName, idx), *sl; for (sl = newCookies; sl != NULL; sl = sl->next) +{ +verbose(0, "DBG login cookie: '%s'\n", sl->name); dyStringPrintf(result, " document.cookie = '%s';", sl->name); +} return result; } static boolean isValidReturnUrl(char *returnUrl) /* Verify that returnUrl startswith an hg.conf approved set of hosts. */ { struct slName *approvedHosts = slNameListFromComma(cfgOptionDefault(CFG_APPROVED_HOSTS, NULL)); slAddHead(&approvedHosts, slNameNew(hLoginHostCgiBinUrl())); if (approvedHosts) { struct slName *approvedStart; for (approvedStart = approvedHosts; approvedStart != NULL; approvedStart = approvedStart->next) { if (startsWith(approvedStart->name, returnUrl)) return TRUE; @@ -699,62 +704,111 @@ void setupNewAccount(struct sqlConnection *conn, char *email, char *username) /* Set up new user account and send activation mail to user */ { char query[256]; char *token = generateRandomPassword(); char *tokenMD5 = generateTokenMD5(token); sqlSafef(query,sizeof(query), "UPDATE gbMembers SET lastUse=NOW(),emailToken='%s', emailTokenExpires=DATE_ADD(NOW(), INTERVAL 7 DAY), accountActivated='N' WHERE userName='%s'", tokenMD5, username ); sqlUpdate(conn, query); sendActivateMail(email, username, tokenMD5); return; } +void printPwdEyeIcon(char *iconId, char *slashId) +/* print a clickable eye icon, absolutely positioned inside a password + * input's wrapper span; slashId is the toggled to show "hidden". + * No-op if disabled via hg.conf login.pwdEyeIcon. */ +{ +if (!pwdEyeIconEnabled) + return; +hPrintf( + "" + "" + "" + "" + "" + "" + "", iconId, slashId); +} + +void printPwdToggleJS() +/* define the password show/hide toggle function used by all eye icons */ +{ +jsInline( + "function togglePwdVisibility(inputId, slashId) {\n" + " var inp = document.getElementById(inputId);\n" + " var slash = document.getElementById(slashId);\n" + " if (inp.type === 'password') {\n" + " inp.type = 'text';\n" + " slash.style.display = 'inline';\n" + " } else {\n" + " inp.type = 'password';\n" + " slash.style.display = 'none';\n" + " }\n" + "}\n" + ); +} + void displayLoginPage(struct sqlConnection *conn) /* draw the account login page */ { +verbose(0, "DBG hgLogin displayLoginPage\n"); char *username = cartUsualString(cart, "hgLogin_userName", ""); hPrintf("
" "\n" "

%s

" "\n", brwName); hPrintf( "

Login

" "\n"); if (errMsg && sameString(errMsg, "Your account has been activated.")) hPrintf("%s\n", errMsg ? errMsg : ""); else hPrintf("%s\n", errMsg ? errMsg : ""); hPrintf("
" "\n" "
" "" "" "
" "\n" "
" "" + "" "" + , hgLoginUrl, username); +printPwdEyeIcon("pwdEyeIcon", "pwdEyeSlash"); +hPrintf( + "" "
" "\n" "
" " " "  Cancel" "
" - , hgLoginUrl, username, getReturnToURL()); + , getReturnToURL()); +if (pwdEyeIconEnabled) + { + printPwdToggleJS(); + jsOnEventById("click", "pwdEyeIcon", "togglePwdVisibility('password','pwdEyeSlash');"); + } cartSaveSession(cart); hPrintf( "
" "\n" "\n" "
" "Can't access your account?
" "Need an account? Sign up.
" "
" "
" "\n" "\n" "" "", hgLoginUrl, hgLoginUrl); } @@ -799,52 +853,71 @@ "

Change Password

" "\n" "

%s

" "\n" "
" "\n" "
" "" "" "
" "\n", errMsg ? errMsg : "", hgLoginUrl, cartUsualString(cart, "hgLogin_userName", "")); hPrintf("
" "\n" "" - "" + "" + ""); +printPwdEyeIcon("curPwEyeIcon", "curPwEyeSlash"); +hPrintf( + "" "
" "\n" "
" "" - "" + "" + ""); +printPwdEyeIcon("newPw1EyeIcon", "newPw1EyeSlash"); +hPrintf( + "" "
" "\n" "
" "" - "" + "" + ""); +printPwdEyeIcon("newPw2EyeIcon", "newPw2EyeSlash"); +hPrintf( + "" "
" "\n" "
" "   " " Cancel" "\n" "
" "
" "\n" "" "\n", getReturnToURL()); +if (pwdEyeIconEnabled) + { + printPwdToggleJS(); + jsOnEventById("click", "curPwEyeIcon", "togglePwdVisibility('currentPw','curPwEyeSlash');"); + jsOnEventById("click", "newPw1EyeIcon", "togglePwdVisibility('newPw1','newPw1EyeSlash');"); + jsOnEventById("click", "newPw2EyeIcon", "togglePwdVisibility('newPw2','newPw2EyeSlash');"); + } cartSaveSession(cart); } void changePassword(struct sqlConnection *conn) /* process the change password form */ { char query[256]; char *user = cartUsualString(cart, "hgLogin_userName", ""); char *currentPassword = cartUsualString(cart, "hgLogin_password", ""); char *newPassword1 = cartUsualString(cart, "hgLogin_newPassword1", ""); char *newPassword2 = cartUsualString(cart, "hgLogin_newPassword2", ""); char *changeRequired = cartUsualString(cart, "hgLogin_changeRequired", ""); if (!user || sameString(user,"")) { freez(&errMsg); @@ -955,48 +1028,62 @@ "" "" "\n", cartUsualString(cart, "hgLogin_userName", ""), cartUsualString(cart, "hgLogin_email", ""), cartUsualString(cart, "hgLogin_email2", "")); if (sqlFieldIndex(conn, "gbMembers", "recovEmail") != -1) hPrintf("
" "" "" "
" "\n"); hPrintf("
" "" - "" + "" + "", + cartUsualString(cart, "hgLogin_password", "")); +printPwdEyeIcon("signupPwEyeIcon", "signupPwEyeSlash"); +hPrintf( + "" "
" "\n" "
" - "" - "" + "" + "" + "", + cartUsualString(cart, "hgLogin_password2", "")); +printPwdEyeIcon("signupPwCheckEyeIcon", "signupPwCheckEyeSlash"); +hPrintf( + "" "\n" "
" "\n" "
" "   " " Cancel" "
" "" "", - cartUsualString(cart, "hgLogin_password", ""), - cartUsualString(cart, "hgLogin_password2", ""), getReturnToURL()); +if (pwdEyeIconEnabled) + { + printPwdToggleJS(); + jsOnEventById("click", "signupPwEyeIcon", "togglePwdVisibility('password','signupPwEyeSlash');"); + jsOnEventById("click", "signupPwCheckEyeIcon", "togglePwdVisibility('passwordCheck','signupPwCheckEyeSlash');"); + } cartSaveSession(cart); } void signup(struct sqlConnection *conn) /* process the signup form */ { char query[1024]; char *user = cartUsualString(cart, "hgLogin_userName", ""); char *encUserName = cgiEncodeFull(user); if (!user || sameString(user,"")) { freez(&errMsg); errMsg = cloneString("User name cannot be blank."); signupPage(conn); return; @@ -1299,62 +1386,67 @@ cartSetString(cart, "hgLogin_changeRequired", "YES"); changePasswordPage(conn); } else { errMsg = cloneString(incorrectUsernameOrPassword); displayLoginPage(conn); return; } gbMembersFree(&m); } void displayLogoutSuccess() /* display logout success msg, and reset cookie */ { +verbose(0, "DBG hgLogin displayLogoutSuccess\n"); hPrintf("

%s Sign Out

", brwName); hPrintf( "

" "

" "" "\n"); struct dyString *javascript = dyStringNew(1024); struct slName *newCookies = loginLogoutUser(), *sl; for (sl = newCookies; sl != NULL; sl = sl->next) +{ +verbose(0, "DBG logout cookie: '%s'\n", sl->name); dyStringPrintf(javascript, " document.cookie = '%s';", sl->name); +} jsInline(javascript->string); /* return to "returnto" URL */ returnToURL(150); } void doMiddle(struct cart *theCart) /* Write the middle parts of the HTML page. * This routine sets up some globals and then * dispatches to the appropriate page-maker. */ { struct sqlConnection *conn = hConnectCentral(); // on mirrors, try to add the field 'recovEmail' to gbMembers. This may or may not work, depending on their config if (sqlFieldIndex(conn, "gbMembers", "recovEmail") == -1) { autoUpgradeTableAddColumn(conn, "gbMembers", "recovEmail", "varchar(255)", FALSE, "''"); } cart = theCart; safecpy(brwName,sizeof(brwName), browserName()); safecpy(brwAddr,sizeof(brwAddr), browserAddr()); safecpy(signature,sizeof(signature), mailSignature()); safecpy(returnAddr,sizeof(returnAddr), mailReturnAddr()); +pwdEyeIconEnabled = cfgOptionBooleanDefault(CFG_LOGIN_PWD_EYE_ICON, FALSE); if (cartVarExists(cart, "hgLogin.do.changePasswordPage")) changePasswordPage(conn); else if (cartVarExists(cart, "hgLogin.do.changePassword")) changePassword(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();