UNPKG

@zeushq/nextjs-zidentity

Version:

Next.js SDK for signin in with Zeus Identity

140 lines (124 loc) 4.53 kB
import { IncomingMessage, ServerResponse } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; import { ClientFactory } from '../zsession'; import { AccessTokenError } from '../utils/errors'; import { intersect, match } from '../utils/arrays'; import { SessionCache, fromTokenSet } from '../session'; import { NextConfig } from '../config'; /** * Custom options to get an Access Token. * * @category Server */ export interface AccessTokenRequest { /** * A list of desired scopes for your Access Token. */ scopes?: string[]; /** * If set to `true`, a new Access Token will be requested with the Refresh Token grant, regardless of whether * the Access Token has expired or not. */ refresh?: boolean; } /** * Response from requesting an Access Token. * * @category Server */ export interface GetAccessTokenResult { /** * Access token returned from the token cache. */ accessToken?: string | undefined; } /** * Get an Access Token to access an external API. * * @category Server */ export type GetAccessToken = ( req: IncomingMessage | NextApiRequest, res: ServerResponse | NextApiResponse, accessTokenRequest?: AccessTokenRequest ) => Promise<GetAccessTokenResult>; /** * @ignore */ export default function accessTokenFactory( config: NextConfig, getClient: ClientFactory, sessionCache: SessionCache ): GetAccessToken { return async (req, res, accessTokenRequest): Promise<GetAccessTokenResult> => { const session = sessionCache.get(req, res); if (!session) { throw new AccessTokenError('invalid_session', 'The user does not have a valid session.'); } if (!session.accessToken && !session.refreshToken) { throw new AccessTokenError('invalid_session', 'The user does not have a valid access token.'); } if (!session.accessTokenExpiresAt) { throw new AccessTokenError( 'access_token_expired', 'Expiration information for the access token is not available. The user will need to sign in again.' ); } if (accessTokenRequest && accessTokenRequest.scopes) { const persistedScopes = session.accessTokenScope; if (!persistedScopes || persistedScopes.length === 0) { throw new AccessTokenError( 'insufficient_scope', 'An access token with the requested scopes could not be provided. The user will need to sign in again.' ); } const matchingScopes = intersect(accessTokenRequest.scopes, persistedScopes.split(' ')); if (!match(accessTokenRequest.scopes, [...matchingScopes])) { throw new AccessTokenError( 'insufficient_scope', `Could not retrieve an access token with scopes "${accessTokenRequest.scopes.join( ' ' )}". The user will need to sign in again.` ); } } // Check if the token has expired. // There is an edge case where we might have some clock skew where our code assumes the token is still valid. // Adding a skew of 1 minute to compensate. if (!session.refreshToken && session.accessTokenExpiresAt * 1000 - 60000 < Date.now()) { throw new AccessTokenError( 'access_token_expired', 'The access token expired and a refresh token is not available. The user will need to sign in again.' ); } // Check if the token has expired. // There is an edge case where we might have some clock skew where our code assumes the token is still valid. // Adding a skew of 1 minute to compensate. if ( (session.refreshToken && session.accessTokenExpiresAt * 1000 - 60000 < Date.now()) || (session.refreshToken && accessTokenRequest && accessTokenRequest.refresh) ) { const client = await getClient(); const tokenSet = await client.refresh(session.refreshToken); // Update the session. const newSession = fromTokenSet(tokenSet, config); Object.assign(session, { ...newSession, refreshToken: newSession.refreshToken || session.refreshToken, user: { ...session.user, ...newSession.user } }); // Return the new access token. return { accessToken: tokenSet.access_token }; } // We don't have an access token. if (!session.accessToken) { throw new AccessTokenError('invalid_session', 'The user does not have a valid access token.'); } // The access token is not expired and has sufficient scopes; return { accessToken: session.accessToken }; }; }