UNPKG

supertokens-node

Version:
418 lines (417 loc) 15.9 kB
// @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import NormalisedURLPath from "../../normalisedURLPath"; import OverrideableBuilder from "supertokens-js-override"; import { JSONObject, JSONValue, UserContext } from "../../types"; import { GeneralErrorResponse } from "../../types"; import RecipeUserId from "../../recipeUserId"; export type KeyInfo = { publicKey: string; expiryTime: number; createdAt: number; }; export type AntiCsrfType = "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE"; export type TokenInfo = { token: string; expiry: number; createdTime: number; }; export type CreateOrRefreshAPIResponse = { session: { handle: string; userId: string; recipeUserId: RecipeUserId; userDataInJWT: any; tenantId: string; }; accessToken: TokenInfo; refreshToken: TokenInfo; antiCsrfToken: string | undefined; }; export interface ErrorHandlers { onUnauthorised?: ErrorHandlerMiddleware; onTryRefreshToken?: ErrorHandlerMiddleware; onTokenTheftDetected?: TokenTheftErrorHandlerMiddleware; onInvalidClaim?: InvalidClaimErrorHandlerMiddleware; onClearDuplicateSessionCookies?: ErrorHandlerMiddleware; } export type TokenType = "access" | "refresh"; export type TokenTransferMethod = "header" | "cookie"; export type TypeInput = { useDynamicAccessTokenSigningKey?: boolean; sessionExpiredStatusCode?: number; invalidClaimStatusCode?: number; accessTokenPath?: string; cookieSecure?: boolean; cookieSameSite?: "strict" | "lax" | "none"; cookieDomain?: string; olderCookieDomain?: string; getTokenTransferMethod?: (input: { req: BaseRequest; forCreateNewSession: boolean; userContext: UserContext; }) => TokenTransferMethod | "any"; getCookieNameForTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; getResponseHeaderNameForTokenType?: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; errorHandlers?: ErrorHandlers; antiCsrf?: "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE"; exposeAccessTokenToFrontendInCookieBasedAuth?: boolean; jwksRefreshIntervalSec?: number; override?: { functions?: ( originalImplementation: RecipeInterface, builder: OverrideableBuilder<RecipeInterface> ) => RecipeInterface; apis?: (originalImplementation: APIInterface, builder: OverrideableBuilder<APIInterface>) => APIInterface; }; }; export type TypeNormalisedInput = { useDynamicAccessTokenSigningKey: boolean; refreshTokenPath: NormalisedURLPath; accessTokenPath: NormalisedURLPath; cookieDomain: string | undefined; olderCookieDomain: string | undefined; getCookieSameSite: (input: { request: BaseRequest | undefined; userContext: UserContext; }) => "strict" | "lax" | "none"; cookieSecure: boolean; getCookieNameForTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; getResponseHeaderNameForTokenType: (req: BaseRequest, tokenType: TokenType, userContext: UserContext) => string; sessionExpiredStatusCode: number; errorHandlers: NormalisedErrorHandlers; antiCsrfFunctionOrString: | "VIA_TOKEN" | "VIA_CUSTOM_HEADER" | "NONE" | ((input: { request: BaseRequest | undefined; userContext: UserContext }) => "VIA_CUSTOM_HEADER" | "NONE"); getTokenTransferMethod: (input: { req: BaseRequest; forCreateNewSession: boolean; userContext: UserContext; }) => TokenTransferMethod | "any"; invalidClaimStatusCode: number; exposeAccessTokenToFrontendInCookieBasedAuth: boolean; jwksRefreshIntervalSec: number; override: { functions: ( originalImplementation: RecipeInterface, builder: OverrideableBuilder<RecipeInterface> ) => RecipeInterface; apis: (originalImplementation: APIInterface, builder: OverrideableBuilder<APIInterface>) => APIInterface; }; }; export interface SessionRequest extends BaseRequest { session?: SessionContainerInterface; } export interface ErrorHandlerMiddleware { (message: string, request: BaseRequest, response: BaseResponse, userContext: UserContext): Promise<void>; } export interface TokenTheftErrorHandlerMiddleware { ( sessionHandle: string, userId: string, recipeUserId: RecipeUserId, request: BaseRequest, response: BaseResponse, userContext: UserContext ): Promise<void>; } export interface InvalidClaimErrorHandlerMiddleware { ( validatorErrors: ClaimValidationError[], request: BaseRequest, response: BaseResponse, userContext: UserContext ): Promise<void>; } export interface NormalisedErrorHandlers { onUnauthorised: ErrorHandlerMiddleware; onTryRefreshToken: ErrorHandlerMiddleware; onTokenTheftDetected: TokenTheftErrorHandlerMiddleware; onInvalidClaim: InvalidClaimErrorHandlerMiddleware; onClearDuplicateSessionCookies: ErrorHandlerMiddleware; } export interface VerifySessionOptions { antiCsrfCheck?: boolean; sessionRequired?: boolean; checkDatabase?: boolean; overrideGlobalClaimValidators?: ( globalClaimValidators: SessionClaimValidator[], session: SessionContainerInterface, userContext: UserContext ) => Promise<SessionClaimValidator[]> | SessionClaimValidator[]; } export type RecipeInterface = { createNewSession(input: { userId: string; recipeUserId: RecipeUserId; accessTokenPayload?: any; sessionDataInDatabase?: any; disableAntiCsrf?: boolean; tenantId: string; userContext: UserContext; }): Promise<SessionContainerInterface>; getGlobalClaimValidators(input: { tenantId: string; userId: string; recipeUserId: RecipeUserId; claimValidatorsAddedByOtherRecipes: SessionClaimValidator[]; userContext: UserContext; }): Promise<SessionClaimValidator[]> | SessionClaimValidator[]; getSession(input: { accessToken: string | undefined; antiCsrfToken?: string; options?: VerifySessionOptions; userContext: UserContext; }): Promise<SessionContainerInterface | undefined>; refreshSession(input: { refreshToken: string; antiCsrfToken?: string; disableAntiCsrf: boolean; userContext: UserContext; }): Promise<SessionContainerInterface>; /** * Used to retrieve all session information for a given session handle. Can be used in place of: * - getSessionDataFromDatabase * - getAccessTokenPayload * * Returns undefined if the sessionHandle does not exist */ getSessionInformation(input: { sessionHandle: string; userContext: UserContext; }): Promise<SessionInformation | undefined>; revokeAllSessionsForUser(input: { userId: string; revokeSessionsForLinkedAccounts: boolean; tenantId: string; revokeAcrossAllTenants?: boolean; userContext: UserContext; }): Promise<string[]>; getAllSessionHandlesForUser(input: { userId: string; fetchSessionsForAllLinkedAccounts: boolean; tenantId: string; fetchAcrossAllTenants?: boolean; userContext: UserContext; }): Promise<string[]>; revokeSession(input: { sessionHandle: string; userContext: UserContext }): Promise<boolean>; revokeMultipleSessions(input: { sessionHandles: string[]; userContext: UserContext }): Promise<string[]>; updateSessionDataInDatabase(input: { sessionHandle: string; newSessionData: any; userContext: UserContext; }): Promise<boolean>; mergeIntoAccessTokenPayload(input: { sessionHandle: string; accessTokenPayloadUpdate: JSONObject; userContext: UserContext; }): Promise<boolean>; /** * @returns {Promise<boolean>} Returns false if the sessionHandle does not exist */ regenerateAccessToken(input: { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext; }): Promise< | { status: "OK"; session: { handle: string; userId: string; recipeUserId: RecipeUserId; userDataInJWT: any; tenantId: string; }; accessToken?: { token: string; expiry: number; createdTime: number; }; } | undefined >; validateClaims(input: { userId: string; recipeUserId: RecipeUserId; accessTokenPayload: any; claimValidators: SessionClaimValidator[]; userContext: UserContext; }): Promise<{ invalidClaims: ClaimValidationError[]; accessTokenPayloadUpdate?: any; }>; fetchAndSetClaim(input: { sessionHandle: string; claim: SessionClaim<any>; userContext: UserContext; }): Promise<boolean>; setClaimValue<T>(input: { sessionHandle: string; claim: SessionClaim<T>; value: T; userContext: UserContext; }): Promise<boolean>; getClaimValue<T>(input: { sessionHandle: string; claim: SessionClaim<T>; userContext: UserContext }): Promise< | { status: "SESSION_DOES_NOT_EXIST_ERROR"; } | { status: "OK"; value: T | undefined; } >; removeClaim(input: { sessionHandle: string; claim: SessionClaim<any>; userContext: UserContext }): Promise<boolean>; }; export interface SessionContainerInterface { revokeSession(userContext?: Record<string, any>): Promise<void>; getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>; updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>; getUserId(userContext?: Record<string, any>): string; getRecipeUserId(userContext?: Record<string, any>): RecipeUserId; getTenantId(userContext?: Record<string, any>): string; getAccessTokenPayload(userContext?: Record<string, any>): any; getHandle(userContext?: Record<string, any>): string; getAllSessionTokensDangerously(): { accessToken: string; refreshToken: string | undefined; antiCsrfToken: string | undefined; frontToken: string; accessAndFrontTokenUpdated: boolean; }; getAccessToken(userContext?: Record<string, any>): string; mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>; getTimeCreated(userContext?: Record<string, any>): Promise<number>; getExpiry(userContext?: Record<string, any>): Promise<number>; assertClaims(claimValidators: SessionClaimValidator[], userContext?: Record<string, any>): Promise<void>; fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>; setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>; getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<T | undefined>; removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>; attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): Promise<void> | void; } export type APIOptions = { recipeImplementation: RecipeInterface; config: TypeNormalisedInput; recipeId: string; isInServerlessEnv: boolean; req: BaseRequest; res: BaseResponse; }; export type APIInterface = { /** * We do not add a GeneralErrorResponse response to this API * since it's not something that is directly called by the user on the * frontend anyway */ refreshPOST: | undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<SessionContainerInterface>); signOutPOST: | undefined | ((input: { options: APIOptions; session: SessionContainerInterface; userContext: UserContext }) => Promise< | { status: "OK"; } | GeneralErrorResponse >); verifySession(input: { verifySessionOptions: VerifySessionOptions | undefined; options: APIOptions; userContext: UserContext; }): Promise<SessionContainerInterface | undefined>; }; export type SessionInformation = { sessionHandle: string; userId: string; recipeUserId: RecipeUserId; sessionDataInDatabase: any; expiry: number; customClaimsInAccessTokenPayload: any; timeCreated: number; tenantId: string; }; export type ClaimValidationResult = | { isValid: true; } | { isValid: false; reason?: JSONValue; }; export type ClaimValidationError = { id: string; reason?: JSONValue; }; export type SessionClaimValidator = ( | // We split the type like this to express that either both claim and shouldRefetch is defined or neither. { claim: SessionClaim<any>; /** * Decides if we need to refetch the claim value before checking the payload with `isValid`. * E.g.: if the information in the payload is expired, or is not sufficient for this check. */ shouldRefetch: (payload: any, userContext: UserContext) => Promise<boolean> | boolean; } | {} ) & { id: string; /** * Decides if the claim is valid based on the payload (and not checking DB or anything else) */ validate: (payload: any, userContext: UserContext) => Promise<ClaimValidationResult>; }; export declare abstract class SessionClaim<T> { readonly key: string; constructor(key: string); /** * This methods fetches the current value of this claim for the user. * The undefined return value signifies that we don't want to update the claim payload and or the claim value is not present in the database * This can happen for example with a second factor auth claim, where we don't want to add the claim to the session automatically. */ abstract fetchValue( userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext ): Promise<T | undefined> | T | undefined; /** * Saves the provided value into the payload, by cloning and updating the entire object. * * @returns The modified payload object */ abstract addToPayload_internal(payload: JSONObject, value: T, userContext: UserContext): JSONObject; /** * Removes the claim from the payload by setting it to null, so mergeIntoAccessTokenPayload clears it * * @returns The modified payload object */ abstract removeFromPayloadByMerge_internal(payload: JSONObject, userContext: UserContext): JSONObject; /** * Removes the claim from the payload, by cloning and updating the entire object. * * @returns The modified payload object */ abstract removeFromPayload(payload: JSONObject, userContext: UserContext): JSONObject; /** * Gets the value of the claim stored in the payload * * @returns Claim value */ abstract getValueFromPayload(payload: JSONObject, userContext: UserContext): T | undefined; build( userId: string, recipeUserId: RecipeUserId, tenantId: string, currentPayload: JSONObject | undefined, userContext: UserContext ): Promise<JSONObject>; } export type ReqResInfo = { res: BaseResponse; req: BaseRequest; transferMethod: TokenTransferMethod; };