UNPKG

@treasure-dev/auth

Version:

Authentication token utilities for the Treasure ecosystem

105 lines (101 loc) 3.16 kB
// src/auth.ts import { KMS } from "@aws-sdk/client-kms"; import jwt from "jsonwebtoken"; // src/base64.ts var base64 = (value) => Buffer.from(value).toString("base64"); var base64url = (value) => base64(value).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); // src/kms.ts var publicKeyCache = {}; var kmsSign = async (kms, key, message) => { const result = await kms.sign({ Message: Buffer.from(message), KeyId: key, SigningAlgorithm: "RSASSA_PKCS1_V1_5_SHA_256", MessageType: "RAW" }); if (!result.Signature) { throw new Error("Unable to generate signature"); } return base64url(result.Signature); }; var kmsGetPublicKey = async (kms, key, cacheTtlSeconds = 3600) => { if (!publicKeyCache[key] || Date.now() - publicKeyCache[key].lastUpdatedAt > cacheTtlSeconds * 1e3) { const result = await kms.getPublicKey({ KeyId: key }); if (!result.PublicKey) { throw new Error("Unable to fetch public key"); } publicKeyCache[key] = { publicKey: `-----BEGIN PUBLIC KEY----- ${base64(result.PublicKey)} -----END PUBLIC KEY-----`, lastUpdatedAt: Date.now() }; } return publicKeyCache[key].publicKey; }; // src/auth.ts var JWT_HEADER = base64url( JSON.stringify({ alg: "RS256", typ: "JWT" }) ); var createAuth = ({ kmsKey, kmsClientConfig, kmsPublicKeyCacheTtlSeconds = 3600, // 1 hour issuer, audience, expirationTimeSeconds = 86400 // 1 day }) => { const kms = kmsClientConfig ? new KMS(kmsClientConfig) : new KMS(); return { generateJWT: async (subject, overrides) => { const payload = { iss: overrides?.issuer ?? issuer ?? "treasure.lol", aud: overrides?.audience ?? audience ?? "treasure.lol", sub: subject, iat: Math.floor((overrides?.issuedAt ?? /* @__PURE__ */ new Date()).getTime() / 1e3), exp: Math.floor( overrides?.expiresAt ? overrides.expiresAt.getTime() / 1e3 : (/* @__PURE__ */ new Date()).getTime() / 1e3 + expirationTimeSeconds ), ctx: overrides?.context ?? {} }; const message = `${JWT_HEADER}.${base64url(JSON.stringify(payload))}`; const signature = await kmsSign(kms, kmsKey, message); return `${message}.${signature}`; }, verifyJWT: async (token) => { const decoded = jwt.decode(token); const now = Math.floor(Date.now() / 1e3); if (!decoded.exp || decoded.exp < now) { throw new Error( `Token expired at ${decoded.exp}, current time is ${now}` ); } if (audience && decoded.aud.toLowerCase() !== audience.toLowerCase()) { throw new Error( `Expected audience "${audience}", but found "${decoded.aud}"` ); } if (issuer && decoded.iss.toLowerCase() !== issuer.toLowerCase()) { throw new Error( `Expected issuer "${issuer}", but found "${decoded.iss}"` ); } const publicKey = await kmsGetPublicKey( kms, kmsKey, kmsPublicKeyCacheTtlSeconds ); return jwt.verify(token, publicKey, { algorithms: ["RS256"] }); } }; }; export { createAuth };