{"version":3,"file":"jwt.mjs","names":[],"sources":["../../src/crypto/jwt.ts"],"sourcesContent":["import { hkdf } from \"@noble/hashes/hkdf.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport {\n\tbase64url,\n\tcalculateJwkThumbprint,\n\tdecodeProtectedHeader,\n\tEncryptJWT,\n\tjwtDecrypt,\n\tjwtVerify,\n\tSignJWT,\n} from \"jose\";\nimport type { SecretConfig } from \"./index\";\n\nexport async function signJWT(\n\tpayload: any,\n\tsecret: string,\n\texpiresIn: number = 3600,\n): Promise<string> {\n\tconst jwt = await new SignJWT(payload)\n\t\t.setProtectedHeader({ alg: \"HS256\" })\n\t\t.setIssuedAt()\n\t\t.setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn)\n\t\t.sign(new TextEncoder().encode(secret));\n\n\treturn jwt;\n}\n\nexport async function verifyJWT<T = any>(\n\ttoken: string,\n\tsecret: string,\n): Promise<T | null> {\n\ttry {\n\t\tconst verified = await jwtVerify(token, new TextEncoder().encode(secret));\n\t\treturn verified.payload as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// \"BetterAuth.js Generated Encryption Key\"\nconst info: Uint8Array = new Uint8Array([\n\t66, 101, 116, 116, 101, 114, 65, 117, 116, 104, 46, 106, 115, 32, 71, 101,\n\t110, 101, 114, 97, 116, 101, 100, 32, 69, 110, 99, 114, 121, 112, 116, 105,\n\t111, 110, 32, 75, 101, 121,\n]);\n\nconst now = () => (Date.now() / 1000) | 0;\n\nconst alg = \"dir\";\nconst enc = \"A256CBC-HS512\"; // 64 bytes key\n\nfunction deriveEncryptionSecret(secret: string, salt: string): Uint8Array {\n\treturn hkdf(\n\t\tsha256,\n\t\tnew TextEncoder().encode(secret),\n\t\tnew TextEncoder().encode(salt),\n\t\tinfo,\n\t\t64,\n\t);\n}\n\nfunction getCurrentSecret(secret: string | SecretConfig): string {\n\tif (typeof secret === \"string\") return secret;\n\tconst value = secret.keys.get(secret.currentVersion);\n\tif (!value) {\n\t\tthrow new Error(\n\t\t\t`Secret version ${secret.currentVersion} not found in keys`,\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction getAllSecrets(\n\tsecret: string | SecretConfig,\n): Array<{ version: number; value: string }> {\n\tif (typeof secret === \"string\") return [{ version: 0, value: secret }];\n\tconst result: Array<{ version: number; value: string }> = [];\n\tfor (const [version, value] of secret.keys) {\n\t\tresult.push({ version, value });\n\t}\n\tif (\n\t\tsecret.legacySecret &&\n\t\t!result.some((s) => s.value === secret.legacySecret)\n\t) {\n\t\tresult.push({ version: -1, value: secret.legacySecret });\n\t}\n\treturn result;\n}\n\nexport async function symmetricEncodeJWT<T extends Record<string, any>>(\n\tpayload: T,\n\tsecret: string | SecretConfig,\n\tsalt: string,\n\texpiresIn: number = 3600,\n): Promise<string> {\n\tconst currentSecret = getCurrentSecret(secret);\n\tconst encryptionSecret = deriveEncryptionSecret(currentSecret, salt);\n\n\tconst thumbprint = await calculateJwkThumbprint(\n\t\t{ kty: \"oct\", k: base64url.encode(encryptionSecret) },\n\t\t\"sha256\",\n\t);\n\n\treturn await new EncryptJWT(payload)\n\t\t.setProtectedHeader({ alg, enc, kid: thumbprint })\n\t\t.setIssuedAt()\n\t\t.setExpirationTime(now() + expiresIn)\n\t\t.setJti(crypto.randomUUID())\n\t\t.encrypt(encryptionSecret);\n}\n\nconst jwtDecryptOpts = {\n\tclockTolerance: 15,\n\tkeyManagementAlgorithms: [alg],\n\tcontentEncryptionAlgorithms: [enc, \"A256GCM\"],\n};\n\nexport async function symmetricDecodeJWT<T = any>(\n\ttoken: string,\n\tsecret: string | SecretConfig,\n\tsalt: string,\n): Promise<T | null> {\n\tif (!token) return null;\n\t// Parse the JWT header to check if kid is present\n\tlet hasKid = false;\n\ttry {\n\t\tconst header = decodeProtectedHeader(token);\n\t\thasKid = header.kid !== undefined;\n\t} catch {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst secrets = getAllSecrets(secret);\n\t\tconst { payload } = await jwtDecrypt(\n\t\t\ttoken,\n\t\t\tasync (protectedHeader: { kid?: string }) => {\n\t\t\t\tconst kid = protectedHeader.kid;\n\t\t\t\tif (kid !== undefined) {\n\t\t\t\t\tfor (const s of secrets) {\n\t\t\t\t\t\tconst encryptionSecret = deriveEncryptionSecret(s.value, salt);\n\t\t\t\t\t\tconst thumbprint = await calculateJwkThumbprint(\n\t\t\t\t\t\t\t{ kty: \"oct\", k: base64url.encode(encryptionSecret) },\n\t\t\t\t\t\t\t\"sha256\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (kid === thumbprint) return encryptionSecret;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(\"no matching decryption secret\");\n\t\t\t\t}\n\t\t\t\t// kid is undefined — single secret: use it directly\n\t\t\t\tif (secrets.length === 1) {\n\t\t\t\t\treturn deriveEncryptionSecret(secrets[0]!.value, salt);\n\t\t\t\t}\n\t\t\t\t// kid is undefined with multiple secrets: cannot determine\n\t\t\t\t// which key to use. Try the current (first) secret.\n\t\t\t\treturn deriveEncryptionSecret(secrets[0]!.value, salt);\n\t\t\t},\n\t\t\tjwtDecryptOpts,\n\t\t);\n\t\treturn payload as T;\n\t} catch {\n\t\t// Only try fallback if token has no kid\n\t\tif (hasKid) {\n\t\t\treturn null;\n\t\t}\n\t\t// If kid was undefined and first secret failed, try remaining secrets\n\t\tconst secrets = getAllSecrets(secret);\n\t\tif (secrets.length <= 1) return null;\n\t\tfor (let i = 1; i < secrets.length; i++) {\n\t\t\ttry {\n\t\t\t\tconst s = secrets[i]!;\n\t\t\t\tconst encryptionSecret = deriveEncryptionSecret(s.value, salt);\n\t\t\t\tconst { payload } = await jwtDecrypt(\n\t\t\t\t\ttoken,\n\t\t\t\t\tencryptionSecret,\n\t\t\t\t\tjwtDecryptOpts,\n\t\t\t\t);\n\t\t\t\treturn payload as T;\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}