UNPKG

@wristband/nextjs-auth

Version:

SDK for integrating your NextJS application with Wristband. Handles user authentication and token management.

124 lines (123 loc) 6.04 kB
import retry from 'async-retry'; import { AppRouterAuthHandler } from './app-router/app-router-auth-handler'; import { PageRouterAuthHandler } from './page-router/page-router-auth-handler'; import { WristbandService } from '../services/wristband-service'; import { TENANT_DOMAIN_TOKEN } from '../utils/constants'; import { FetchError, WristbandError } from '../error'; /** * WristbandAuth is a utility class providing methods for seamless interaction with the Wristband authentication service. * @implements {WristbandAuth} */ export class WristbandAuthImpl { /** * Creates an instance of WristbandAuth. * * @param {AuthConfig} authConfig The configuration for Wristband authentication. */ constructor(authConfig) { this.appRouter = { login: (req, loginConfig) => { return this.appRouterAuthHandler.login(req, loginConfig); }, callback: (req) => { return this.appRouterAuthHandler.callback(req); }, logout: (req, logoutConfig) => { return this.appRouterAuthHandler.logout(req, logoutConfig); }, createCallbackResponse: (req, redirectUrl) => { return this.appRouterAuthHandler.createCallbackResponse(req, redirectUrl); }, }; this.pageRouter = { login: (req, res, loginConfig) => { return this.pageRouterAuthHandler.login(req, res, loginConfig); }, callback: (req, res) => { return this.pageRouterAuthHandler.callback(req, res); }, logout: (req, res, logoutConfig) => { return this.pageRouterAuthHandler.logout(req, res, logoutConfig); }, }; if (!authConfig.clientId) { throw new TypeError('The [clientId] config must have a value.'); } if (!authConfig.clientSecret) { throw new TypeError('The [clientSecret] config must have a value.'); } if (!authConfig.loginStateSecret || authConfig.loginStateSecret.length < 32) { throw new TypeError('The [loginStateSecret] config must have a value of at least 32 characters.'); } if (!authConfig.loginUrl) { throw new TypeError('The [loginUrl] config must have a value.'); } if (!authConfig.redirectUri) { throw new TypeError('The [redirectUri] config must have a value.'); } if (!authConfig.wristbandApplicationDomain) { throw new TypeError('The [wristbandApplicationDomain] config must have a value.'); } if (authConfig.useTenantSubdomains) { if (!authConfig.rootDomain) { throw new TypeError('The [rootDomain] config must have a value when using tenant subdomains.'); } if (!authConfig.loginUrl.includes(TENANT_DOMAIN_TOKEN)) { throw new TypeError('The [loginUrl] must contain the "{tenant_domain}" token when using tenant subdomains.'); } if (!authConfig.redirectUri.includes(TENANT_DOMAIN_TOKEN)) { throw new TypeError('The [redirectUri] must contain the "{tenant_domain}" token when using tenant subdomains.'); } } else { if (authConfig.loginUrl.includes(TENANT_DOMAIN_TOKEN)) { throw new TypeError('The [loginUrl] must contain the "{tenant_domain}" token when using tenant subdomains.'); } if (authConfig.redirectUri.includes(TENANT_DOMAIN_TOKEN)) { throw new TypeError('The [redirectUri] must contain the "{tenant_domain}" token when using tenant subdomains.'); } } const wristbandServiceImpl = new WristbandService(authConfig.wristbandApplicationDomain, authConfig.clientId, authConfig.clientSecret); this.wristbandService = wristbandServiceImpl; this.appRouterAuthHandler = new AppRouterAuthHandler(authConfig, wristbandServiceImpl); this.pageRouterAuthHandler = new PageRouterAuthHandler(authConfig, wristbandServiceImpl); } async refreshTokenIfExpired(refreshToken, expiresAt) { // Safety checks if (!refreshToken) { throw new TypeError('Refresh token must be a valid string'); } if (!expiresAt || expiresAt < 0) { throw new TypeError('The expiresAt field must be an integer greater than 0'); } if (Date.now().valueOf() <= expiresAt) { return null; } // Try up to 3 times to perform a token refresh. let tokenResponse = null; await retry(async (bail) => { try { tokenResponse = await this.wristbandService.refreshToken(refreshToken); } catch (error) { if (error instanceof FetchError && error.response && error.response.status >= 400 && error.response.status < 500) { const errorDescription = error.body && error.body.error_description ? error.body.error_description : 'Invalid Refresh Token'; // Only 4xx errors should short-circuit the retry loop early. bail(new WristbandError('invalid_refresh_token', errorDescription)); return; } // Retry any 5xx errors. throw new WristbandError('unexpected_error', 'Unexpected Error'); } }, { retries: 2, minTimeout: 100, maxTimeout: 100 }); if (tokenResponse) { const { access_token: accessToken, id_token: idToken, expires_in: expiresIn, refresh_token: responseRefreshToken, } = tokenResponse; return { accessToken, idToken, refreshToken: responseRefreshToken, expiresIn }; } // This is merely a safety check, but this should never happen. throw new WristbandError('unexpected_error', 'Unexpected Error'); } }