UNPKG

alinea

Version:

[![npm](https://img.shields.io/npm/v/alinea.svg)](https://npmjs.org/package/alinea) [![install size](https://packagephobia.com/badge?p=alinea)](https://packagephobia.com/result?p=alinea)

138 lines (136 loc) 4.95 kB
import "../../chunks/chunk-U5RRZUYZ.js"; // src/core/util/JWT.ts import { crypto } from "@alinea/iso"; import { base64, base64url } from "./Encoding.js"; var textDecoder = new TextDecoder(); var textEncoder = new TextEncoder(); var algorithms = { ES256: { name: "ECDSA", namedCurve: "P-256", hash: { name: "SHA-256" } }, ES384: { name: "ECDSA", namedCurve: "P-384", hash: { name: "SHA-384" } }, ES512: { name: "ECDSA", namedCurve: "P-512", hash: { name: "SHA-512" } }, HS256: { name: "HMAC", hash: { name: "SHA-256" } }, HS384: { name: "HMAC", hash: { name: "SHA-384" } }, HS512: { name: "HMAC", hash: { name: "SHA-512" } }, RS256: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }, RS384: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-384" } }, RS512: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-512" } } }; function defaultAlgorithms(secretOrPublicKey) { if (typeof secretOrPublicKey === "string") { if (secretOrPublicKey.includes("BEGIN CERTIFICATE") || secretOrPublicKey.includes("BEGIN PUBLIC KEY")) return ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]; if (secretOrPublicKey.includes("BEGIN RSA PUBLIC KEY")) return ["RS256", "RS384", "RS512"]; return ["HS256", "HS384", "HS512"]; } return ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]; } function importKey(secret, algorithm, use) { if (typeof secret !== "string") return crypto.subtle.importKey("jwk", secret, algorithm, false, [use]); if (secret.startsWith("-----BEGIN")) return crypto.subtle.importKey( "pkcs8", base64.parse( secret.replace(/-----BEGIN.*?-----/g, "").replace(/-----END.*?-----/g, "").replace(/\s/g, "") ), algorithm, false, [use] ); return crypto.subtle.importKey( "raw", textEncoder.encode(secret), algorithm, false, [use] ); } async function sign(payload, secret, options = { algorithm: "HS256" }) { if (payload === null || typeof payload !== "object") throw new Error("payload must be an object"); if (typeof options.algorithm !== "string") throw new Error("options.algorithm must be a string"); const algorithm = algorithms[options.algorithm]; if (!algorithm) throw new Error("algorithm not found"); const payloadAsJSON = JSON.stringify(payload); const partialToken = `${base64url.stringify( textEncoder.encode( JSON.stringify( options.header || { typ: "JWT", alg: options.algorithm } ) ), { pad: false } )}.${base64url.stringify(textEncoder.encode(payloadAsJSON), { pad: false })}`; const key = await importKey(secret, algorithm, "sign"); const signature = await crypto.subtle.sign( algorithm, key, textEncoder.encode(partialToken) ); return [ partialToken, base64url.stringify(new Uint8Array(signature), { pad: false }) ].join("."); } async function verify(token, secretOrPublicKey, options = {}) { if (typeof token !== "string") throw new Error("Token must be string"); if (typeof secretOrPublicKey !== "string" && typeof secretOrPublicKey !== "object") throw new Error("Invalid secret"); if (typeof options !== "object") throw new Error("Options must be object"); const allowedAlgorithms = options.algorithms || defaultAlgorithms(secretOrPublicKey); const { header, payload, signature } = decode(token); if (!(header.alg in algorithms)) throw new Error("Unsupported algorithm"); if (!allowedAlgorithms.includes(header.alg)) throw new Error("Unsupported algorithm"); const algorithm = algorithms[header.alg]; const key = await importKey(secretOrPublicKey, algorithm, "verify"); const isValid = await crypto.subtle.verify( algorithm, key, signature, new TextEncoder().encode(token.slice(0, token.lastIndexOf("."))) ); if (!isValid) throw new Error("Invalid signature"); const clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1e3); const clockTolerance = options.clockTolerance || 0; const nbf = payload.nbf; if (nbf && typeof nbf !== "number") throw new Error("Invalid nbf value"); if (payload.nbf > clockTimestamp + clockTolerance) throw new Error("Token not yet valid"); const exp = payload.exp; if (exp && typeof payload.exp !== "number") throw new Error("Invalid exp value"); if (clockTimestamp >= payload.exp + clockTolerance) throw new Error("Token expired"); return payload; } function decode(token) { const parts = token.split("."); if (parts.length !== 3) throw new Error("Invalid token"); const header = JSON.parse( textDecoder.decode(base64url.parse(parts[0], { loose: true })) ); const payload = JSON.parse( textDecoder.decode(base64url.parse(parts[1], { loose: true })) ); const signature = base64url.parse(parts[2], { loose: true }); return { header, payload, signature }; } export { decode, sign, verify };