{"version":3,"file":"index.mjs","names":["Client"],"sources":["../src/verifier/cache/memory.ts","../src/verifier/cache/redis.ts","../src/verifier/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { TTLCache } from '@isaacs/ttlcache';\nimport type { TokenVerificationData } from '../types';\nimport type { ITokenVerifierCache } from './types';\n\nexport class MemoryTokenVerifierCache implements ITokenVerifierCache {\n    protected driver : TTLCache<string, TokenVerificationData>;\n\n    constructor() {\n        this.driver = new TTLCache();\n    }\n\n    async get(token: string): Promise<TokenVerificationData | undefined> {\n        return this.driver.get(token);\n    }\n\n    async set(token: string, data: TokenVerificationData, seconds: number): Promise<void> {\n        this.driver.set(token, data, { ttl: seconds * 1000 });\n    }\n}\n","/*\n * Copyright (c) 2023-2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { Client } from 'redis-extension';\nimport { JsonAdapter, createClient } from 'redis-extension';\nimport type { TokenVerificationData } from '../types';\nimport type { ITokenVerifierCache } from './types';\n\nexport class RedisTokenVerifierCache implements ITokenVerifierCache {\n    protected instance : JsonAdapter;\n\n    constructor(input?: Client | string) {\n        let client: Client;\n\n        if (!input) {\n            client = createClient();\n        } else if (typeof input === 'string') {\n            client = createClient({ connectionString: input });\n        } else {\n            client = input;\n        }\n\n        this.instance = new JsonAdapter(client);\n    }\n\n    get(token: string): Promise<TokenVerificationData | undefined> {\n        return this.instance.get(this.buildKey(token));\n    }\n\n    set(token: string, data: TokenVerificationData, seconds: number): Promise<void> {\n        return this.instance.set(\n            this.buildKey(token),\n            data,\n            { seconds },\n        );\n    }\n\n    protected buildKey(token: string) {\n        const hash = createHash('sha256').update(token).digest('hex');\n        return `token:${hash}`;\n    }\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n    Client,\n    ClientAuthenticationHook,\n} from '@authup/core-http-kit';\nimport { ErrorCode } from '@authup/errors';\nimport { isObject } from '@authup/kit';\nimport {\n    JWKType,\n    JWTError,\n} from '@authup/specs';\nimport type {\n    JWTAlgorithm,\n    OAuth2JsonWebKey,\n    OAuth2TokenIntrospectionResponse,\n    OAuth2TokenPayload,\n} from '@authup/specs';\nimport {\n    extractTokenHeader,\n    verifyToken,\n} from '@authup/server-kit';\nimport { importJWK } from 'jose';\nimport type { ITokenVerifierCache } from './cache';\nimport type {\n    ITokenVerifier, \n    TokenVerificationData, \n    TokenVerificationDataInput, \n    TokenVerifierContext,\n} from './types';\n\nexport class TokenVerifier implements ITokenVerifier {\n    protected interceptorMounted : boolean | undefined;\n\n    protected client: Client;\n\n    protected cache : ITokenVerifierCache | undefined;\n\n    protected maxRemoteCacheTTL: number | undefined;\n\n    constructor(ctx: TokenVerifierContext) {\n        this.cache = ctx.cache;\n        this.maxRemoteCacheTTL = ctx.maxRemoteCacheTTL;\n        this.client = new Client({ baseURL: ctx.baseURL });\n\n        if (ctx.creator) {\n            // todo: use server kit singleton :)\n            const hook = new ClientAuthenticationHook({\n                tokenCreator: ctx.creator,\n                baseURL: ctx.baseURL,\n            });\n\n            hook.attach(this.client);\n\n            this.interceptorMounted = true;\n        }\n    }\n\n    async verify(token: string) : Promise<TokenVerificationData> {\n        if (this.interceptorMounted) {\n            return this.verifyRemote(token);\n        }\n\n        return this.verifyLocal(token);\n    }\n\n    async verifyLocal(token: string) : Promise<TokenVerificationData> {\n        let output: TokenVerificationData | undefined;\n        if (this.cache) {\n            output = await this.cache.get(token);\n            if (output) {\n                return output;\n            }\n        }\n\n        const header = extractTokenHeader(token);\n        if (!header) {\n            throw JWTError.headerInvalid('The token header could not be extracted.');\n        }\n\n        if (!header.kid) {\n            throw JWTError.headerPropertyInvalid('kid');\n        }\n\n        let jwk : OAuth2JsonWebKey;\n\n        try {\n            // todo: this should be cached as well :)\n            jwk = await this.client.getJwk(header.kid);\n        } catch (e) {\n            if (isObject(e) && isObject(e.response) && e.response.status === 404) {\n                throw JWTError.payloadPropertyInvalid('kid');\n            }\n\n            throw new JWTError({\n                message: 'Failed to fetch JWK from auth server.',\n                cause: e as Error,\n            });\n        }\n\n        const key = await importJWK(jwk);\n\n        /* istanbul ignore next */\n        if (!(key instanceof CryptoKey)) {\n            throw JWTError.payloadInvalid('The jwt key is not valid.');\n        }\n\n        let payload : OAuth2TokenPayload;\n\n        // todo: get jwk type for algorithm\n\n        try {\n            payload = await verifyToken(token, {\n                type: JWKType.RSA,\n                key,\n                ...(jwk.alg ? { algorithms: [jwk.alg as JWTAlgorithm.RS256] } : {}),\n            }) as OAuth2TokenPayload;\n        } catch {\n            throw JWTError.payloadInvalid('The token could not be verified.');\n        }\n\n        const secondsDiff = this.getTokenExpiresIn(payload);\n\n        output = this.transform(payload);\n\n        if (this.cache) {\n            await this.cache.set(token, output, secondsDiff);\n        }\n\n        return output;\n    }\n\n    async verifyRemote(token: string) : Promise<TokenVerificationData> {\n        let output: TokenVerificationData | undefined;\n        if (this.cache) {\n            output = await this.cache.get(token);\n            if (output) {\n                return output;\n            }\n        }\n\n        let payload : OAuth2TokenIntrospectionResponse;\n\n        try {\n            payload = await this.client.token.introspect({ token }, { authorizationHeaderInherit: true });\n        } catch (e) {\n            /* istanbul ignore next */\n            if (!isObject(e)) {\n                throw new JWTError({ message: 'An unexpected token occurred.' });\n            }\n\n            if (\n                isObject(e.response) &&\n                isObject(e.response.data)\n            ) {\n                const code = typeof e.response.data.code === 'string' ?\n                    e.response.data.code :\n                    ErrorCode.JWT_INVALID;\n\n                const message = typeof e.response.data.message === 'string' ?\n                    e.response.data.message :\n                    undefined;\n\n                throw new JWTError({\n                    statusCode: e.response.status,\n                    code,\n                    message,\n                });\n            }\n\n            /* istanbul ignore next */\n            throw new JWTError({\n                message: e.message || 'An unexpected error occurred.',\n                cause: e as Error,\n            });\n        }\n\n        if ('active' in payload && !payload.active) {\n            throw JWTError.notActive();\n        }\n\n        const secondsDiff = this.getTokenExpiresIn(payload);\n        const cacheTTL = this.maxRemoteCacheTTL ?\n            Math.min(secondsDiff, this.maxRemoteCacheTTL) :\n            secondsDiff;\n\n        output = this.transform(payload);\n\n        if (this.cache) {\n            await this.cache.set(token, output, cacheTTL);\n        }\n\n        return output;\n    }\n\n    /**\n     * Return remaining seconds until token expires.\n     * Throw error if token is already expired.\n     *\n     * @param payload\n     * @protected\n     */\n    protected getTokenExpiresIn(payload: OAuth2TokenPayload) : number {\n        if (!payload.exp) {\n            throw JWTError.payloadPropertyInvalid('exp');\n        }\n\n        const now = Math.floor(Date.now() / 1000);\n        const secondsDiff = payload.exp - now;\n        /* istanbul ignore next */\n        if (secondsDiff <= 0) {\n            throw JWTError.expired();\n        }\n\n        return secondsDiff;\n    }\n\n    protected transform(input: TokenVerificationDataInput) : TokenVerificationData {\n        return {\n            ...input,\n            permissions: input.permissions || [],\n        };\n    }\n}\n"],"mappings":"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"}