import type { PathLike } from 'node:fs'; import fs from 'node:fs/promises'; import * as Sentry from '@sentry/node'; import { importPKCS8, importSPKI, jwtVerify, SignJWT } from 'jose'; import type { KeyLike, JWTPayload } from 'jose'; import { rootDir } from './paths.ts'; import { getUrlForLocale, getUrlsForAllLocales } from '~/src/domain.ts'; class Jwt { constructor(private privateKey: KeyLike, private publicKey: KeyLike) {} static async from(privateKeyPath: PathLike, publicKeyPath: PathLike) { const privateKeyPromise = fs.readFile(privateKeyPath, 'utf-8') .then((privateKeyContent) => importPKCS8(privateKeyContent, 'RS256')); const publicKeyPromise = fs.readFile(publicKeyPath, 'utf-8') .then((publicKeyContent) => importSPKI(publicKeyContent, 'RS256')); const [privateKey, publicKey] = await Promise.all([privateKeyPromise, publicKeyPromise]); return new Jwt(privateKey, publicKey); } async sign( locale: string, payload: JWTPayload, expiresIn = '365d', domainBase: string | undefined = undefined, ): Promise { return await new SignJWT(payload) .setProtectedHeader({ alg: 'RS256' }) .setExpirationTime(expiresIn) .setAudience(getUrlsForAllLocales(locale, false, domainBase)) .setIssuer(getUrlForLocale(locale, domainBase)) .sign(this.privateKey); } async validate( locale: string, token: string, domainBase: string | undefined = undefined, ): Promise { const urls = getUrlsForAllLocales(locale, false, domainBase); try { const { payload } = await jwtVerify(token, this.publicKey, { algorithms: ['RS256'], audience: urls, issuer: urls, }); return payload; } catch (error) { Sentry.captureException(error); } } } export default await Jwt.from(`${rootDir}/keys/private.pem`, `${rootDir}/keys/public.pem`);