auth

Main authentication module for my projects
git clone git://git.finwo.net/app/auth
Log | Files | Refs

keys.js (2335B)


      1 import crypto from 'node:crypto';
      2 import {
      3   SignJWT,
      4   exportJWK,
      5   generateKeyPair,
      6   importJWK,
      7   jwtVerify,
      8 } from 'jose';
      9 
     10 const pending = new WeakMap();
     11 
     12 async function loadSigningKey(db) {
     13   const row = await db.get(
     14     `SELECT id, alg, private_jwk, public_jwk
     15        FROM signing_keys
     16       WHERE status = 'active'
     17       ORDER BY created_at DESC
     18       LIMIT 1`
     19   );
     20   if (row) {
     21     return {
     22       id: row.id,
     23       alg: row.alg,
     24       privateJwk: JSON.parse(row.private_jwk),
     25       publicJwk: JSON.parse(row.public_jwk),
     26     };
     27   }
     28 
     29   const { privateKey, publicKey } = await generateKeyPair('ES256', { extractable: true });
     30   const id = crypto.randomUUID();
     31   const privateJwk = await exportJWK(privateKey);
     32   const publicJwk = await exportJWK(publicKey);
     33 
     34   privateJwk.kid = id;
     35   privateJwk.alg = 'ES256';
     36   privateJwk.use = 'sig';
     37   publicJwk.kid = id;
     38   publicJwk.alg = 'ES256';
     39   publicJwk.use = 'sig';
     40 
     41   await db.run(
     42     `INSERT INTO signing_keys (id, alg, private_jwk, public_jwk, status, created_at)
     43      VALUES ({id}, 'ES256', {privateJwk}, {publicJwk}, 'active', {createdAt})`,
     44     {
     45       id,
     46       privateJwk: JSON.stringify(privateJwk),
     47       publicJwk: JSON.stringify(publicJwk),
     48       createdAt: Date.now(),
     49     }
     50   );
     51 
     52   return { id, alg: 'ES256', privateJwk, publicJwk };
     53 }
     54 
     55 export function ensureSigningKey(db) {
     56   let promise = pending.get(db);
     57   if (!promise) {
     58     promise = loadSigningKey(db);
     59     pending.set(db, promise);
     60   }
     61   return promise;
     62 }
     63 
     64 export async function getJwks(db) {
     65   const key = await ensureSigningKey(db);
     66   return { keys: [key.publicJwk] };
     67 }
     68 
     69 export async function signToken(db, claims, { issuer, audience, expiresIn }) {
     70   const key = await ensureSigningKey(db);
     71   const privateKey = await importJWK(key.privateJwk, key.alg);
     72   const now = Math.floor(Date.now() / 1000);
     73 
     74   return new SignJWT(claims)
     75     .setProtectedHeader({ alg: key.alg, kid: key.id, typ: 'JWT' })
     76     .setIssuer(issuer)
     77     .setAudience(audience)
     78     .setIssuedAt(now)
     79     .setExpirationTime(now + expiresIn)
     80     .sign(privateKey);
     81 }
     82 
     83 export async function verifyToken(db, token, { issuer, audience }) {
     84   const key = await ensureSigningKey(db);
     85   const publicKey = await importJWK(key.publicJwk, key.alg);
     86   const { payload } = await jwtVerify(token, publicKey, { issuer, audience });
     87   return payload;
     88 }