UNPKG

@zeushq/nextjs-zidentity

Version:

Next.js SDK for signin in with Zeus Identity

100 lines (85 loc) 3.32 kB
import { IncomingMessage, ServerResponse } from 'http'; import urlJoin from 'url-join'; import { strict as assert } from 'assert'; import { Config, LoginOptions } from '../config'; import TransientStore, { StoreOptions } from '../transient-store'; import { encodeState } from '../hooks/get-login-state'; import { ClientFactory } from '../client'; import createDebug from '../utils/debug'; const debug = createDebug('handlers'); function getRedirectUri(config: Config): string { return urlJoin(config.baseURL, config.routes.callback); } export type HandleLogin = (req: IncomingMessage, res: ServerResponse, options?: LoginOptions) => Promise<void>; export default function loginHandlerFactory( config: Config, getClient: ClientFactory, transientHandler: TransientStore ): HandleLogin { return async (req, res, options = {}) => { const client = await getClient(); const returnTo = options.returnTo || config.baseURL; const opts = { returnTo, getLoginState: config.getLoginState, ...options }; // Ensure a redirect_uri, merge in configuration options, then passed-in options. opts.authorizationParams = { redirect_uri: getRedirectUri(config), ...config.authorizationParams, ...(opts.authorizationParams || {}) }; const transientOpts: StoreOptions = { sameSite: opts.authorizationParams.response_mode === 'form_post' ? 'none' : 'lax' }; const stateValue = await opts.getLoginState(req as any, opts); if (typeof stateValue !== 'object') { throw new Error('Custom state value must be an object.'); } stateValue.nonce = transientHandler.generateNonce(); stateValue.returnTo = stateValue.returnTo || opts.returnTo; const usePKCE = (opts.authorizationParams.response_type as string).includes('code'); if (usePKCE) { debug('response_type includes code, the authorization request will use PKCE'); stateValue.code_verifier = transientHandler.generateCodeVerifier(); } const authParams = { ...opts.authorizationParams, nonce: transientHandler.save('nonce', req, res, transientOpts), state: transientHandler.save('state', req, res, { ...transientOpts, value: encodeState(stateValue) }), ...(opts.authorizationParams.signup ? { signup: true } : undefined), ...(usePKCE ? { code_challenge: transientHandler.calculateCodeChallenge( transientHandler.save('code_verifier', req, res, transientOpts) ), code_challenge_method: 'S256' } : undefined) }; const validResponseTypes = ['id_token', 'code id_token', 'code']; assert( validResponseTypes.includes(authParams.response_type as string), `response_type should be one of ${validResponseTypes.join(', ')}` ); assert(/\bopenid\b/.test(authParams.scope as string), 'scope should contain "openid"'); if (authParams.max_age) { transientHandler.save('max_age', req, res, { ...transientOpts, value: authParams.max_age.toString() }); } const authorizationUrl = client.authorizationUrl(authParams); debug('redirecting to %s', authorizationUrl); res.writeHead(302, { Location: authorizationUrl }); res.end(); }; }