UNPKG

auth-vir

Version:

Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.

62 lines (61 loc) 2.53 kB
import { check } from '@augment-vir/assert'; import { calculateRelativeDate, createFullDateInUserTimezone, getNowInUtcTimezone, toTimestamp, } from 'date-vir'; import { EncryptJWT, jwtDecrypt, jwtVerify, SignJWT } from 'jose'; const encryptionProtectedHeader = { alg: 'dir', enc: 'A256GCM' }; const signingProtectedHeader = { alg: 'HS512' }; /** * Creates a signed and encrypted JWT that contains the given data. * * @category Internal */ export async function createJwt( /** The data to be included in the JWT. */ data, params) { const rawJwt = new SignJWT({ data }) .setProtectedHeader(signingProtectedHeader) .setIssuedAt(params.issuedAt ? toTimestamp(createFullDateInUserTimezone(params.issuedAt)) : undefined) .setIssuer(params.issuer) .setAudience(params.audience) .setExpirationTime(toTimestamp(calculateRelativeDate(getNowInUtcTimezone(), params.jwtDuration))); if (params.notValidUntil) { rawJwt.setNotBefore(toTimestamp(createFullDateInUserTimezone(params.notValidUntil))); } const signedJwt = await rawJwt.sign(params.jwtKeys.signingKey); return await new EncryptJWT({ jwt: signedJwt }) .setProtectedHeader(encryptionProtectedHeader) .encrypt(params.jwtKeys.encryptionKey); } /** * Parse and extract all data from an encrypted and signed JWT. * * @category Internal * @throws Errors if the decryption, signature verification, or other JWT requirements fail */ export async function parseJwt(encryptedJwt, params) { const decryptedJwt = await jwtDecrypt(encryptedJwt, params.jwtKeys.encryptionKey); if (!check.deepEquals(decryptedJwt.protectedHeader, encryptionProtectedHeader)) { throw new Error('Invalid encryption protected header.'); } else if (!check.isString(decryptedJwt.payload.jwt)) { throw new TypeError('Decrypted jwt is not a string.'); } const verifiedJwt = await jwtVerify(decryptedJwt.payload.jwt, params.jwtKeys.signingKey, { issuer: params.issuer, audience: params.audience, requiredClaims: [ 'iat', 'aud', 'iss', ], }); if (!verifiedJwt.payload.iat || verifiedJwt.payload.iat * 1000 > Date.now()) { throw new Error('"iat" claim timestamp check failed'); } const data = verifiedJwt.payload.data; if (!check.deepEquals(verifiedJwt.protectedHeader, signingProtectedHeader)) { throw new Error('Invalid signing protected header.'); } return data; }