e81efb1074d9786436ed8cdb626fc3bdeb6ac14c
max
Thu Aug 6 09:20:30 2026 -0700
hgLogin: fix the social-login/email-link code review issues from #38008
#Preview2 week - bugs introduced now will need a build patch to fix
Brian, thanks for the thorough review - every one of these was real. Here is what
I changed for the six items that stayed on the ticket (the pre-existing XSS and the
site-wide httpsCertCheck default went to #38011 and #38012).
1. Reflected XSS in the account chooser and the other new pages. Every address and
username now goes through htmlEncode() before it lands in the HTML or an attribute
(chooseAccountPage, completeAccountPage, emailLinkPage, changeEmailPage and the
confirmation pages). I also gated the email-link side of the chooser on
emailLinkEnabled(), so your chooseAccount + emailLogin_email= URL now
renders the tag as text and does nothing at all where the feature is off.
2. Registering someone else's address to steal their social login. The two OAuth
email-match queries (resolveIdentity and chooseAccount) now require
accountActivated='Y', so an unactivated row someone planted with a victim's address
can no longer be matched or linked. completeAccount only marks the new account
activated when the provider actually verified the address and the user kept it;
otherwise it creates the account inactive and sends the usual confirmation mail, so
an unverified address can never be planted as a trusted one.
3. OAuth requests not enforcing the server certificate. Rather than poke the env var,
I added a small library knob, httpsSetCertCheck() in lib/https.c, that pins the
cert-check mode for the rest of the process and is not overwritten by openSslInit()
or hg.conf. hgLogin's httpRequest() calls it with "abort", so those requests refuse
a bad certificate no matter how the site is configured, and it no longer depends on
being the first HTTPS connection.
4. The pending-identity signature. It now also covers the hguid (which survives the
provider redirect, unlike the hgsid) and the time it was minted, with a 15-minute
expiry, and it is cleared on the failure paths too. A signature that leaks into a
saved or shared session is now useless to another browser and dies quickly anyway.
5. changeEmail. It now asks for the current password where the account has one, and it
no longer changes the address on the spot - it emails a one-time signed confirmation
link to the new address and only applies the change when that link is opened. When the
change lands it also mails the OLD address to say the account's email was changed and
who to contact if that wasn't them, so a hijack gets noticed. One honest caveat: an
account with no password (social-only) still can't be re-checked before the change, so
a stolen cookie could still start it - but the old-address alert now gives the owner a
way to catch it. Expiring login cookies is the deeper fix and feels like its own ticket.
6. isalnum() on a signed char in suggestUsername - now cast to unsigned char.
Build is clean, no new warnings. Set back to you for another look.
refs #38008
diff --git src/hg/hgLogin/hgLogin.c src/hg/hgLogin/hgLogin.c
index db2f1aede6e..3445e3459d7 100644
--- src/hg/hgLogin/hgLogin.c
+++ src/hg/hgLogin/hgLogin.c
@@ -1,2398 +1,2608 @@
/* hgLogin - Administer UCSC Genome Browser membership - signup, lost password, etc. */
/* Copyright (C) 2014 The Regents of the University of California
* See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
#include
#include
#include
#include "common.h"
#include "hash.h"
+#include "portable.h"
#include "hmac.h"
#include "obscure.h"
#include "hgConfig.h"
#include "cheapcgi.h"
#include "memalloc.h"
#include "jksql.h"
#include "htmshell.h"
#include "cart.h"
#include "hPrint.h"
#include "hdb.h"
#include "hui.h"
#include "web.h"
#include "ra.h"
#include "hgColors.h"
#include "net.h"
#include "wikiLink.h"
#include "hgLogin.h"
#include "gbMembers.h"
#include "oauthLogin.h"
#include "versionInfo.h"
#include "mailViaPipe.h"
#include "dystring.h"
#include "autoUpgrade.h"
#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",
- "code", "state", "provider", "user", "token", NULL };
+ "hgLogin_curPassword", "code", "state", "provider", "user", "token",
+ "newEmail", "exp", "sig", 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() */
/* 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 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));
}
char *browserAddr()
/* Return the browser address like 'http://genome.ucsc.edu' */
{
if isEmpty(cfgOption(CFG_LOGIN_BROWSER_ADDR))
return cloneString("NULL_browserAddr");
else
return cloneString(cfgOption(CFG_LOGIN_BROWSER_ADDR));
}
char *mailSignature()
/* Return the signature to be used by outbound mail or NULL. Allocd here. */
{
if isEmpty(cfgOption(CFG_LOGIN_MAIL_SIGNATURE))
return cloneString("NULL_mailSignature");
else
return cloneString(cfgOption(CFG_LOGIN_MAIL_SIGNATURE));
}
char *mailReturnAddr()
/* Return the return addr. to be used by outbound mail or NULL. Allocd here.
* If set to "NOEMAIL" then no email will be sent and the account is activated right away.
* */
{
if isEmpty(cfgOption(CFG_LOGIN_MAIL_RETURN_ADDR))
return cloneString("NULL_mailReturnAddr");
else
return cloneString(cfgOption(CFG_LOGIN_MAIL_RETURN_ADDR));
}
/* ---- password functions depend on installed openssl lib ---- */
void md5It(unsigned char *input, int inputSize, unsigned char *output)
/* handle function deprecated by newer versions of openssl */
{
#if OPENSSL_VERSION_NUMBER >= 0x30000000L // > #3.0
EVP_Q_digest(NULL, "MD5", NULL, input, inputSize, output, NULL);
#else
MD5(input, inputSize, output);
#endif
}
void cryptWikiWay(char *password, char *salt, char* result)
/* encrypt password in mediawiki format -
':B:'.$salt.':'. md5($salt.'-'.md5($password ) */
{
int i;
unsigned char result1[MD5_DIGEST_LENGTH];
unsigned char result2[MD5_DIGEST_LENGTH];
char firstMD5[MD5_DIGEST_LENGTH*2 + 1];
char secondMD5[MD5_DIGEST_LENGTH*2 + 1];
i = MD5_DIGEST_LENGTH;
md5It((unsigned char *)password, strlen(password), result1);
for(i = 0; i < MD5_DIGEST_LENGTH; i++)
{
sprintf(&firstMD5[i*2], "%02x", result1[i]);
}
// add the salt with "-"
char saltDashMD5[256];
strcpy(saltDashMD5,salt);
strcat(saltDashMD5,"-");
strcat(saltDashMD5,firstMD5);
md5It((unsigned char *) saltDashMD5, strlen(saltDashMD5), result2);
for(i = 0; i < MD5_DIGEST_LENGTH; i++)
{
sprintf(&secondMD5[i*2], "%02x", result2[i]);
}
strcpy(result, secondMD5);
}
void encryptPWD(char *password, char *salt, char *buf, int bufsize)
/* encrypt a password in mediawiki way */
{
char md5Returned[100];
cryptWikiWay(password, salt, md5Returned);
safecat(buf,bufsize,":B:");
safecat(buf,bufsize,salt);
safecat(buf,bufsize,":");
safecat(buf,bufsize,md5Returned);
}
void encryptNewPwd(char *password, char *buf, int bufsize)
/* encrypt a new password */
{
unsigned long seed[2];
char salt[] = "........";
const char *const seedchars =
"0123456789ABCDEFGHIJKLMNOPQRST"
"UVWXYZabcdefghijklmnopqrstuvwxyz";
int i;
/* Generate a (not very) random seed. */
seed[0] = time(NULL);
seed[1] = getpid() ^ (seed[0] >> 14 & 0x30000);
/* Turn it into printable characters from 'seedchars'. */
for (i = 0; i < 8; i++)
salt[i] = seedchars[(seed[i/5] >> (i%5)*6) & 0x3f];
encryptPWD(password, salt, buf, bufsize);
}
char *generateTokenMD5(char *token)
/* Generate an unsalted MD5 string from token. */
{
unsigned char result[MD5_DIGEST_LENGTH];
char tokenMD5[MD5_DIGEST_LENGTH*2 + 1];
int i = MD5_DIGEST_LENGTH;
md5It((unsigned char *) token, strlen(token), result);
// Convert the tokenMD5 value to string
for(i = 0; i < MD5_DIGEST_LENGTH; i++)
{
sprintf(&tokenMD5[i*2], "%02x", result[i]);
}
return cloneString(tokenMD5);
}
void findSalt(char *encPassword, char *salt, int saltSize)
/* find the salt part from the password field */
{
char tempStr1[45];
char tempStr2[45];
int i;
// Skip the ":B:" part
for (i = 3; i <= strlen(encPassword); i++)
tempStr1[i-3] = encPassword[i];
i = strcspn(tempStr1,":");
safencpy(tempStr2, sizeof(tempStr2), tempStr1, i);
safecpy(salt, saltSize,tempStr2);
}
bool checkPwd(char *password, char *encPassword)
/* check an encrypted password */
{
char salt[14];
int saltSize;
saltSize = sizeof(salt);
findSalt(encPassword, salt, saltSize);
char encPwd[45] = "";
encryptPWD(password, salt, encPwd, sizeof(encPwd));
if (sameString(encPassword,encPwd))
return TRUE;
else
return FALSE;
}
unsigned int randInt(unsigned int n)
/* little randome number helper returns 0 to n-1 */
{
return (unsigned int) n * (rand() / (RAND_MAX + 1.0));
}
char *generateRandomPassword()
/* Generate random password for users who have lost their old one. */
{
char boundary[256];
char punc[] = "!@#$%&()";
/* choose a new string for the boundary */
/* Set initial seed */
int i = 0;
int r = 0;
char c = ' ';
boundary[0]=0;
srand( (unsigned)time( NULL ) );
for(i=0;i<8;++i)
{
r = randInt(4);
switch (r)
{
case 0 :
c = 'A' + randInt(26);
break;
case 1 :
c = 'a' + randInt(26);
break;
case 2 :
c = '0' + randInt(10);
break;
default:
c = punc[randInt(8)];
break;
}
boundary[i] = c;
}
boundary[i]=0;
return cloneString(boundary);
}
/* ---- General purpose helper routines. ---- */
int spc_email_isvalid(const char *address) {
/* Check the format of an email address syntactically. Return 1 if valid, else 0 */
/* Code copied from the book:
"Secure Programming Cookbook for C and C++"
By: John Viega; Matt Messier
Publisher: O'Reilly Media, Inc.
Pub. Date: July 14, 2003
Print ISBN-13: 978-0-596-00394-4
*/
int count = 0;
const char *c, *domain;
static char *rfc822_specials = "()<>@,;:\\\"[]";
/* first we validate the name portion (name@domain) */
for (c = address; *c; c++)
{
if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"'))
{
while (*++c)
{
if (*c == '\"') break;
if (*c == '\\' && (*++c == ' ')) continue;
if (*c <= ' ' || *c >= 127) return 0;
}
if (!*c++) return 0;
if (*c == '@') break;
if (*c != '.') return 0;
continue;
}
if (*c == '@') break;
if (*c <= ' ' || *c >= 127) return 0;
if (strchr(rfc822_specials, *c)) return 0;
}
if (c == address || *(c - 1) == '.') return 0;
/* next we validate the domain portion (name@domain) */
if (!*(domain = ++c)) return 0;
do
{
if (*c == '.')
{
if (c == domain || *(c - 1) == '.') return 0;
count++;
}
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)
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;
}
}
return FALSE;
}
char *getReturnToURL()
/* get URL from cart var returnto; if empty, make URL to hgSession on login host. */
{
char *returnURL = cartUsualString(cart, "returnto", "");
char returnTo[2048];
if (!returnURL || sameString(returnURL,""))
safef(returnTo, sizeof(returnTo), "%shgSession?hgS_doMainPage=1", hLoginHostCgiBinUrl());
else if (cfgOptionDefault(CFG_APPROVED_HOSTS, NULL))
{
if (isValidReturnUrl(returnURL))
safecpy(returnTo, sizeof(returnTo), returnURL);
else
{
hDumpStackDisallow();
errAbort("Error: Invalid returnto URL. Please send email to genome-www@soe.ucsc.edu "
"with the returnto argument from the URL (or just the full URL) so we can "
"fix this.");
}
}
else
safecpy(returnTo, sizeof(returnTo), returnURL);
return cloneString(returnTo);
}
void returnToURL(int delay)
/* delay for delay mill-seconds then return to the "returnto" URL */
{
char *returnURL = getReturnToURL();
jsInlineF(
"setTimeout(function(){location='%s';}, %d);\n"
, returnURL, delay);
}
static void redirectToLoginPage(char *paramStr)
/* redirect to hgLogin page with given parameter string */
{
jsInlineF(
"window.location ='%s?%s';\n"
, hgLoginUrl, paramStr);
}
void displayActMailSuccess()
/* display Activate mail success box */
{
char *returnURL = getReturnToURL();
hPrintf(
"
"
"\n"
"
%s
", brwName);
hPrintf(
"
A confirmation email has been sent to you. \n"
"Please click the confirmation link in the email to activate your account.
"
"
You may have to look in your spam folder for an email from genome-www@soe.ucsc.edu, "
"especially if you use Microsoft Outlook or Hotmail.
", returnURL);
cartRemove(cart, "hgLogin_email");
cartRemove(cart, "hgLogin_userName");
}
void sendActMailOut(char *email, char *subject, char *msg)
/* send mail to email address */
{
int result;
result = mailViaPipeBounce(email, subject, msg, returnAddr);
if (result == -1)
{
hPrintf(
"
%s
", brwName);
hPrintf(
"
"
"
"
"
Error emailing to: %s
"
"Click here to return. ",
hgLoginUrl, email );
exit(0);
}
}
void displayMailSuccess()
/* display mail success confirmation box */
{
char *sendMailTo = cartUsualString(cart, "hgLogin_sendMailTo", "");
hPrintf(
"
"
"
%s
", brwName);
hPrintf(
"
All usernames on file (if any) for %s "
"have been sent to that address.
"
" If %s is not your registered email address, you will not receive an email."
" If you can't find the message we sent you, please contact %s for help.
", brwName);
char *contactAddr = returnAddr;
if (sameString(returnAddr, "NOEMAIL"))
contactAddr = "the administrator of this Genome Browser Mirror";
hPrintf(
"
An email containing password reset information has been sent to the registered email address of %s.
"
" If you do not receive an email, please contact %s for help.
", username, contactAddr);
if (sameString(returnAddr, "NOEMAIL"))
hPrintf("
If you still have questions, you can contact the Genome Browser team at "
"genome-www@soe.ucsc.edu. As this is a mirror website not managed by UCSC, please "
"specify the address of the mirror in your email.
"
"Click here to return. ",
hgLoginUrl, obj, email );
}
else
{
jsInlineF(
"window.location = '%s?hgLogin.do.displayMailSuccess=1';\n"
, hgLoginUrl);
}
}
void mailUsername(char *email, char *users)
/* send user name list to the email address */
{
char subject[256];
char msg[4096];
char *remoteAddr=getenv("REMOTE_ADDR");
safef(subject, sizeof(subject),"Your username at the %s", brwName);
safef(msg, sizeof(msg),
" Someone (probably you, from IP address %s) has requested username(s) associated with this email address at the %s: \n\n %s\n\n%s\n%s",
remoteAddr, brwName, users, signature, returnAddr);
sendMailOut(email, subject, msg);
}
void sendUsername(struct sqlConnection *conn, char *email)
/* email user username(s) */
{
struct sqlResult *sr;
char **row;
char query[256];
/* find all the user names associated with this email address */
char userList[512]="";
sqlSafef(query,sizeof(query),"SELECT * FROM gbMembers WHERE email='%s' or recovEmail='%s'", email, email);
sr = sqlGetResult(conn, query);
int numUser = 0;
while ((row = sqlNextRow(sr)) != NULL)
{
struct gbMembers *m = gbMembersLoad(row);
if (numUser >= 1)
safecat(userList, sizeof(userList), ", ");
safecat(userList, sizeof(userList), m->userName);
numUser += 1;
}
sqlFreeResult(&sr);
mailUsername(email, userList);
}
void sendPwdMailOut(char *email, char *recovEmail, char *subject, char *msg, char *username)
/* send password reset mail to user at registered email address */
{
char *obj = cartUsualString(cart, "hgLogin_helpWith", "");
int result;
result = mailViaPipeBounce(email, subject, msg, returnAddr);
if ((result != -1) && !isEmpty(recovEmail))
result = mailViaPipeBounce(recovEmail, subject, msg, returnAddr);
if (result == -1)
{
hPrintf(
"
%s
", brwName);
hPrintf(
"
"
"
"
"
Error emailing %s to: %s
"
"Click here to return. ",
hgLoginUrl, obj, email );
}
else
{
jsInlineF(
"window.location = '%s?hgLogin.do.displayMailSuccessPwd=1&user=%s';\n"
, hgLoginUrl, username);
}
}
void sendNewPwdMail(char *username, char *email, char *recovEmail, char *password)
/* send user new password */
{
char subject[256];
char msg[4096];
char *remoteAddr=getenv("REMOTE_ADDR");
safef(subject, sizeof(subject),"New temporary password for your account at the %s", brwName);
safef(msg, sizeof(msg),
" Someone (probably you, from IP address %s) requested a new password for the %s (%s). A temporary password for user \"%s\" has been created and was set to \"%s\". If this was your intent, you will need to log in and choose a new password now. Your temporary password will expire in 7 days.\n\n If someone else made this request, or if you have remembered your password, and you no longer wish to change it, you may ignore this message and continue using your old password.\n\n%s\n%s",
remoteAddr, brwName, brwAddr, username, password, signature, returnAddr);
sendPwdMailOut(email, recovEmail, subject, msg, username);
}
void displayAccHelpPage(struct sqlConnection *conn)
/* draw the account help page */
{
char *email = cartUsualString(cart, "hgLogin_email", "");
char *username = cartUsualString(cart, "hgLogin_userName", "");
jsInline(
"function toggle(value){\n"
"if(value=='showE'){\n"
" document.getElementById('usernameBox').style.display='none';\n"
" document.getElementById('emailAddrBox').style.display='inline';\n"
" } else {\n"
" document.getElementById('usernameBox').style.display='inline';\n"
" document.getElementById('emailAddrBox').style.display='none';\n"
" }\n"
"}\n"
);
hPrintf("
"
"\n"
"
%s
"
"\n", brwName);
hPrintf("
Having trouble signing in?
"
"\n"
""
"
", username, email, getReturnToURL());
jsOnEventById("click", "password", "toggle('showU');");
jsOnEventById("click", "username", "toggle('showE');");
if (emailLinkEnabled())
jsOnEventById("click", "loginLink", "toggle('showE');");
// If a radio was preselected via the URL, open its matching input box on load.
if (sameString(pre, "password"))
jsInline("toggle('showU');");
else if (sameString(pre, "username") || sameString(pre, "loginLink"))
jsInline("toggle('showE');");
cartSaveSession(cart);
}
void sendNewPassword(struct sqlConnection *conn, char *username, char *password)
/* email user new password */
{
char query[256];
/* find email address associated with this username */
sqlSafef(query,sizeof(query),"SELECT email FROM gbMembers WHERE userName='%s'", username);
char *email = sqlQuickString(conn, query);
if (!email || sameString(email,""))
{
freez(&errMsg);
errMsg = cloneString("Email address not found.");
displayAccHelpPage(conn);
return;
}
sqlSafef(query,sizeof(query),"SELECT recovEmail FROM gbMembers WHERE userName='%s'", username);
char *recovEmail = sqlQuickString(conn, query);
sendNewPwdMail(username, email, recovEmail, password);
}
void lostPassword(struct sqlConnection *conn, char *username)
/* Generate and mail new password to user */
{
char query[256];
char *password = generateRandomPassword();
char encPwd[45] = "";
encryptNewPwd(password, encPwd, sizeof(encPwd));
sqlSafef(query,sizeof(query), "UPDATE gbMembers SET lastUse=NOW(),newPassword='%s', newPasswordExpire=DATE_ADD(NOW(), INTERVAL 7 DAY), passwordChangeRequired='Y' WHERE userName='%s'",
encPwd, username);
sqlUpdate(conn, query);
sendNewPassword(conn, username, password);
return;
}
void clearNewPasswordFields(struct sqlConnection *conn, char *username)
/* clear the newPassword fields */
{
char query[256];
sqlSafef(query,sizeof(query), "UPDATE gbMembers SET lastUse=NOW(),newPassword='', newPasswordExpire='', passwordChangeRequired='N' WHERE userName='%s'",
username);
sqlUpdate(conn, query);
cartRemove(cart, "hgLogin_changeRequired");
return;
}
void sendActivateMail(char *email, char *username, char *encToken)
/* Send activation mail with token to user*/
{
char subject[256];
char msg[4096];
char activateURL[256];
char *remoteAddr=getenv("REMOTE_ADDR");
safef(activateURL, sizeof(activateURL),
"%s?hgLogin.do.activateAccount=1&user=%s&token=%s\n",
hgLoginUrl,
cgiEncode(username),
cgiEncode(encToken));
safef(subject, sizeof(subject),"%s account e-mail address confirmation", brwName);
safef(msg, sizeof(msg),
"Someone (probably you, from IP address %s) has requested an account %s with this e-mail address on the %s.\nTo confirm that this account really does belong to you on the %s, open this link in your browser:\n\n%s\n\nIf this is *not* you, do not follow the link. This confirmation code will expire in 7 days.\n\nIf this *is* you, after clicking the activation link, your new account gives you access to sessions you can create and name. Sessions allow you to save your Genome Browser screen configuration and share it with others with a link like https://genome.ucsc.edu/s/%s/YourSessionName\n\nFor more information on sessions, see our help page on the topic: https://genome.ucsc.edu/goldenPath/help/hgSessionHelp.html#Introduction\n\nAdditional resources:\nSubscribe to the Genome Browser Mailing List: https://groups.google.com/a/soe.ucsc.edu/group/genome-announce?hl=en\nGenome Browser User Guide: https://genome.ucsc.edu/goldenPath/help/hgTracksHelp.html\nTraining and Tutorials: https://genome.ucsc.edu/training/index.html\n\n%s\n%s",
remoteAddr, username, brwName, brwName, activateURL, username, signature, returnAddr);
sendActMailOut(email, subject, msg);
}
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 */
{
char *username = cartUsualString(cart, "hgLogin_userName", "");
hPrintf("