in packages/fxa-auth-server/lib/routes/oauth/token.js [562:708]
handler: async function (req) {
const sessionToken = req.auth.credentials;
delete req.headers.authorization;
let grant;
switch (req.payload.grant_type) {
case 'authorization_code':
case 'refresh_token':
try {
grant = await tokenHandler(req);
} catch (err) {
// TODO auth/oauth error reconciliation
if (err.errno === 108) {
throw AuthError.invalidToken();
}
throw err;
}
break;
case 'fxa-credentials':
if (!sessionToken) {
throw AuthError.invalidToken();
}
req.payload.assertion = await makeAssertionJWT(
config.getProperties(),
sessionToken
);
grant = await tokenHandler(req);
break;
default:
throw AuthError.internalValidationError();
}
const scopeSet = ScopeSet.fromString(grant.scope);
if (scopeSet.contains(OAUTH_SCOPE_SESSION_TOKEN)) {
// the OAUTH_SCOPE_SESSION_TOKEN allows the client to create a new session token.
// the sessionTokens live in the auth-server db, we create them here after the oauth-server has validated the request.
let origSessionToken;
try {
origSessionToken = await db.sessionToken(grant.session_token_id);
} catch (e) {
throw AuthError.unknownAuthorizationCode();
}
const newTokenData = await origSessionToken.copyTokenState();
// Update UA info based on the requesting device.
const { ua } = req.app;
const newUAInfo = {
uaBrowser: ua.browser,
uaBrowserVersion: ua.browserVersion,
uaOS: ua.os,
uaOSVersion: ua.osVersion,
uaDeviceType: ua.deviceType,
uaFormFactor: ua.formFactor,
};
const sessionTokenOptions = {
...newTokenData,
...newUAInfo,
};
const newSessionToken =
await db.createSessionToken(sessionTokenOptions);
// the new session token information is later
// used in 'newTokenNotification' to attach it to device records
grant.session_token_id = newSessionToken.id;
grant.session_token = newSessionToken.data;
}
if (grant.refresh_token) {
// if a refresh token has
// been provisioned as part of the flow
// then we want to send some notifications to the user
await oauthRouteUtils.newTokenNotification(
db,
mailer,
devices,
req,
grant
);
}
if (updateLastAccessTime && sessionToken) {
sessionToken.lastAccessTime = Date.now();
await db.touchSessionToken(sessionToken, {}, true);
}
// done with 'session_token_id' at this point, do not return it.
delete grant.session_token_id;
// attempt to record metrics, but swallow the error if one is thrown.
try {
let uid = sessionToken && sessionToken.uid;
// As mentioned in lib/routes/utils/oauth.js, some grant flows won't
// have the uid in `credentials`, so we get it from the oauth DB.
if (!uid) {
const tokenVerify = await token.verify(grant.access_token);
uid = tokenVerify.user;
}
await req.emitMetricsEvent('oauth.token.created', {
grantType: req.payload.grant_type,
uid,
clientId: req.payload.client_id,
service: req.payload.client_id,
});
// We emit the `account.signed`
// event to signal to the flow it has been completed (see flowCompleteSignal).
// A "fxa_activity - cert_signed" event will be emitted since
// "account.signed" is mapped to it. And cert_signed is used in a
// rollup to generate the "fxa_activity - active" event in Amplitude
// (ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1632635), where
// we need the 'service' event property to distinguish between sync
// and browser.
if (
scopeSet.contains(OAUTH_SCOPE_OLD_SYNC) &&
// Desktop requests a profile scope token before adding the device
// to the account. To ensure we record accurate device counts, only
// emit this event if the request is for an oldsync scope, not a
// profile scope. See #6578 for details on the order of API calls
// made by both desktop and fenix.
!scopeSet.contains('profile')
) {
// For desktop, the 'service' parameter for this event gets
// special-cased to 'sync' so that it matches its pre-oauth
// `/certificate/sign` event.
// ref: https://github.com/mozilla/fxa/pull/6581#issuecomment-702248031
// Otherwise, for mobile browsers, just use the existing client ID
// to service name mapping used in the metrics code (see the
// OAUTH_CLIENT_IDS config value). #5143
const service = config
.get('oauth.oldSyncClientIds')
.includes(req.payload.client_id)
? 'sync'
: req.payload.client_id;
await req.emitMetricsEvent('account.signed', {
uid: uid,
device_id: sessionToken.deviceId,
service,
});
}
} catch (ex) {}
return grant;
},