UNPKG

passport-google-oidc-token

Version:
225 lines (197 loc) 5.61 kB
import { Request } from 'express'; import { OAuth2Client, TokenPayload } from 'google-auth-library'; import { Profile as PassportProfile } from 'passport'; interface StrategyOptions { clientID: string; } export interface Profile extends PassportProfile { id: string; username?: string; name?: { givenName: string; middleName?: string; familyName: string; }; photos: { value: string }[]; emails: { value: string; verified: boolean; }[]; displayName: string; _json: any; } export interface StrategyOptionsWithRequest extends StrategyOptions { passReqToCallback: true; } type Info = { message: string; }; type DoneCallback = ( error: Error | null, user: any | undefined, options: Info | undefined, ) => void; type VerifyArgs = [ idToken: string, profile: Profile, doneCallback: DoneCallback, ]; export type VerifyFunction = (...args: VerifyArgs) => void; export type VerifyFunctionWithRequest = ( req: Request, ...args: VerifyArgs ) => void; /** * `GoogleOIDCTokenStrategy` constructor. * * The Google OIDC token strategy authenticates using the Google Auth Library * * Applications must supply a `verify` callback which accepts an `accessToken`, * `refreshToken` and service-specific `profile`, and then calls the `cb` * callback supplying a `user`, which should be set to `false` if the * credentials are not valid. If an exception occurred, `err` should be set. * * @param {Object} options * @param {Function} verify * @example * passport.use(new GoogleOIDCTokenStrategy( * { * clientID: '123456789', * }, * (accessToken, refreshToken, profile, cb) => { * User.findOrCreate({ googleId: profile.id }, cb); * } * ); */ export default class GoogleOIDCTokenStrategy { client: OAuth2Client; clientId: string; name: string; _verify: VerifyFunction | VerifyFunctionWithRequest; _passReqToCallback: boolean; error: (err: Error | unknown) => void = () => {}; fail: (info: Info | undefined) => void = () => {}; success: (user: any, info: Info | undefined) => void = () => {}; constructor( options: StrategyOptionsWithRequest, verify: VerifyFunctionWithRequest, ); constructor(options: StrategyOptions, verify: VerifyFunction); constructor( options: StrategyOptions | StrategyOptionsWithRequest, verify: VerifyFunction | VerifyFunctionWithRequest, ) { this.client = new OAuth2Client(options.clientID); this.clientId = options.clientID; this.name = 'google-oidc-token'; this._verify = verify; if ('passReqToCallback' in options) { } this._passReqToCallback = 'passReqToCallback' in options ? options.passReqToCallback : false; } /** * Authenticate request using Google Auth Library * @param {Object} req */ async authenticate(req: Request) { const idToken = this.lookup(req, 'id_token'); try { const ticket = await this.client.verifyIdToken({ idToken, audience: this.clientId, // Specify the CLIENT_ID of the app that accesses the backend }); const payload = ticket.getPayload(); if (!payload) { throw new Error('No payload returned'); } const verifiedFunction: DoneCallback = (error, user, info) => { if (error) { return this.error(error); } if (!user) { return this.fail(info); } return this.success(user, info); }; const profile = GoogleOIDCTokenStrategy.parseProfile(payload); if (this._passReqToCallback) { (this._verify as VerifyFunctionWithRequest)( req, idToken, profile, verifiedFunction, ); } else { (this._verify as VerifyFunction)(idToken, profile, verifiedFunction); } } catch (err) { this.error(err); } } /** * This method handles searhing the value of provided field in body, query, and header. * * @param {Object} req http request object * @param {String} field * @returns {String} field's value in body, query, or headers */ private lookup(req: Request, field: string): string { return ( (req.body && req.body[field]) || (req.query && req.query[field]) || (req.headers && req.headers[field]) ); } /** * Parse profile. * * Parses user profiles as fetched from Google's OpenID Connect-compatible user * info endpoint. * * The amount of detail in the profile varies based on the scopes granted by the * user. The following scope values add additional data: * * `profile` - basic profile information * `email` - email address * * References: * - https://developers.google.com/identity/protocols/OpenIDConnect * * @param {object} payload * @return {object} */ private static parseProfile(payload: TokenPayload): Profile { const profile: Profile = { provider: 'google', id: payload.sub, displayName: payload.name || '', name: undefined, photos: [], emails: [], _json: payload, }; if (payload.family_name || payload.given_name) { profile.name = { familyName: payload.family_name as string, givenName: payload.given_name as string, }; } if (payload.email) { profile.emails = [ { value: payload.email, verified: payload.email_verified as boolean, }, ]; } if (payload.picture) { profile.photos = [ { value: payload.picture, }, ]; } return profile; } }