auth-vir
Version:
Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.
62 lines (61 loc) • 2.53 kB
JavaScript
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;
}