All files / token.ts

100.00% Branches 0/0
100.00% Functions 3/3
100.00% Lines 40/40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
x5
x5
 
 
 
 
 
 
 
 
 
x5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x5
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
 
 
x5
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
 
x2
x2
 
x5
x5
x7
x7


































































import { type JWK, SignJWT } from "jose";
import { encodeBase64Url } from "@std/encoding/base64url";

/** An RSA key the Issuer signs Tokens with; its public half is published in the JWKS. */
export interface SigningKey {
  kid: string;
  privateKey: CryptoKey;
  publicJwk: JWK;
}

/** Token lifetime (ADR-0001): 15 minutes. */
export const TOKEN_TTL_SECONDS = 15 * 60;

export interface MintParams {
  issuer: string;
  subject: string;
  audience: string;
  /** Raw public key string, surfaced as the `key` claim. */
  key: string;
  /** e.g. "ssh-ed25519" or "age", surfaced as `key_type`. */
  keyType: string;
  signingKey: SigningKey;
  nowSeconds: number;
  ttlSeconds?: number;
}

/** Mint the RS256 ID Token asserting possession of a key. */
export function mintToken(p: MintParams): Promise<string> {
  const ttl = p.ttlSeconds ?? TOKEN_TTL_SECONDS;
  return new SignJWT({ key: p.key, key_type: p.keyType })
    .setProtectedHeader({ alg: "RS256", kid: p.signingKey.kid, typ: "JWT" })
    .setIssuer(p.issuer)
    .setSubject(p.subject)
    .setAudience(p.audience)
    .setIssuedAt(p.nowSeconds)
    .setNotBefore(p.nowSeconds)
    .setExpirationTime(p.nowSeconds + ttl)
    .setJti(encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))))
    .sign(p.signingKey.privateKey);
}

/** The OIDC discovery document. */
export function discoveryDocument(issuer: string): Record<string, unknown> {
  return {
    issuer,
    jwks_uri: `${issuer}/.well-known/jwks.json`,
    id_token_signing_alg_values_supported: ["RS256"],
    subject_types_supported: ["public"],
    response_types_supported: ["id_token"],
    scopes_supported: ["openid"],
    claims_supported: [
      "iss",
      "sub",
      "aud",
      "iat",
      "nbf",
      "exp",
      "jti",
      "key",
      "key_type",
    ],
  };
}

/** The JWKS document — public halves only, one entry per (rotating) signing key. */
export function jwksDocument(keys: SigningKey[]): { keys: JWK[] } {
  return { keys: keys.map((k) => k.publicJwk) };
}