UNPKG

better-auth

Version:

The most comprehensive authentication library for TypeScript.

319 lines (314 loc) • 11.8 kB
import { s as schema } from '../../shared/better-auth.CGrHn1Ih.mjs'; import { importJWK, SignJWT, generateKeyPair, exportJWK } from 'jose'; import { B as BetterAuthError } from '../../shared/better-auth.DdzSJf-n.mjs'; import { symmetricEncrypt, symmetricDecrypt } from '../../crypto/index.mjs'; import 'better-call'; import { i as createAuthMiddleware, j as createAuthEndpoint, l as sessionMiddleware } from '../../shared/better-auth.z3dsxLxE.mjs'; import 'zod/v4'; import '../../shared/better-auth.8zoxzg-F.mjs'; import '@better-auth/utils/base64'; import '@better-auth/utils/hmac'; import '@better-auth/utils/binary'; import { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs'; import '../../shared/better-auth.DBGfIDnh.mjs'; import 'defu'; import '@better-auth/utils/hash'; import '@noble/ciphers/chacha'; import '@noble/ciphers/utils'; import '@noble/ciphers/webcrypto'; import '@noble/hashes/scrypt'; import '@better-auth/utils'; import '@better-auth/utils/hex'; import '@noble/hashes/utils'; import '../../shared/better-auth.B4Qoxdgc.mjs'; import '@better-auth/utils/random'; import '../../shared/better-auth.CW6D9eSx.mjs'; import '@better-fetch/fetch'; import '../../shared/better-auth.VTXNLFMT.mjs'; import '../../cookies/index.mjs'; import '../../shared/better-auth.tB5eU6EY.mjs'; import 'jose/errors'; const getJwksAdapter = (adapter) => { return { getAllKeys: async () => { return await adapter.findMany({ model: "jwks" }); }, getLatestKey: async () => { const key = await adapter.findMany({ model: "jwks", sortBy: { field: "createdAt", direction: "desc" }, limit: 1 }); return key[0]; }, createJwk: async (webKey) => { const jwk = await adapter.create({ model: "jwks", data: { ...webKey, createdAt: /* @__PURE__ */ new Date() } }); return jwk; } }; }; async function getJwtToken(ctx, options) { const adapter = getJwksAdapter(ctx.context.adapter); let key = await adapter.getLatestKey(); const privateKeyEncryptionEnabled = !options?.jwks?.disablePrivateKeyEncryption; if (key === void 0) { const alg = options?.jwks?.keyPairConfig?.alg || "EdDSA"; const { publicWebKey, privateWebKey: privateWebKey2 } = await generateExportedKeyPair(options); const stringifiedPrivateWebKey = JSON.stringify(privateWebKey2); let jwk = { publicKey: JSON.stringify({ alg, ...publicWebKey }), privateKey: privateKeyEncryptionEnabled ? JSON.stringify( await symmetricEncrypt({ key: ctx.context.secret, data: stringifiedPrivateWebKey }) ) : stringifiedPrivateWebKey, createdAt: /* @__PURE__ */ new Date() }; key = await adapter.createJwk(jwk); } let privateWebKey = privateKeyEncryptionEnabled ? await symmetricDecrypt({ key: ctx.context.secret, data: JSON.parse(key.privateKey) }).catch(() => { throw new BetterAuthError( "Failed to decrypt private private key. Make sure the secret currently in use is the same as the one used to encrypt the private key. If you are using a different secret, either cleanup your jwks or disable private key encryption." ); }) : key.privateKey; const privateKey = await importJWK( JSON.parse(privateWebKey), options?.jwks?.keyPairConfig?.alg ?? "EdDSA" ); const payload = !options?.jwt?.definePayload ? ctx.context.session.user : await options?.jwt.definePayload(ctx.context.session); const jwt = await new SignJWT(payload).setProtectedHeader({ alg: options?.jwks?.keyPairConfig?.alg ?? "EdDSA", kid: key.id }).setIssuedAt().setIssuer(options?.jwt?.issuer ?? ctx.context.options.baseURL).setAudience(options?.jwt?.audience ?? ctx.context.options.baseURL).setExpirationTime(options?.jwt?.expirationTime ?? "15m").setSubject( await options?.jwt?.getSubject?.(ctx.context.session) ?? ctx.context.session.user.id ).sign(privateKey); return jwt; } async function generateExportedKeyPair(options) { const { alg, ...cfg } = options?.jwks?.keyPairConfig ?? { alg: "EdDSA", crv: "Ed25519" }; const keyPairConfig = { ...cfg, extractable: true }; const { publicKey, privateKey } = await generateKeyPair(alg, keyPairConfig); const publicWebKey = await exportJWK(publicKey); const privateWebKey = await exportJWK(privateKey); return { publicWebKey, privateWebKey }; } const jwt = (options) => { return { id: "jwt", endpoints: { getJwks: createAuthEndpoint( "/jwks", { method: "GET", metadata: { openapi: { description: "Get the JSON Web Key Set", responses: { "200": { description: "JSON Web Key Set retrieved successfully", content: { "application/json": { schema: { type: "object", properties: { keys: { type: "array", description: "Array of public JSON Web Keys", items: { type: "object", properties: { kid: { type: "string", description: "Key ID uniquely identifying the key, corresponds to the 'id' from the stored Jwk" }, kty: { type: "string", description: "Key type (e.g., 'RSA', 'EC', 'OKP')" }, alg: { type: "string", description: "Algorithm intended for use with the key (e.g., 'EdDSA', 'RS256')" }, use: { type: "string", description: "Intended use of the public key (e.g., 'sig' for signature)", enum: ["sig"], nullable: true }, n: { type: "string", description: "Modulus for RSA keys (base64url-encoded)", nullable: true }, e: { type: "string", description: "Exponent for RSA keys (base64url-encoded)", nullable: true }, crv: { type: "string", description: "Curve name for elliptic curve keys (e.g., 'Ed25519', 'P-256')", nullable: true }, x: { type: "string", description: "X coordinate for elliptic curve keys (base64url-encoded)", nullable: true }, y: { type: "string", description: "Y coordinate for elliptic curve keys (base64url-encoded)", nullable: true } }, required: ["kid", "kty", "alg"] } } }, required: ["keys"] } } } } } } } }, async (ctx) => { const adapter = getJwksAdapter(ctx.context.adapter); const keySets = await adapter.getAllKeys(); if (keySets.length === 0) { const { alg, ...cfg } = options?.jwks?.keyPairConfig ?? { alg: "EdDSA", crv: "Ed25519" }; const keyPairConfig = { ...cfg, extractable: true }; const { publicKey, privateKey } = await generateKeyPair( alg, keyPairConfig ); const publicWebKey = await exportJWK(publicKey); const privateWebKey = await exportJWK(privateKey); const stringifiedPrivateWebKey = JSON.stringify(privateWebKey); const privateKeyEncryptionEnabled = !options?.jwks?.disablePrivateKeyEncryption; let jwk = { publicKey: JSON.stringify({ alg, ...publicWebKey }), privateKey: privateKeyEncryptionEnabled ? JSON.stringify( await symmetricEncrypt({ key: ctx.context.secret, data: stringifiedPrivateWebKey }) ) : stringifiedPrivateWebKey, createdAt: /* @__PURE__ */ new Date() }; await adapter.createJwk(jwk); return ctx.json({ keys: [ { ...publicWebKey, alg, kid: jwk.id } ] }); } return ctx.json({ keys: keySets.map((keySet) => ({ ...JSON.parse(keySet.publicKey), kid: keySet.id })) }); } ), getToken: createAuthEndpoint( "/token", { method: "GET", requireHeaders: true, use: [sessionMiddleware], metadata: { openapi: { description: "Get a JWT token", responses: { 200: { description: "Success", content: { "application/json": { schema: { type: "object", properties: { token: { type: "string" } } } } } } } } } }, async (ctx) => { const jwt2 = await getJwtToken(ctx, options); return ctx.json({ token: jwt2 }); } ) }, hooks: { after: [ { matcher(context) { return context.path === "/get-session"; }, handler: createAuthMiddleware(async (ctx) => { const session = ctx.context.session || ctx.context.newSession; if (session && session.session) { const jwt2 = await getJwtToken(ctx, options); const exposedHeaders = ctx.context.responseHeaders?.get( "access-control-expose-headers" ) || ""; const headersSet = new Set( exposedHeaders.split(",").map((header) => header.trim()).filter(Boolean) ); headersSet.add("set-auth-jwt"); ctx.setHeader("set-auth-jwt", jwt2); ctx.setHeader( "Access-Control-Expose-Headers", Array.from(headersSet).join(", ") ); } }) } ] }, schema: mergeSchema(schema, options?.schema) }; }; export { generateExportedKeyPair, getJwtToken, jwt };