handler: async function()

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;
      },