UNPKG

jwt-smith

Version:

Enhanced JWT Authentication and Authorization Module

1 lines 64.8 kB
{"version":3,"sources":["../src/lib/core.ts","../src/lib/logger.ts","../src/lib/signing-token.ts","../src/lib/verify-token.ts","../src/helper/utils.ts","../src/module/token-storage.ts","../src/module/refresh-token-handler.ts","../src/middleware/auth-header-verification.middleware.ts","../src/middleware/role-based-authentication.middleware.ts","../src/helper/constants.ts","../src/middleware/auth-cookie-verification.middleware.ts"],"sourcesContent":["import Joi from 'joi';\nimport { Logger, PublicKey, Secret, SignTokenOptions, VerifyTokenOptions, MiddlewareConfigsOptions } from './custom.d';\nimport { log, logFormat, setLogger } from './logger';\nimport { setDefaultSignOptions } from './signing-token';\nimport { setDefaultVerifyOptions } from './verify-token';\nimport {\n\tdefaultTokenGenerationHandler,\n\tdefaultExtractApiVersion,\n\textractAuthHeaderValue,\n\tdefaultRefreshTokenPayloadVerifier,\n\tdefaultRefreshTokenHolderVerifier,\n} from '../helper/utils';\n\n/**\n * Public key for token verification.\n *\n * @type Secret | PublicKey\n */\nexport let publicKey: Secret | PublicKey;\n\n/**\n * Refresh token key for token verification.\n *\n * @type Secret | PublicKey\n */\nexport let refreshTokenKey: Secret | PublicKey;\n\n/**\n * Middleware configurations.\n * These configurations will be used by the middleware.\n * The user can override these configurations by providing their own configurations.\n * The middleware will use these configurations if not provided by the user.\n *\n * @type MiddlewareConfigsOptions\n */\nexport let middlewareConfigs: MiddlewareConfigsOptions = {\n\ttokenStorage: undefined,\n\tauthHeaderName: 'authorization',\n\tappendToRequest: [],\n\tcookieSettings: { accessTokenCookieName: 'accessToken', accessCookieOptions: {}, refreshTokenCookieName: undefined },\n\tauthTokenExtractor: extractAuthHeaderValue,\n\ttokenGenerationHandler: defaultTokenGenerationHandler,\n\trefreshTokenPayloadVerifier: defaultRefreshTokenPayloadVerifier,\n\trefreshTokenHolderVerifier: defaultRefreshTokenHolderVerifier,\n\textractApiVersion: defaultExtractApiVersion,\n};\n/**\n * Default configurations.\n * These configurations will be used if not provided by the user.\n * The user can override these configurations by providing their own configurations.\n *\n * @interface ConfigOptions\n */\ninterface ConfigOptions {\n\t/**\n\t * Logger instance.\n\t * If not provided, the library will use the default logger.\n\t * The user can provide their own logger instance.\n\t * The library will use the provided logger instance.\n\t * The logger instance should implement the Logger interface.\n\t * The logger instance should have the following methods:\n\t * - info\n\t * - warn\n\t * - error\n\t * - debug\n\t *\n\t * @type {Logger}\n\t * @memberof ConfigOptions\n\t */\n\tlogger?: Logger;\n\t/**\n\t * Public key for token verification.\n\t * If not provided, the library will use the default public key.\n\t * The user can provide their own public key.\n\t * The library will use the provided public key.\n\t *\n\t * @type {Secret | PublicKey}\n\t * @memberof ConfigOptions\n\t */\n\tpublicKey?: Secret | PublicKey;\n\t/**\n\t * Refresh token key for token verification.\n\t * If not provided, the library will use the default refresh token key.\n\t * The user can provide their own refresh token key.\n\t * The library will use the provided refresh token key.\n\t *\n\t * @type {Secret | PublicKey}\n\t * @memberof ConfigOptions\n\t */\n\trefreshTokenKey?: Secret | PublicKey;\n\t/**\n\t * Default sign options.\n\t * If not provided, the library will use the default sign options.\n\t * The user can provide their own sign options.\n\t * The library will use the provided sign options.\n\t *\n\t * @type {SignTokenOptions}\n\t * @memberof ConfigOptions\n\t */\n\tsignOptions?: SignTokenOptions;\n\t/**\n\t * Default verify options.\n\t * If not provided, the library will use the default verify options.\n\t * The user can provide their own verify options.\n\t * The library will use the provided verify options.\n\t *\n\t * @type {VerifyTokenOptions}\n\t * @memberof ConfigOptions\n\t */\n\tverifyOptions?: VerifyTokenOptions;\n\t/**\n\t * Middleware configurations.\n\t * These configurations will be used by the middleware.\n\t * The user can override these configurations by providing their own configurations.\n\t * The middleware will use these configurations if not provided by the user.\n\t *\n\t * @type {MiddlewareConfigsOptions}\n\t * @memberof ConfigOptions\n\t */\n\tmiddlewareConfigs?: MiddlewareConfigsOptions;\n}\n\nconst secretSchema = Joi.alternatives().try(\n\tJoi.string(),\n\tJoi.binary(),\n\tJoi.object().instance(Buffer),\n\tJoi.object({\n\t\tkey: Joi.alternatives().try(Joi.string(), Joi.binary()),\n\t\tpassphrase: Joi.string(),\n\t}),\n);\n\nconst configOptionsSchema = Joi.object<ConfigOptions>({\n\tlogger: Joi.object<Logger>().optional(),\n\tpublicKey: secretSchema.optional(),\n\trefreshTokenKey: secretSchema.optional(),\n\tsignOptions: Joi.object<SignTokenOptions>().optional(),\n\tverifyOptions: Joi.object<VerifyTokenOptions>().optional(),\n\tmiddlewareConfigs: Joi.object<MiddlewareConfigsOptions>().optional(),\n});\n\n/**\n * Configures the library with the provided options.\n * This function should be called before using any other functions.\n * If not called, the library will use the default configurations.\n * If called multiple times, the latest configurations will be used.\n * If the provided options are invalid, an error will be thrown.\n *\n * @param {ConfigOptions} options\n * @throws {Error} Parameter Validation Error\n * @returns {void}\n *\n * @example\n * ```typescript\n * import { JwtManager } from 'jwt-smith';\n *\n * JwtManager({\n * publicKey: 'public key',\n * refreshTokenKey: 'refresh token key',\n * signOptions: {\n * algorithm: 'RS256',\n * expiresIn: '1h',\n * },\n * verifyOptions: {\n * algorithms: ['RS256'],\n * },\n * middlewareConfigs: {\n * tokenStorage: new TokenStorage(),\n * authHeaderName: 'authorization',\n * appendToRequest: ['user'],\n * cookieSettings: {\n * accessTokenCookieName: 'access_token',\n * accessCookieOptions: {\n * httpOnly: true,\n * secure: true,\n * },\n * refreshTokenCookieName: 'refresh_token',\n * refreshCookieOptions: {\n * httpOnly: true,\n * secure: true,\n * },\n * },\n * });\n */\nexport const JwtManager = (options: ConfigOptions) => {\n\tconst { error, warning, value } = configOptionsSchema.validate(options);\n\n\tif (error) {\n\t\tthrow new Error(logFormat(`Parameter Validation Error: ${error.message}`));\n\t} else if (warning) {\n\t\tlog('warn', 'Parameter Validation Warning:', { warning_details: warning });\n\t}\n\n\tif (value.logger) setLogger(value.logger);\n\n\tif (value.publicKey) publicKey = value.publicKey;\n\tif (value.refreshTokenKey) refreshTokenKey = value.refreshTokenKey;\n\n\tif (value.signOptions) setDefaultSignOptions(value.signOptions);\n\tif (value.verifyOptions) setDefaultVerifyOptions(value.verifyOptions);\n\n\tif (value.middlewareConfigs) middlewareConfigs = { ...middlewareConfigs, ...value.middlewareConfigs };\n};\n","import { Logger } from './custom.d';\n\ntype LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ninterface LoggerOptions {\n\tsetPrefix: boolean;\n}\n\nconst LOGGER_PREFIX = '[JWT-Smith] ';\n\nlet currentLogger: Logger = console;\n\nlet logSettings: LoggerOptions = {\n\tsetPrefix: true,\n};\n\n/**\n * Sets the logger to be used by the library.\n * The logger should implement the Logger interface.\n * The logger should have the following methods:\n * - info\n * - warn\n * - error\n * - debug\n *\n * @param {Logger} logger\n * @param {LoggerOptions} [options]\n */\nexport const setLogger = (logger: Logger, options?: LoggerOptions): void => {\n\tcurrentLogger = logger;\n\n\tlogSettings = {\n\t\t...logSettings,\n\t\t...(options || {}),\n\t};\n};\n\n/**\n * Returns the current logger.\n * The logger can be the default console logger or a custom logger.\n * The logger should implement the Logger interface.\n *\n * @return {*} {Logger}\n */\nexport const getLogger = (): Logger => {\n\treturn currentLogger;\n};\n\n/**\n * Formats a log message.\n * If the setPrefix option is true, the message is prefixed with the LOGGER_PREFIX.\n *\n * @param {string} message\n * @return {*} {string}\n */\nexport const logFormat = (message: string): string => {\n\treturn `${logSettings.setPrefix ? LOGGER_PREFIX : ''}${message}`;\n};\n\n/**\n * Logs a message with the given log level.\n * The message is formatted using the logFormat function.\n *\n * @param {LogLevel} level\n * @param {string} message\n * @param {...unknown[]} args\n */\nexport const log = (level: LogLevel, message: string, ...args: unknown[]): void => {\n\tconst logger = getLogger();\n\n\tif (logger[level]) {\n\t\tlogger[level](logFormat(message), ...args);\n\t} else {\n\t\tconsole.error(logFormat(`Invalid log level: ${level}`));\n\t}\n};\n","import jsonwebtoken, { JwtHeader } from 'jsonwebtoken';\nimport Joi from 'joi';\n\nimport { log, logFormat } from './logger';\nimport { Algorithm, PrivateKey, Secret, SignTokenOptions } from './custom.d';\n\n/**\n * Sign token parameters.\n * The payload can be a string, object, or binary.\n * The secret can be a string, binary, or object.\n * The options are optional.\n *\n * @interface SignTokenParams\n */\ninterface SignTokenParams {\n\tpayload: string | Buffer | object;\n\tsecret: Secret | PrivateKey;\n\toptions?: SignTokenOptions;\n}\n\nconst signTokenOptionsSchema = Joi.object<SignTokenOptions>({\n\talgorithm: Joi.string<Algorithm>().default('HS256'),\n\tkeyid: Joi.string(),\n\texpiresIn: Joi.alternatives().try(Joi.string(), Joi.number()),\n\tnotBefore: Joi.alternatives().try(Joi.string(), Joi.number()),\n\taudience: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())),\n\tsubject: Joi.string(),\n\tissuer: Joi.string(),\n\tjwtid: Joi.string(),\n\tmutatePayload: Joi.bool(),\n\tnoTimestamp: Joi.bool(),\n\theader: Joi.object<JwtHeader>(),\n\tencoding: Joi.string(),\n\tallowInsecureKeySizes: Joi.bool(),\n\tallowInvalidAsymmetricKeyTypes: Joi.boolean(),\n});\n\nconst secretSchema = Joi.alternatives()\n\t.try(\n\t\tJoi.string(),\n\t\tJoi.binary(),\n\t\tJoi.object().instance(Buffer),\n\t\tJoi.object({\n\t\t\tkey: Joi.alternatives().try(Joi.string(), Joi.binary()),\n\t\t\tpassphrase: Joi.string(),\n\t\t}),\n\t)\n\t.not('');\n\nconst signTokenParamsSchema = Joi.object<SignTokenParams>({\n\tpayload: Joi.alternatives().try(Joi.string(), Joi.object(), Joi.binary()).required(),\n\tsecret: secretSchema.required(),\n\toptions: signTokenOptionsSchema.optional(),\n});\n\nlet defaultSignOptions: SignTokenOptions = {};\n\n/**\n * Signs a JSON Web Token (JWT) with the given parameters.\n *\n * @param {SignTokenParams} parameters - The parameters for signing the token.\n * @param {string | Buffer | object} parameters.payload - The payload to sign, which can be a string, object, or binary.\n * @param {Secret | PrivateKey} parameters.secret - The secret or private key to sign the token with.\n * @param {SignTokenOptions} [parameters.options] - Optional signing options.\n * @returns {Promise<string | undefined>} A promise that resolves to the signed JWT as a string, or undefined if an error occurs.\n *\n * @throws {Error} If parameter validation fails.\n *\n * @example\n * ```typescript\n * const token = await sign({\n * payload: { userId: 123 },\n * secret: 'your-256-bit-secret',\n * options: { expiresIn: '1h' }\n * });\n * console.log(token);\n * ```\n */\nexport const sign = (parameters: SignTokenParams): Promise<string | undefined> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst { error, warning, value } = signTokenParamsSchema.validate(parameters);\n\n\t\tif (error) {\n\t\t\treject(logFormat(`Parameter Validation Error: ${error.message}`));\n\t\t} else if (warning) {\n\t\t\tlog('warn', 'Parameter Validation Warning:', { warning_details: warning });\n\t\t}\n\n\t\tconst { payload, secret, options = {} } = value;\n\t\tconst signOptions = { ...defaultSignOptions, ...options };\n\n\t\tjsonwebtoken.sign(payload, secret, signOptions, (err, token) => {\n\t\t\tif (err) {\n\t\t\t\treject(err);\n\t\t\t} else {\n\t\t\t\tresolve(token);\n\t\t\t}\n\t\t});\n\t});\n};\n\n/**\n * Sets the default signing options for tokens.\n *\n * This function validates the provided options against the `signTokenOptionsSchema`.\n * If the validation fails, it throws an error with a detailed message.\n * If there are any warnings during validation, it logs a warning message.\n * Upon successful validation, it sets the `defaultSignOptions` to the validated value.\n *\n * @param options - The signing options to be validated and set as default.\n * @throws {Error} If the validation of the options fails.\n */\nexport const setDefaultSignOptions = (options: SignTokenOptions): void => {\n\tconst { error, warning, value } = signTokenOptionsSchema.validate(options);\n\n\tif (error) {\n\t\tthrow new Error(logFormat(`Parameter Validation Error: ${error.message}`));\n\t} else if (warning) {\n\t\tlog('warn', 'Parameter Validation Warning:', { warning_details: warning });\n\t}\n\n\tdefaultSignOptions = value;\n};\n","import jsonwebtoken from 'jsonwebtoken';\nimport Joi from 'joi';\nimport { log, logFormat } from './logger';\n\nimport { VerifyTokenOptions, PublicKey, Secret, VerifyResponse } from './custom.d';\n\n/**\n * Verify token parameters.\n * The token should be string.\n * The secret can be a string, binary, or object.\n * The options are optional.\n *\n * @interface SignTokenParams\n */\ninterface VerifyTokenParams {\n\ttoken: string;\n\tsecret: Secret | PublicKey;\n\toptions?: VerifyTokenOptions;\n}\n\nconst verifyTokenOptionsSchema = Joi.object<VerifyTokenOptions>({\n\talgorithms: Joi.array().items(Joi.string()),\n\taudience: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())),\n\tclockTimestamp: Joi.number(),\n\tclockTolerance: Joi.number(),\n\tcomplete: Joi.bool(),\n\tissuer: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())),\n\tignoreExpiration: Joi.boolean(),\n\tignoreNotBefore: Joi.boolean(),\n\tjwtid: Joi.string(),\n\tnonce: Joi.string(),\n\tsubject: Joi.string(),\n\tmaxAge: Joi.alternatives().try(Joi.string(), Joi.number()),\n\tallowInvalidAsymmetricKeyTypes: Joi.boolean(),\n});\n\nconst secretSchema = Joi.alternatives().try(\n\tJoi.string().not(''),\n\tJoi.binary(),\n\tJoi.object().instance(Buffer),\n\tJoi.object({\n\t\tkey: Joi.alternatives().try(Joi.string(), Joi.binary()),\n\t\tpassphrase: Joi.string(),\n\t}),\n);\n\nconst verifyTokenParamsSchema = Joi.object<VerifyTokenParams>({\n\ttoken: Joi.string().not('').required(),\n\tsecret: secretSchema.required(),\n\toptions: verifyTokenOptionsSchema.optional(),\n});\n\nlet defaultVerifyOptions: VerifyTokenOptions = {};\n\n/**\n * Verifies a JSON Web Token (JWT) using the provided parameters.\n *\n * @param {VerifyTokenParams} parameters - The parameters required to verify the token.\n * @param {string} parameters.token - The JWT to be verified.\n * @param {Secret | PublicKey} parameters.secret - The secret or public key to verify the token.\n * @param {VerifyTokenOptions} [parameters.options] - Optional verification options.\n *\n * @returns {Promise<VerifyResponse>} A promise that resolves with the decoded token if verification is successful, or rejects with an error if verification fails.\n *\n * @throws {Error} If the parameter validation fails.\n *\n * @example\n * ```typescript\n * const decodedToken = await verify({\n * token: 'awsajhd.suf.aoefyao',\n * secret: 'your-256-bit-secret',\n * options: { subject: 'auth-app-user' }\n * });\n * console.log(decodedToken);\n * ```\n */\nexport const verify = (parameters: VerifyTokenParams): Promise<VerifyResponse> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst { error, warning, value } = verifyTokenParamsSchema.validate(parameters);\n\n\t\tif (error) {\n\t\t\treject(logFormat(`Parameter Validation Error: ${error.message}`));\n\t\t} else if (warning) {\n\t\t\tlog('warn', 'Parameter Validation Warning:', { warning_details: warning });\n\t\t}\n\n\t\tconst { token, secret, options = {} } = value;\n\t\tconst verifyOptions = { ...defaultVerifyOptions, options };\n\n\t\tjsonwebtoken.verify(token, secret, verifyOptions, (err, decoded) => {\n\t\t\tif (err) {\n\t\t\t\treject(err);\n\t\t\t} else {\n\t\t\t\tresolve(decoded);\n\t\t\t}\n\t\t});\n\t});\n};\n\n/**\n * Sets the default options for verifying tokens.\n *\n * This function validates the provided options against the `verifyTokenOptionsSchema`.\n * If the validation fails, it throws an error with a detailed message.\n * If there are warnings during validation, it logs a warning message.\n * Upon successful validation, it sets the `defaultVerifyOptions` to the validated value.\n *\n * @param {VerifyTokenOptions} options - The options to be validated and set as default.\n * @throws {Error} Throws an error if the validation of the options fails.\n */\nexport const setDefaultVerifyOptions = (options: VerifyTokenOptions): void => {\n\tconst { error, warning, value } = verifyTokenOptionsSchema.validate(options);\n\n\tif (error) {\n\t\tthrow new Error(logFormat(`Parameter Validation Error: ${error.message}`));\n\t} else if (warning) {\n\t\tlog('warn', 'Parameter Validation Warning:', { warning_details: warning });\n\t}\n\n\tdefaultVerifyOptions = value;\n};\n","import {\n\tAppendToRequest,\n\tAppendToRequestProperties,\n\tAuthedRequest,\n\tAuthTokenPayloadVerifier,\n\tAuthUser,\n\tExtractApiVersion,\n\tRefreshTokenHolderVerifier,\n\tRefreshTokenPayloadVerifier,\n\tRequestAppends,\n\tTokenGenerationHandler,\n\tVerifyResponse,\n} from '../lib/custom';\nimport { log } from '../lib/logger';\n\n/**\n * Extracts the token value from the Authorization header.\n * The token value is the second part of the header, separated by a space.\n * The token value is returned as a string.\n * If the header is empty or the token value is not found, it returns `undefined`.\n *\n * @param {string} header\n * @return {*} {(string | undefined)}\n */\nexport const extractAuthHeaderValue = (header: string): string | undefined => {\n\tlet tokenValue;\n\n\tif (header && header.split(' ')[1]) {\n\t\ttokenValue = header.split(' ')[1] as string;\n\t}\n\n\treturn tokenValue;\n};\n\n/**\n * Appends the token payload to the request.\n * If the token payload is a string, it appends it to the request as `tokenPayload`.\n * If the token payload is an object, it appends the properties to the request as specified in the `appendToRequest` array.\n *\n * @param {AuthedRequest} req\n * @param {AppendToRequest} appendToRequest\n * @param {VerifyResponse} decodedTokenPayload\n */\nexport const appendTokenPayloadToRequest = (\n\treq: AuthedRequest,\n\tappendToRequest: AppendToRequest,\n\tdecodedTokenPayload: VerifyResponse,\n) => {\n\tif (\n\t\tArray.isArray(appendToRequest) &&\n\t\tappendToRequest?.length > 0 &&\n\t\tdecodedTokenPayload &&\n\t\ttypeof decodedTokenPayload !== 'string'\n\t) {\n\t\tlog('debug', `Properties to append to the request: ${appendToRequest}`);\n\n\t\ttry {\n\t\t\tconst castedPayload: RequestAppends = decodedTokenPayload as RequestAppends;\n\n\t\t\tappendToRequest.forEach((item: AppendToRequestProperties) => {\n\t\t\t\tif (Object.hasOwn(castedPayload, item)) {\n\t\t\t\t\treq[item] = castedPayload[item] as unknown as (AuthUser & (string | (string & string[]))) | undefined;\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlog('error', 'Token payload appending to the request failed!', error);\n\t\t}\n\t} else if (typeof appendToRequest === 'boolean' && typeof decodedTokenPayload === 'string') {\n\t\tlog('debug', `Token payload appending to the request: ${decodedTokenPayload}`);\n\n\t\treq.tokenPayload = decodedTokenPayload;\n\t}\n};\n\n/**\n * Default token generation handler.\n * This function is used when the token generation handler is not implemented.\n *\n * @param {VerifyResponse} refreshTokenPayload\n * @return {*} {Promise<{ token: string; refreshToken: string }>}\n */\nexport const defaultTokenGenerationHandler: TokenGenerationHandler = async (refreshTokenPayload: VerifyResponse) => {\n\tif (process.env.NODE_ENV === 'production') {\n\t\tthrow new Error('Token generation handler not implemented.');\n\t} else {\n\t\tlog('warn', 'Token generation handler not implemented. Using default handler.');\n\t\tconsole.debug({ refreshTokenPayload });\n\t}\n\n\treturn {\n\t\ttoken: 'new-token',\n\t\trefreshToken: 'new-refresh-token',\n\t};\n};\n\n/**\n * Default auth token payload verifier.\n * This function is used when the auth token payload verifier is not implemented.\n * It checks if the token payload is empty.\n * If the token payload is empty, it throws an error.\n * Otherwise, it returns `void`.\n *\n * @param {VerifyResponse} tokenPayload\n * @return {*} {Promise<void>}\n */\nexport const defaultAuthTokenPayloadVerifier: AuthTokenPayloadVerifier = async (\n\ttokenPayload: VerifyResponse,\n): Promise<void> => {\n\tif (!tokenPayload) {\n\t\tthrow new Error('Empty payload in the auth token.');\n\t}\n};\n\n/**\n * Default refresh token payload verifier.\n * This function is used when the refresh token payload verifier is not implemented.\n * It checks if the user ID is present in the token payload.\n * If the user ID is not found, it throws an error.\n * Otherwise, it returns `void`.\n *\n * @param {VerifyResponse} tokenPayload\n * @return {*} {Promise<void>}\n */\nexport const defaultRefreshTokenPayloadVerifier: RefreshTokenPayloadVerifier = async (\n\ttokenPayload: VerifyResponse,\n): Promise<void> => {\n\tconst user: Record<string, unknown> = (tokenPayload as unknown as Record<string, unknown>)?.user as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tconst userId = user?.id;\n\n\tif (!userId) {\n\t\tthrow new Error('Refresh token process failed. User ID not found in the refresh payload.');\n\t}\n};\n\n/**\n * Default refresh token holder verifier.\n * This function is used when the refresh token holder verifier is not implemented.\n * It checks if the user ID in the token holder matches the user ID in the token payload.\n * If the user IDs do not match, it returns `false`.\n * Otherwise, it returns `true`.\n *\n * @param {Record<string, unknown>} tokenHolder\n * @param {VerifyResponse} tokenPayload\n * @return {*} {Promise<boolean>}\n */\nexport const defaultRefreshTokenHolderVerifier: RefreshTokenHolderVerifier = async (\n\ttokenHolder: Record<string, unknown>,\n\ttokenPayload: VerifyResponse,\n): Promise<boolean> => {\n\tconst user: Record<string, unknown> = (tokenPayload as unknown as Record<string, unknown>)?.user as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tconst userId = user?.id || user?.userId;\n\tconst tokenHolderId = tokenHolder?.id || tokenHolder?.userId;\n\n\treturn tokenHolderId === userId;\n};\n\n/**\n * Default extract API version.\n * This function is used when the extract API version is not implemented.\n * It checks the `api-version` header in the request.\n * If the header is not found, it returns the first part of the base URL.\n * Otherwise, it returns `undefined`.\n * The API version is returned as a string.\n * If the API version is not found, it returns `undefined`.\n *\n * @param {AuthedRequest} req\n * @return {*} {(Promise<string | undefined>)}\n */\nexport const defaultExtractApiVersion: ExtractApiVersion = async (req: AuthedRequest): Promise<string | undefined> => {\n\tconst version = req.headers['api-version'] as string;\n\treturn version ?? req.baseUrl.split('/')[1];\n};\n","import { TokenStorage } from '../lib/custom';\n\ninterface tokenEntity {\n\trefreshTokens: string[];\n\ttokens?: string[];\n}\n\nexport default class DefaultTokenStorage implements TokenStorage {\n\tprivate tokens = new Map<string, tokenEntity>();\n\tprivate defectedTokens = new Map<string, Record<string, unknown>>();\n\n\tasync saveOrUpdateToken(userId: string, tokenOrRefreshToken: string, token?: string): Promise<void> {\n\t\tconst existingData = this.tokens.get(userId) || { refreshTokens: [], tokens: [] };\n\t\tlet update: tokenEntity = { refreshTokens: [] };\n\n\t\tupdate = {\n\t\t\t...existingData,\n\t\t\trefreshTokens: [...(existingData.refreshTokens || []), tokenOrRefreshToken],\n\t\t};\n\n\t\tif (token) {\n\t\t\tupdate = {\n\t\t\t\t...update,\n\t\t\t\ttokens: [...(existingData.tokens || []), token],\n\t\t\t};\n\t\t}\n\n\t\tthis.tokens.set(userId, update);\n\t}\n\n\tasync getRefreshTokenHolder(refreshToken: string): Promise<Record<string, unknown> | null> {\n\t\tlet holder = null;\n\n\t\tthis.tokens.forEach((data, userId) => {\n\t\t\tif (data.refreshTokens?.includes(refreshToken)) {\n\t\t\t\tholder = { id: userId, ...this.tokens.get(userId) };\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t});\n\n\t\treturn holder;\n\t}\n\n\tasync getRefreshToken(userId: string): Promise<string[] | null> {\n\t\treturn this.tokens.get(userId)?.refreshTokens || null;\n\t}\n\n\tasync deleteToken(userId: string): Promise<void> {\n\t\tthis.tokens.delete(userId);\n\t}\n\n\tasync getToken?(userId: string): Promise<string[] | null> {\n\t\treturn this.tokens.get(userId)?.tokens || null;\n\t}\n\n\tasync blackListRefreshToken(token: string, relatedData?: Record<string, unknown>): Promise<void> {\n\t\tthis.defectedTokens.set(token, relatedData || {});\n\t}\n\n\tasync checkBlackListedRefreshToken(token: string): Promise<Record<string, unknown> | undefined> {\n\t\treturn this.defectedTokens.get(token);\n\t}\n}\n","import DefaultTokenStorage from './token-storage';\nimport { TokenExpiredError } from 'jsonwebtoken';\nimport {\n\tTokenStorage,\n\tVerifyResponse,\n\tTokenGenerationHandler,\n\tRefreshTokenPayloadVerifier,\n\tRefreshTokenHolderVerifier,\n\tAuthTokenPayloadVerifier,\n} from '../lib/custom.d';\nimport { RefreshTokenHandlerOptions, ValidateResponse } from './internal';\nimport { publicKey, refreshTokenKey } from '../lib/core';\nimport { log } from '../lib/logger';\nimport { verify } from '../lib/verify-token';\nimport {\n\tdefaultAuthTokenPayloadVerifier,\n\tdefaultRefreshTokenPayloadVerifier,\n\tdefaultRefreshTokenHolderVerifier,\n} from '../helper/utils';\n\n/* TODO: Next step is to implement the session handling. */\n\n/**\n * The `TokenHandler` class is responsible for handling authentication and refresh tokens.\n * It provides methods to validate authentication tokens, rotate refresh tokens, and manage token storage.\n */\nexport class TokenHandler {\n\tprivate refreshTokenStorage: TokenStorage;\n\tprivate tokenGenerationHandler: TokenGenerationHandler;\n\tprivate authTokenPayloadVerifier: AuthTokenPayloadVerifier;\n\tprivate refreshTokenPayloadVerifier: RefreshTokenPayloadVerifier;\n\tprivate refreshTokenHolderVerifier: RefreshTokenHolderVerifier;\n\n\tconstructor(options: RefreshTokenHandlerOptions) {\n\t\tthis.refreshTokenStorage = options.refreshTokenStorage || new DefaultTokenStorage();\n\t\tthis.tokenGenerationHandler = options.tokenGenerationHandler;\n\t\tthis.authTokenPayloadVerifier = options.authTokenPayloadVerifier || defaultAuthTokenPayloadVerifier;\n\t\tthis.refreshTokenPayloadVerifier = options.refreshTokenPayloadVerifier || defaultRefreshTokenPayloadVerifier;\n\t\tthis.refreshTokenHolderVerifier = options.refreshTokenHolderVerifier || defaultRefreshTokenHolderVerifier;\n\n\t\tif (!options.refreshTokenStorage) {\n\t\t\tconst logType = process.env.NODE_ENV === 'production' ? 'error' : 'warn';\n\t\t\tlog(logType, '[TokenHandler]: Using default in-memory token storage. This is not recommended for production.');\n\t\t}\n\t}\n\n\tasync validateOrRefreshAuthToken(authToken: string, refreshToken: string | undefined): Promise<ValidateResponse> {\n\t\tlet decodedToken: VerifyResponse;\n\t\tlet token: string = authToken;\n\t\tlet nextRefreshToken: string | undefined = refreshToken;\n\n\t\tawait this.validateAuthToken(authToken)\n\t\t\t.then((tokenPayload) => {\n\t\t\t\tdecodedToken = tokenPayload;\n\t\t\t})\n\t\t\t.catch(async (error) => {\n\t\t\t\tif (error instanceof TokenExpiredError && refreshToken) {\n\t\t\t\t\tconst response = await this.rotateRefreshToken(refreshToken, authToken);\n\n\t\t\t\t\ttoken = response.token;\n\t\t\t\t\tnextRefreshToken = response.refreshToken;\n\n\t\t\t\t\tdecodedToken = await verify({\n\t\t\t\t\t\ttoken,\n\t\t\t\t\t\tsecret: publicKey,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tlog('info', 'Refresh token not found.');\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t});\n\n\t\treturn { decodedToken, nextRefreshToken, token };\n\t}\n\n\tasync validateAuthToken(authToken: string): Promise<VerifyResponse> {\n\t\tconst tokenPayload = await verify({\n\t\t\ttoken: authToken,\n\t\t\tsecret: publicKey,\n\t\t});\n\n\t\tawait this.authTokenPayloadVerifier(tokenPayload);\n\n\t\tlog('info', 'Auth token validation complete!');\n\n\t\treturn tokenPayload;\n\t}\n\n\tasync rotateRefreshToken(refreshToken: string, token?: string): Promise<{ token: string; refreshToken: string }> {\n\t\ttry {\n\t\t\tconst isBlackListed = await this.refreshTokenStorage.checkBlackListedRefreshToken(refreshToken);\n\n\t\t\tlog('debug', 'Checking if the refresh token is blacklisted.');\n\n\t\t\tif (isBlackListed) {\n\t\t\t\tlog('error', 'Blacklisted refresh token received!', { refreshToken });\n\n\t\t\t\tthrow new Error('Blacklisted refresh token found!');\n\t\t\t}\n\n\t\t\tconst decodedTokenPayload = await verify({\n\t\t\t\ttoken: refreshToken,\n\t\t\t\tsecret: refreshTokenKey || publicKey,\n\t\t\t});\n\n\t\t\tlog('debug', 'Decoded the refresh token payload.');\n\n\t\t\tif (!decodedTokenPayload) {\n\t\t\t\tthrow new Error('Refresh token payload is undefined!');\n\t\t\t}\n\n\t\t\tawait this.refreshTokenPayloadVerifier(decodedTokenPayload);\n\n\t\t\tlog('debug', 'Refresh token payload verification complete.');\n\n\t\t\tconst tokenHolder = await this.refreshTokenStorage.getRefreshTokenHolder(refreshToken);\n\n\t\t\tlog('debug', 'Retrieved the refresh token holder.');\n\n\t\t\tif (!tokenHolder) {\n\t\t\t\tthrow new Error('Could not find a matching token holder for the refresh token.');\n\t\t\t}\n\n\t\t\tconst isHolderVerified = await this.refreshTokenHolderVerifier(tokenHolder, decodedTokenPayload);\n\n\t\t\tlog('debug', 'Refresh token holder verification complete.');\n\n\t\t\tif (!isHolderVerified) {\n\t\t\t\tawait this.refreshTokenStorage.blackListRefreshToken(refreshToken);\n\n\t\t\t\tthrow new Error('Refresh token holder verification failed.');\n\t\t\t}\n\n\t\t\tconst response = await this.tokenGenerationHandler(decodedTokenPayload, tokenHolder);\n\n\t\t\tlog('debug', 'Generated new tokens.');\n\n\t\t\tconst userId =\n\t\t\t\ttokenHolder.id ||\n\t\t\t\t(typeof decodedTokenPayload !== 'string' && 'user' in decodedTokenPayload\n\t\t\t\t\t? decodedTokenPayload.user?.id\n\t\t\t\t\t: undefined);\n\n\t\t\tawait this.refreshTokenStorage.saveOrUpdateToken(userId, response.refreshToken, response.token);\n\n\t\t\tlog('debug', 'Saved the new tokens.');\n\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tawait this.cleanupInvalidRefreshToken(refreshToken, token);\n\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async cleanupInvalidRefreshToken(refreshToken: string, token?: string): Promise<void> {\n\t\tconst tokenHolder = await this.refreshTokenStorage.getRefreshTokenHolder(refreshToken);\n\n\t\tif (tokenHolder && Object.hasOwn(tokenHolder, 'id')) {\n\t\t\tconst userId: string = tokenHolder.id as string;\n\t\t\tawait this.refreshTokenStorage.deleteToken(userId, token, refreshToken);\n\t\t}\n\t}\n}\n","import { NextFunction, Response } from 'express';\n\nimport { middlewareConfigs } from '../lib/core';\nimport { AuthedRequest } from '../lib/custom';\nimport { log } from '../lib/logger';\nimport { TokenHandler } from '../module/refresh-token-handler';\nimport { appendTokenPayloadToRequest } from '../helper/utils';\n\n/**\n * Middleware to validate JWT from the request header.\n *\n * This middleware extracts the JWT from the specified header, validates it, and optionally refreshes it if needed.\n * It also appends the decoded token payload to the request object for further use in the application.\n *\n * @param req - The authenticated request object.\n * @param res - The response object.\n * @param next - The next middleware function in the stack.\n *\n * @throws Will throw an error if the auth header is not found, the token is invalid, or if the token extraction method is not provided.\n *\n * @returns A promise that resolves to void.\n */\nconst validateJwtHeaderMiddleware = async (req: AuthedRequest, res: Response, next: NextFunction): Promise<void> => {\n\ttry {\n\t\tconst {\n\t\t\tappendToRequest = [],\n\t\t\tauthHeaderName,\n\t\t\trefreshTokenHeaderName,\n\t\t\tauthTokenExtractor,\n\t\t\ttokenGenerationHandler,\n\t\t\tcookieSettings = {},\n\t\t\tauthTokenPayloadVerifier,\n\t\t\trefreshTokenPayloadVerifier,\n\t\t\trefreshTokenHolderVerifier,\n\t\t\ttokenStorage,\n\t\t} = middlewareConfigs;\n\t\tlet authHeader = req.headers[authHeaderName ?? ''];\n\n\t\tlog('debug', 'Auth header and middleware configurations extracted.');\n\n\t\tif (Array.isArray(authHeader)) authHeader = authHeader.join('__');\n\n\t\tlog('debug', `Auth header: ${authHeader}`);\n\n\t\tif (!authHeader || !authHeader.startsWith('Bearer ')) {\n\t\t\tthrow new Error('Valid auth header not found');\n\t\t}\n\n\t\tif (authTokenExtractor) {\n\t\t\tlog('debug', 'Auth token extractor method found.');\n\n\t\t\tconst tokenValue = authTokenExtractor(authHeader);\n\n\t\t\tlog('debug', `Auth token value: ${tokenValue}`);\n\n\t\t\tif (!tokenValue) {\n\t\t\t\tthrow new Error('Auth token not found');\n\t\t\t}\n\n\t\t\tlet refreshToken =\n\t\t\t\treq.cookies && cookieSettings.refreshTokenCookieName\n\t\t\t\t\t? req.cookies[cookieSettings.refreshTokenCookieName]\n\t\t\t\t\t: undefined;\n\n\t\t\tif (!refreshToken && refreshTokenHeaderName) {\n\t\t\t\trefreshToken = req.headers[refreshTokenHeaderName];\n\t\t\t}\n\n\t\t\tlog('debug', 'Refresh token extracted.');\n\n\t\t\tconst refreshTokenHandler = new TokenHandler({\n\t\t\t\trefreshTokenStorage: tokenStorage,\n\t\t\t\ttokenGenerationHandler: tokenGenerationHandler,\n\t\t\t\tauthTokenPayloadVerifier,\n\t\t\t\trefreshTokenPayloadVerifier,\n\t\t\t\trefreshTokenHolderVerifier,\n\t\t\t});\n\n\t\t\tlog('debug', 'Token handler created.');\n\t\t\tlog('debug', `Auth token: ${tokenValue} | Refresh token: ${refreshToken}`);\n\n\t\t\tconst { decodedToken, nextRefreshToken, token } = await refreshTokenHandler.validateOrRefreshAuthToken(\n\t\t\t\ttokenValue,\n\t\t\t\trefreshToken,\n\t\t\t);\n\n\t\t\tlog('debug', 'Token handler validated or refreshed the auth token.');\n\n\t\t\tif (!decodedToken) {\n\t\t\t\tthrow new Error('Auth cookie payload is undefined!');\n\t\t\t}\n\n\t\t\tappendTokenPayloadToRequest(req, appendToRequest, decodedToken);\n\n\t\t\tlog('debug', 'Token payload appended to the request object.');\n\n\t\t\tres.setHeader(authHeaderName ?? 'authorization', token);\n\n\t\t\tif (cookieSettings.refreshTokenCookieName && nextRefreshToken) {\n\t\t\t\tlog('debug', 'New refresh token set in the cookie.');\n\t\t\t\tres.cookie(cookieSettings.refreshTokenCookieName, nextRefreshToken, cookieSettings.refreshCookieOptions || {});\n\t\t\t} else if (refreshTokenHeaderName && nextRefreshToken) {\n\t\t\t\tlog('debug', 'New refresh token set in the header.');\n\t\t\t\tres.setHeader(refreshTokenHeaderName, nextRefreshToken);\n\t\t\t}\n\n\t\t\tlog('debug', 'Next middleware called.');\n\n\t\t\treturn next();\n\t\t} else {\n\t\t\tthrow new Error('Token value extractor method not found');\n\t\t}\n\t} catch (error) {\n\t\tlog('error', 'Error occurred while authenticating the JST token.', error);\n\n\t\tconst errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\t\tres.status(401).json({ message: 'Unauthorized', error: errorMessage });\n\t}\n};\n\nexport default validateJwtHeaderMiddleware;\n","import { Response, NextFunction } from 'express';\nimport fs from 'node:fs/promises';\nimport Joi from 'joi';\n\nimport { AuthedRequest } from '../lib/custom';\nimport { log } from '../lib/logger';\nimport { RolePermissionConfigFilePath } from '../helper/constants';\nimport {\n\tEndPointConfig,\n\tEndPointsPermissionConfig,\n\tPermissionsConfiguration,\n\tPermissionsSet,\n\tRolesSet,\n} from '../module/internal';\nimport { middlewareConfigs } from '../lib/core';\n\nconst permissionSchema = Joi.object({\n\troles: Joi.array().items(Joi.string().required()).required(),\n\tactions: Joi.array().items(Joi.string().required()).required(),\n});\n\nconst endpointSchema = Joi.object({\n\tpath: Joi.string().required(),\n\tmethods: Joi.array()\n\t\t.items(Joi.string().valid('GET', 'POST', 'PUT', 'PATCH', 'DELETE'))\n\t\t.required(),\n\tpermissions: Joi.array().items(permissionSchema).required(),\n});\n\nconst groupSchema = Joi.object({\n\tbasePath: Joi.string().required(),\n\tpermissions: Joi.array().items(permissionSchema).required(),\n\tendpoints: Joi.array().items(endpointSchema).required(),\n});\n\nconst commonRolesSchema = Joi.object({\n\tname: Joi.string().required(),\n\tpermissions: Joi.array().items(Joi.string().required()).required(),\n});\n\nconst permissionsConfigSchema = Joi.object({\n\tversioned: Joi.boolean().optional(),\n\tactiveVersions: Joi.array().items(Joi.string().required()).optional(),\n\tcommon: Joi.object({\n\t\troles: Joi.array().items(commonRolesSchema).required(),\n\t}).optional(),\n\tgroups: Joi.object().pattern(Joi.string(), groupSchema).optional(),\n\tendpoints: Joi.array().items(endpointSchema).optional(),\n});\n\nconst getPermissionConfigs = async () => {\n\tawait fs.stat(RolePermissionConfigFilePath).catch((e) => {\n\t\tlog('error', 'Auth permissions configuration file could not found');\n\n\t\tthrow e;\n\t});\n\n\treturn JSON.parse(await fs.readFile(RolePermissionConfigFilePath, 'utf-8'));\n};\n\n/**\n * Middleware to handle role-based authentication for endpoints.\n *\n * This middleware checks if the user has the required permissions to access a specific endpoint\n * based on their role and the action they are trying to perform. It validates the permissions\n * configuration file and matches the user's role and action against the defined permissions.\n *\n * @param requiredAction - The action that the user is trying to perform (e.g., 'read', 'write').\n *\n * @returns An Express middleware function that checks the user's permissions and either allows\n * the request to proceed or responds with an access denied error.\n *\n * @throws Will throw an error if the permissions configuration file is not found or is invalid.\n *\n * @example\n * // Usage in an Express route\n * app.get('articles/some-endpoint', roleBasedAuthenticationMiddleware('articles:list'), (req, res) => {\n * res.send('You have access to this endpoint');\n * });\n */\nconst roleBasedAuthenticationMiddleware = (requiredAction: string) => {\n\treturn async (req: AuthedRequest, res: Response, next: NextFunction): Promise<void> => {\n\t\tconst { extractApiVersion } = middlewareConfigs;\n\n\t\tconst { user, role } = req;\n\t\tconst userRole = user && Object.hasOwn(user, 'role') ? user.role : role;\n\t\tconst endpointPath = req.baseUrl;\n\t\tconst method = req.method;\n\n\t\tlet requestVersion = undefined;\n\t\tlet versionValidationError: string | undefined = undefined;\n\n\t\tlog('debug', 'Role-based authentication middleware invoked.');\n\n\t\tif (extractApiVersion) {\n\t\t\tconst version = await extractApiVersion(req);\n\n\t\t\tif (version) {\n\t\t\t\trequestVersion = version;\n\t\t\t}\n\t\t}\n\n\t\tlog('debug', 'Role-based authentication middleware configurations extracted.');\n\t\tlog(\n\t\t\t'debug',\n\t\t\t`User role: ${userRole} | Required action: ${requiredAction} | Endpoint: ${endpointPath} | Method: ${method}`,\n\t\t);\n\t\tlog('debug', `API version extracted from the request: ${requestVersion}`);\n\n\t\tif (!userRole) {\n\t\t\tres.status(403).json({ error: 'Access denied. Role not found.' });\n\t\t} else {\n\t\t\tconst permissionsConfig: PermissionsConfiguration = await getPermissionConfigs();\n\n\t\t\tlog('debug', 'Auth permissions configuration file loaded.');\n\n\t\t\tconst { error } = permissionsConfigSchema.validate(permissionsConfig);\n\n\t\t\tlog('debug', 'Auth permissions configuration file validated.');\n\n\t\t\tif (error) {\n\t\t\t\tlog('error', \"Auth Permissions config file's validation failed.\", error);\n\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (!permissionsConfig.common && !permissionsConfig.groups && !permissionsConfig.endpoints) {\n\t\t\t\tlog('error', 'At least one permission set should be in the configs.');\n\n\t\t\t\tthrow new Error('Permission configurations is empty.');\n\t\t\t}\n\n\t\t\tif (permissionsConfig.versioned) {\n\t\t\t\tif (!requestVersion) {\n\t\t\t\t\tversionValidationError = 'Access denied. Insufficient permissions.';\n\t\t\t\t}\n\t\t\t\tif (requestVersion && !permissionsConfig.activeVersions?.includes(requestVersion)) {\n\t\t\t\t\tversionValidationError = 'Unsupported API version.';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlog('debug', 'Permission configurations validated.');\n\n\t\t\tif (versionValidationError) {\n\t\t\t\tlog('error', versionValidationError);\n\n\t\t\t\tres.status(400).json({ error: versionValidationError });\n\t\t\t} else {\n\t\t\t\tlog('debug', 'Permission configurations validating...');\n\t\t\t\t/* Match standalone endpoints. */\n\t\t\t\tif (permissionsConfig.endpoints) {\n\t\t\t\t\tconst standaloneEndpoint = permissionsConfig.endpoints.find(\n\t\t\t\t\t\t(ep: EndPointsPermissionConfig) => ep.path === endpointPath && ep.methods.includes(method),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (standaloneEndpoint) {\n\t\t\t\t\t\tif (checkPermissions(userRole, requiredAction, [], standaloneEndpoint.permissions)) {\n\t\t\t\t\t\t\treturn next();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* Match group-based routes. */\n\t\t\t\tif (permissionsConfig.groups) {\n\t\t\t\t\tconst groups: EndPointConfig[] = Object.values(permissionsConfig.groups);\n\t\t\t\t\tconst matchedGroup = groups.find(\n\t\t\t\t\t\t(group: EndPointConfig) =>\n\t\t\t\t\t\t\tendpointPath.startsWith(group.basePath) &&\n\t\t\t\t\t\t\tgroup.endpoints.some((ep: EndPointsPermissionConfig) => ep.path === endpointPath),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (matchedGroup) {\n\t\t\t\t\t\tconst groupPermissions = matchedGroup.permissions || [];\n\t\t\t\t\t\tconst endpointPermissions =\n\t\t\t\t\t\t\tmatchedGroup.endpoints.find(\n\t\t\t\t\t\t\t\t(ep: EndPointsPermissionConfig) => ep.path === endpointPath && ep.methods.includes(method),\n\t\t\t\t\t\t\t)?.permissions || [];\n\n\t\t\t\t\t\tif (checkPermissions(userRole, requiredAction, groupPermissions, endpointPermissions)) {\n\t\t\t\t\t\t\treturn next();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* Check common permissions. */\n\t\t\t\tif (permissionsConfig.common) {\n\t\t\t\t\tconst commonRoles = permissionsConfig.common.roles || [];\n\t\t\t\t\tconst commonRole = commonRoles.find((role: RolesSet) => role.name === userRole);\n\t\t\t\t\tif (commonRole && commonRole.permissions.includes('*:*')) {\n\t\t\t\t\t\t/* Full access granted. */\n\t\t\t\t\t\treturn next();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tres.status(403).json({ error: 'Access denied. Insufficient permissions.' });\n\t\t\t}\n\t\t}\n\t};\n};\n\nfunction checkPermissions(\n\tuserRole: string,\n\trequiredAction: string,\n\tgroupPermissions: PermissionsSet[],\n\tendpointPermissions: PermissionsSet[],\n): boolean {\n\tconst combinedPermissions = [...groupPermissions, ...endpointPermissions];\n\n\treturn combinedPermissions.some((permission: PermissionsSet) => {\n\t\tif (permission.roles.includes(userRole)) {\n\t\t\treturn permission.actions.includes(requiredAction) || permission.actions.includes('*:*');\n\t\t}\n\t\treturn false;\n\t});\n}\n\nexport default roleBasedAuthenticationMiddleware;\n","export const RolePermissionConfigFilePath = './.auth-permissions.json';\n","import { NextFunction, Request, Response } from 'express';\n\nimport { middlewareConfigs } from '../lib/core';\nimport { log } from '../lib/logger';\nimport { appendTokenPayloadToRequest } from '../helper/utils';\nimport { TokenHandler } from '../module/refresh-token-handler';\n\n/**\n * Middleware to validate JWT tokens stored in cookies.\n *\n * This middleware checks for the presence of access and refresh tokens in the request cookies.\n * If neither token is found, it throws an error. If tokens are found, it validates or refreshes\n * the tokens using the provided token generation handler and token storage.\n *\n * If the tokens are valid, it appends the decoded token payload to the request object and sets\n * new tokens in the cookies if necessary. If the tokens are invalid or an error occurs during\n * validation, it responds with a 401 Unauthorized status and an error message.\n *\n * @param req - The HTTP request object.\n * @param res - The HTTP response object.\n * @param next - The next middleware function in the stack.\n * @returns A promise that resolves to void.\n *\n * @throws Will throw an error if neither access token nor refresh token is found in the cookies.\n * @throws Will throw an error if the decoded token payload is undefined.\n */\nconst validateJwtCookieMiddleware = async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n\ttry {\n\t\tconst {\n\t\t\tappendToRequest = [],\n\t\t\ttokenGenerationHandler,\n\t\t\tcookieSettings = {},\n\t\t\tauthTokenPayloadVerifier,\n\t\t\trefreshTokenPayloadVerifier,\n\t\t\trefreshTokenHolderVerifier,\n\t\t\ttokenStorage,\n\t\t} = middlewareConfigs;\n\n\t\tconst accessToken =\n\t\t\treq.cookies && cookieSettings.accessTokenCookieName\n\t\t\t\t? req.cookies[cookieSettings.accessTokenCookieName]\n\t\t\t\t: undefined;\n\n\t\tconst refreshToken =\n\t\t\treq.cookies && cookieSettings.refreshTokenCookieName\n\t\t\t\t? req.cookies[cookieSettings.refreshTokenCookieName]\n\t\t\t\t: undefined;\n\n\t\tif (!accessToken && !refreshToken) {\n\t\t\tthrow new Error('Auth cookie not found!');\n\t\t}\n\n\t\tlog('debug', 'Auth cookie and middleware configurations extracted.');\n\t\tlog('debug', `Access token: ${accessToken} | Refresh token: ${refreshToken}`);\n\n\t\tconst refreshTokenHandler = new TokenHandler({\n\t\t\trefreshTokenStorage: tokenStorage,\n\t\t\ttokenGenerationHandler: tokenGenerationHandler,\n\t\t\tauthTokenPayloadVerifier,\n\t\t\trefreshTokenPayloadVerifier,\n\t\t\trefreshTokenHolderVerifier,\n\t\t});\n\n\t\tlog('debug', 'Token handler created.');\n\n\t\tconst { decodedToken, nextRefreshToken, token } = await refreshTokenHandler.validateOrRefreshAuthToken(\n\t\t\taccessToken,\n\t\t\trefreshToken,\n\t\t);\n\n\t\tlog('debug', 'Token handler validated or refreshed the auth token.');\n\n\t\tif (!decodedToken) {\n\t\t\tthrow new Error('Auth cookie payload is undefined!');\n\t\t}\n\n\t\tappendTokenPayloadToRequest(req, appendToRequest, decodedToken);\n\n\t\tlog('debug', 'Token payload appended to the request object.');\n\n\t\tif (cookieSettings.accessTokenCookieName) {\n\t\t\tlog('debug', 'New access token set in the cookie.');\n\t\t\tres.cookie(cookieSettings.accessTokenCookieName, token, cookieSettings.accessCookieOptions || {});\n\t\t}\n\n\t\tif (cookieSettings.refreshTokenCookieName && nextRefreshToken) {\n\t\t\tlog('debug', 'New refresh token set in the cookie.');\n\t\t\tres.cookie(cookieSettings.refreshTokenCookieName, nextRefreshToken, cookieSettings.refreshCookieOptions || {});\n\t\t}\n\n\t\tlog('debug', 'Token handler completed successfully.');\n\n\t\tnext();\n\t} catch (error) {\n\t\tlog('error', 'Error occurred while authenticating the JST token.', error);\n\n\t\tconst errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\t\tres.status(401).json({ message: 'Unauthorized', error: errorMessage });\n\t}\n};\n\nexport default validateJwtCookieMiddleware;\n"],"mappings":"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