diff options
| author | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 12:16:40 +0100 |
|---|---|---|
| committer | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 12:16:40 +0100 |
| commit | e904e9634548e47d611bdcbb88d7b180b927fd5f (patch) | |
| tree | 21aa5be08fc5b22585508c0263ee5ea4effcc593 /src/account/jwt.ts | |
feat: initial commit!
Diffstat (limited to 'src/account/jwt.ts')
| -rw-r--r-- | src/account/jwt.ts | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/src/account/jwt.ts b/src/account/jwt.ts new file mode 100644 index 0000000..21d49f8 --- /dev/null +++ b/src/account/jwt.ts @@ -0,0 +1,91 @@ +import * as jose from "jose"; +import { logger } from "../lib/pino"; + +const privateKey = process.env.JWT_PRIVATE_KEY; +const publicKey = process.env.JWT_PUBLIC_KEY; + +/** + * Creates a signed JWT token with the provided payload. + * Tokens are set to expire in 1000 years (effectively never). + */ +export async function signJwt( + payload: Record<string, unknown>, + audience: string, +) { + try { + if (!privateKey) { + logger.error("JWT_PRIVATE_KEY environment variable not set"); + throw new Error("JWT private key not configured"); + } + + if (!payload || typeof payload !== "object") { + logger.error({ payload }, "Invalid payload for JWT signing"); + throw new Error("Invalid JWT payload"); + } + + if (!audience || typeof audience !== "string") { + logger.error({ audience }, "Invalid audience for JWT signing"); + throw new Error("Invalid JWT audience"); + } + + const privateKeyJose = await jose.importPKCS8(privateKey, "RS256"); + + const jwt = await new jose.SignJWT(payload) + .setProtectedHeader({ alg: "RS256" }) + .setIssuedAt() + .setIssuer("urn:lajosh:buddy") + .setAudience(audience) + .setExpirationTime("1000years") + .sign(privateKeyJose); + + logger.debug( + { audience, payloadType: payload.type }, + "JWT signed successfully", + ); + return jwt; + } catch (e) { + logger.error( + { error: e, audience, payloadType: payload?.type }, + "Failed to sign JWT", + ); + throw e; + } +} + +/** + * Verifies a JWT token and returns the payload if valid. + * Checks signature, issuer, and audience claims. + */ +export async function verifyJwt(token: string, audience: string) { + try { + if (!publicKey) { + logger.error("JWT_PUBLIC_KEY environment variable not set"); + throw new Error("JWT public key not configured"); + } + + if (!token || typeof token !== "string") { + logger.warn("Invalid token for JWT verification"); + throw new Error("Invalid token"); + } + + if (!audience || typeof audience !== "string") { + logger.error({ audience }, "Invalid audience for JWT verification"); + throw new Error("Invalid JWT audience"); + } + + const publicKeyJose = await jose.importSPKI(publicKey, "RS256"); + const { payload } = await jose.jwtVerify(token, publicKeyJose, { + issuer: "urn:lajosh:buddy", + audience: audience, + }); + + logger.debug( + { audience, payloadType: payload.type as string }, + "JWT verified successfully", + ); + return payload; + } catch (e) { + logger.warn({ error: e, audience }, "JWT verification failed"); + throw e; + } +} |