Skip to content

Commit

Permalink
async ALL THE THINGS
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredhirsch committed Aug 21, 2019
1 parent 536e7cb commit 3eda0f6
Showing 1 changed file with 132 additions and 160 deletions.
292 changes: 132 additions & 160 deletions packages/fxa-auth-server/lib/routes/password.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,92 +154,74 @@ module.exports = function(
.then(createKeyFetchToken)
.then(createResponse);

function checkTotpToken() {
return totpUtils.hasTotpToken(passwordChangeToken).then(result => {
hasTotp = result;

// Currently, users that have a TOTP token must specify a sessionTokenId to complete the
// password change process. While the `sessionTokenId` is optional, we require it
// in the case of TOTP because we want to check that session has been verified
// by TOTP.
if (result && !sessionTokenId) {
throw error.unverifiedSession();
}
});
async function checkTotpToken() {
const result = await totpUtils.hasTotpToken(passwordChangeToken);
hasTotp = result;

// Currently, users that have a TOTP token must specify a sessionTokenId to complete the
// password change process. While the `sessionTokenId` is optional, we require it
// in the case of TOTP because we want to check that session has been verified
// by TOTP.
if (result && !sessionTokenId) {
throw error.unverifiedSession();
}
}

function getSessionVerificationStatus() {
async function getSessionVerificationStatus() {
if (sessionTokenId) {
return db.sessionToken(sessionTokenId).then(tokenData => {
verifiedStatus = tokenData.tokenVerified;
if (tokenData.deviceId) {
originatingDeviceId = tokenData.deviceId;
}
const tokenData = await db.sessionToken(sessionTokenId);
verifiedStatus = tokenData.tokenVerified;
if (tokenData.deviceId) {
originatingDeviceId = tokenData.deviceId;
}

if (hasTotp && tokenData.authenticatorAssuranceLevel <= 1) {
throw error.unverifiedSession();
}
});
if (hasTotp && tokenData.authenticatorAssuranceLevel <= 1) {
throw error.unverifiedSession();
}
} else {
// Don't create a verified session unless they already had one.
verifiedStatus = false;
return P.resolve();
}
}

function fetchDevicesToNotify() {
async function fetchDevicesToNotify() {
// We fetch the devices to notify before changePassword() because
// db.resetAccount() deletes all the devices saved in the account.
return request.app.devices.then(devices => {
devicesToNotify = devices;
// If the originating sessionToken belongs to a device,
// do not send the notification to that device. It will
// get informed about the change via WebChannel message.
if (originatingDeviceId) {
devicesToNotify = devicesToNotify.filter(
d => d.id !== originatingDeviceId
);
}
});
const devices = await request.app.devices;
devicesToNotify = devices;
// If the originating sessionToken belongs to a device,
// do not send the notification to that device. It will
// get informed about the change via WebChannel message.
if (originatingDeviceId) {
devicesToNotify = devicesToNotify.filter(
d => d.id !== originatingDeviceId
);
}
}

function changePassword() {
async function changePassword() {
let authSalt, password;
return random
.hex(32)
.then(hex => {
authSalt = hex;
password = new Password(authPW, authSalt, verifierVersion);
return db.deletePasswordChangeToken(passwordChangeToken);
})
.then(() => {
return password.verifyHash();
})
.then(hash => {
verifyHash = hash;
return password.wrap(wrapKb);
})
.then(wrapWrapKb => {
// Reset account, delete all sessions and tokens
return db.resetAccount(passwordChangeToken, {
verifyHash: verifyHash,
authSalt: authSalt,
wrapWrapKb: wrapWrapKb,
verifierVersion: password.version,
});
})
.then(result => {
return request
.emitMetricsEvent('account.changedPassword', {
uid: passwordChangeToken.uid,
})
.then(() => {
return result;
});
});
const hex = await random.hex(32); // TODO why is this async?
authSalt = hex;
password = new Password(authPW, authSalt, verifierVersion);
await db.deletePasswordChangeToken(passwordChangeToken);
const hash = await password.verifyHash();
verifyHash = hash;
const wrapWrapKb = await password.wrap(wrapKb);
// Reset account, delete all sessions and tokens
const result = db.resetAccount(passwordChangeToken, {
verifyHash: verifyHash,
authSalt: authSalt,
wrapWrapKb: wrapWrapKb,
verifierVersion: password.version,
});
request.emitMetricsEvent('account.changedPassword', {
uid: passwordChangeToken.uid,
});
return result;
}

function notifyAccount() {
async function notifyAccount() {
if (devicesToNotify) {
// Notify the devices that the account has changed.
push.notifyPasswordChanged(
Expand All @@ -248,112 +230,102 @@ module.exports = function(
);
}

return db
.account(passwordChangeToken.uid)
.then(accountData => {
account = accountData;

log.notifyAttachedServices('passwordChange', request, {
uid: passwordChangeToken.uid,
generation: account.verifierSetAt,
});
return db.accountEmails(passwordChangeToken.uid);
})
.then(emails => {
const geoData = request.app.geo;
const {
browser: uaBrowser,
browserVersion: uaBrowserVersion,
os: uaOS,
osVersion: uaOSVersion,
deviceType: uaDeviceType,
} = request.app.ua;

return mailer
.sendPasswordChangedNotification(emails, account, {
acceptLanguage: request.app.acceptLanguage,
ip,
location: geoData.location,
timeZone: geoData.timeZone,
uaBrowser,
uaBrowserVersion,
uaOS,
uaOSVersion,
uaDeviceType,
uid: passwordChangeToken.uid,
})
.catch(e => {
// If we couldn't email them, no big deal. Log
// and pretend everything worked.
log.trace(
'Password.changeFinish.sendPasswordChangedNotification.error',
{
error: e,
}
);
});
});
}

function createSessionToken() {
return P.resolve()
.then(() => {
if (!verifiedStatus) {
return random.hex(16);
}
})
.then(maybeToken => {
const {
browser: uaBrowser,
browserVersion: uaBrowserVersion,
os: uaOS,
osVersion: uaOSVersion,
deviceType: uaDeviceType,
formFactor: uaFormFactor,
} = request.app.ua;
const accountData = await db.account(passwordChangeToken.uid);
account = accountData;

// Create a sessionToken with the verification status of the current session
const sessionTokenOptions = {
uid: account.uid,
email: account.email,
emailCode: account.emailCode,
emailVerified: account.emailVerified,
verifierSetAt: account.verifierSetAt,
mustVerify: wantsKeys,
tokenVerificationId: maybeToken,
log.notifyAttachedServices('passwordChange', request, {
uid: passwordChangeToken.uid,
generation: account.verifierSetAt,
});
const emails = await db.accountEmails(passwordChangeToken.uid);
const geoData = request.app.geo;
const {
browser: uaBrowser,
browserVersion: uaBrowserVersion,
os: uaOS,
osVersion: uaOSVersion,
deviceType: uaDeviceType,
} = request.app.ua;

try {
return await mailer.sendPasswordChangedNotification(
emails,
account,
{
acceptLanguage: request.app.acceptLanguage,
ip,
location: geoData.location,
timeZone: geoData.timeZone,
uaBrowser,
uaBrowserVersion,
uaOS,
uaOSVersion,
uaDeviceType,
uaFormFactor,
};
uid: passwordChangeToken.uid,
}
);
} catch (e) {
// If we couldn't email them, no big deal. Log
// and pretend everything worked.
log.trace(
'Password.changeFinish.sendPasswordChangedNotification.error',
{
error: e,
}
);
}
}

return db.createSessionToken(sessionTokenOptions);
})
.then(result => {
sessionToken = result;
});
async function createSessionToken() {
let maybeToken;
if (!verifiedStatus) {
maybeToken = await random.hex(16);
}
const {
browser: uaBrowser,
browserVersion: uaBrowserVersion,
os: uaOS,
osVersion: uaOSVersion,
deviceType: uaDeviceType,
formFactor: uaFormFactor,
} = request.app.ua;

// Create a sessionToken with the verification status of the current session
const sessionTokenOptions = {
uid: account.uid,
email: account.email,
emailCode: account.emailCode,
emailVerified: account.emailVerified,
verifierSetAt: account.verifierSetAt,
mustVerify: wantsKeys,
tokenVerificationId: maybeToken,
uaBrowser,
uaBrowserVersion,
uaOS,
uaOSVersion,
uaDeviceType,
uaFormFactor,
};

sessionToken = await db.createSessionToken(sessionTokenOptions);
}

function createKeyFetchToken() {
async function createKeyFetchToken() {
if (wantsKeys) {
// Create a verified keyFetchToken. This is deliberately verified because we don't
// want to perform an email confirmation loop.
return db
.createKeyFetchToken({
uid: account.uid,
kA: account.kA,
wrapKb: wrapKb,
emailVerified: account.emailVerified,
})
.then(result => {
keyFetchToken = result;
});
const result = await db.createKeyFetchToken({
uid: account.uid,
kA: account.kA,
wrapKb: wrapKb,
emailVerified: account.emailVerified,
});
keyFetchToken = result;
return result;
}
}

function createResponse() {
async function createResponse() {
// If no sessionToken, this could be a legacy client
// attempting to change password, return legacy response.
if (!sessionTokenId) {
Expand Down

0 comments on commit 3eda0f6

Please sign in to comment.