@zeushq/nextjs-zidentity
Version:
Next.js SDK for signin in with Zeus Identity
134 lines (126 loc) • 3.76 kB
text/typescript
import { strict as assert } from 'assert';
import { NextApiResponse, NextApiRequest } from 'next';
import { HandleCallback as BaseHandleCallback } from '../zsession';
import { Session } from '../session';
import { assertReqRes } from '../utils/assert';
import { NextConfig } from '../config';
import { HandlerError } from '../utils/errors';
/**
* Use this function for validating additional claims on the user's ID Token or adding removing items from
* the session after login, eg
*
* ### Validate additional claims
*
* ```js
* // pages/api/auth/[...zidentity].js
* import { handleAuth, handleCallback } from '@zeushq/nextjs-zidentity';
*
* const afterCallback = (req, res, session, state) => {
* if (!session.user.isAdmin) {
* throw new UnauthorizedError('User is not admin');
* }
* return session;
* };
*
* export default handleAuth({
* async callback(req, res) {
* try {
* await handleCallback(req, res, { afterCallback });
* } catch (error) {
* res.status(error.status || 500).end(error.message);
* }
* }
* });
* ```
*
* ### Modify the session after login
*
* ```js
* // pages/api/auth/[...zidentity].js
* import { handleAuth, handleCallback } from '@zeushq/nextjs-zidentity';
*
* const afterCallback = (req, res, session, state) => {
* session.user.customProperty = 'foo';
* delete session.refreshToken;
* return session;
* };
*
* export default handleAuth({
* async callback(req, res) {
* try {
* await handleCallback(req, res, { afterCallback });
* } catch (error) {
* res.status(error.status || 500).end(error.message);
* }
* }
* });
* ```
*
* @category Server
*/
export type AfterCallback = (
req: NextApiRequest,
res: NextApiResponse,
session: Session,
state: { [key: string]: any }
) => Promise<Session> | Session;
/**
* Options to customize the callback handler.
*
* @category Server
*/
export interface CallbackOptions {
afterCallback?: AfterCallback;
/**
* This is useful to specify in addition to {@Link BaseConfig.baseURL} when your app runs on multiple domains,
* it should match {@Link LoginOptions.authorizationParams.redirect_uri}.
*/
redirectUri?: string;
/**
* This is useful to specify instead of {@Link NextConfig.organization} when your app has multiple
* organizations, it should match {@Link LoginOptions.authorizationParams}.
*/
organization?: string;
}
/**
* The handler for the `api/auth/callback` route.
*
* @category Server
*/
export type HandleCallback = (req: NextApiRequest, res: NextApiResponse, options?: CallbackOptions) => Promise<void>;
/**
* @ignore
*/
const idTokenValidator =
(afterCallback?: AfterCallback, organization?: string): AfterCallback =>
(req, res, session, state) => {
if (organization) {
assert(session.user.org_id, 'Organization Id (org_id) claim must be a string present in the ID token');
assert.equal(
session.user.org_id,
organization,
`Organization Id (org_id) claim value mismatch in the ID token; ` +
`expected "${organization}", found "${session.user.org_id}"`
);
}
if (afterCallback) {
return afterCallback(req, res, session, state);
}
return session;
};
/**
* @ignore
*/
export default function handleCallbackFactory(handler: BaseHandleCallback, config: NextConfig): HandleCallback {
return async (req, res, options = {}): Promise<void> => {
try {
assertReqRes(req, res);
return await handler(req, res, {
...options,
afterCallback: idTokenValidator(options.afterCallback, options.organization || config.organization)
});
} catch (e: any) {
throw new HandlerError(e);
}
};
}