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 <line> toggled to show "hidden". + * No-op if disabled via hg.conf login.pwdEyeIcon. */ +{ +if (!pwdEyeIconEnabled) + return; +hPrintf( + "<span id=\"%s\" title=\"Show/hide password\" " + "style=\"position:absolute; right:8px; top:50%%; transform:translateY(-50%%); " + "cursor:pointer; user-select:none;\">" + "<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" " + "stroke=\"#666\" stroke-width=\"2\">" + "<path d=\"M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z\"/>" + "<circle cx=\"12\" cy=\"12\" r=\"3\"/>" + "<line id=\"%s\" x1=\"2\" y1=\"2\" x2=\"22\" y2=\"22\" style=\"display:none;\"/>" + "</svg>" + "</span>", 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("<div id=\"loginBox\" class=\"centeredContainer formBox\">" "\n" "<h2>%s</h2>" "\n", brwName); hPrintf( "<h3>Login</h3>" "\n"); if (errMsg && sameString(errMsg, "Your account has been activated.")) hPrintf("<span style='color:green;'>%s</span>\n", errMsg ? errMsg : ""); else hPrintf("<span style='color:red;'>%s</span>\n", errMsg ? errMsg : ""); hPrintf("<form method=post action=\"%s\" name=\"accountLoginForm\" id=\"accountLoginForm\">" "\n" "<div class=\"inputGroup\">" "<label for=\"userName\">Username</label>" "<input type=text name=\"hgLogin_userName\" value=\"%s\" size=\"30\" id=\"userName\">" "</div>" "\n" "<div class=\"inputGroup\">" "<label for=\"password\">Password</label>" + "<span style=\"position:relative; display:inline-block;\">" "<input type=password name=\"hgLogin_password\" value=\"\" size=\"30\" id=\"password\">" + , hgLoginUrl, username); +printPwdEyeIcon("pwdEyeIcon", "pwdEyeSlash"); +hPrintf( + "</span>" "</div>" "\n" "<div class=\"formControls\">" " <input type=\"submit\" name=\"hgLogin.do.displayLogin\" value=\"Login\" class=\"largeButton\">" " <a href=\"%s\">Cancel</a>" "</div>" - , hgLoginUrl, username, getReturnToURL()); + , getReturnToURL()); +if (pwdEyeIconEnabled) + { + printPwdToggleJS(); + jsOnEventById("click", "pwdEyeIcon", "togglePwdVisibility('password','pwdEyeSlash');"); + } cartSaveSession(cart); hPrintf( "</form>" "\n" "\n" "<div id=\"helpBox\">" "<a href=\"%s?hgLogin.do.displayAccHelpPage=1\">Can't access your account?</a><br>" "Need an account? <a href=\"%s?hgLogin.do.signupPage=1\">Sign up</a>.<br>" "</div><!-- END - helpBox -->" "</div><!-- END - loginBox -->" "\n" "\n" "</body>" "</html>", hgLoginUrl, hgLoginUrl); } @@ -799,52 +853,71 @@ "<h3>Change Password</h3>" "\n" "<p> <span style='color:red;'>%s</span> </p>" "\n" "<form method=\"post\" action=\"%s\" name=\"changePasswordForm\" id=\"changePasswordForm\">" "\n" "<div class=\"inputGroup\">" "<label for=\"userName\">Username</label>" "<input type=\"text\" name=\"hgLogin_userName\" size=\"30\" value=\"%s\" id=\"email\">" "</div>" "\n", errMsg ? errMsg : "", hgLoginUrl, cartUsualString(cart, "hgLogin_userName", "")); hPrintf("<div class=\"inputGroup\">" "\n" "<label for=\"currentPw\">Current or Emailed Password</label>" - "<input type=\"password\" name=\"hgLogin_password\" value=\"\" size=\"30\" id=\"currentPw\">" + "<span style=\"position:relative; display:inline-block;\">" + "<input type=\"password\" name=\"hgLogin_password\" value=\"\" size=\"30\" id=\"currentPw\">"); +printPwdEyeIcon("curPwEyeIcon", "curPwEyeSlash"); +hPrintf( + "</span>" "</div>" "\n" "<div class=\"inputGroup\">" "<label for=\"newPw1\">New Password</label>" - "<input type=\"password\" name=\"hgLogin_newPassword1\" value=\"\" size=\"30\" id=\"newPw\">" + "<span style=\"position:relative; display:inline-block;\">" + "<input type=\"password\" name=\"hgLogin_newPassword1\" value=\"\" size=\"30\" id=\"newPw1\">"); +printPwdEyeIcon("newPw1EyeIcon", "newPw1EyeSlash"); +hPrintf( + "</span>" "</div>" "\n" "<div class=\"inputGroup\">" "<label for=\"newPw2\">Re-enter New Password</label>" - "<input type=\"password\" name=\"hgLogin_newPassword2\" value=\"\" size=\"30\" id=\"newPw\">" + "<span style=\"position:relative; display:inline-block;\">" + "<input type=\"password\" name=\"hgLogin_newPassword2\" value=\"\" size=\"30\" id=\"newPw2\">"); +printPwdEyeIcon("newPw2EyeIcon", "newPw2EyeSlash"); +hPrintf( + "</span>" "</div>" "\n" "<div class=\"formControls\">" " <input type=\"submit\" name=\"hgLogin.do.changePassword\" value=\"Change Password\" class=\"largeButton\"> " " <a href=\"%s\">Cancel</a>" "\n" "</div>" "</form>" "\n" "</div><!-- END - changePwBox -->" "\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 @@ "<label for=\"reenterEmail\">Re-enter Email address</label>" "<input type=text name=\"hgLogin_email2\" value=\"%s\" size=\"30\" id=\"emailCheck\">" "</div>\n", cartUsualString(cart, "hgLogin_userName", ""), cartUsualString(cart, "hgLogin_email", ""), cartUsualString(cart, "hgLogin_email2", "")); if (sqlFieldIndex(conn, "gbMembers", "recovEmail") != -1) hPrintf("<div class=\"inputGroup\">" "<label for=\"recovEmail\">Optional Secondary Recovery Email</label>" "<input type=text name=\"hgLogin_recovEmail\" size=\"30\" id=\"recovEmail\">" "</div>" "\n"); hPrintf("<div class=\"inputGroup\">" "<label for=\"password\">Password <small>(must be at least 5 characters)</small></label>" - "<input type=password name=\"hgLogin_password\" value=\"%s\" size=\"30\" id=\"password\">" + "<span style=\"position:relative; display:inline-block;\">" + "<input type=password name=\"hgLogin_password\" value=\"%s\" size=\"30\" id=\"password\">", + cartUsualString(cart, "hgLogin_password", "")); +printPwdEyeIcon("signupPwEyeIcon", "signupPwEyeSlash"); +hPrintf( + "</span>" "</div>" "\n" "<div class=\"inputGroup\">" - "<label for=\"password\">Re-enter Password</label>" - "<input type=password name=\"hgLogin_password2\" value=\"%s\" size=\"30\" id=\"passwordCheck\">" + "<label for=\"passwordCheck\">Re-enter Password</label>" + "<span style=\"position:relative; display:inline-block;\">" + "<input type=password name=\"hgLogin_password2\" value=\"%s\" size=\"30\" id=\"passwordCheck\">", + cartUsualString(cart, "hgLogin_password2", "")); +printPwdEyeIcon("signupPwCheckEyeIcon", "signupPwCheckEyeSlash"); +hPrintf( + "</span>" "\n" "</div>" "\n" "<div class=\"formControls\">" " <input type=\"submit\" name=\"hgLogin.do.signup\" value=\"Sign Up\" class=\"largeButton\"> " " <a href=\"%s\">Cancel</a>" "</div>" "</form>" "</div><!-- END - signUpBox -->", - 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("<h2>%s Sign Out</h2>", brwName); hPrintf( "<p align=\"left\">" "</p>" "<span style='color:red;'></span>" "\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();