@uln/auth 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/middleware.ts CHANGED
@@ -1,35 +1,65 @@
1
- import { extractCookie } from "./cookie.ts";
1
+ import { extractCookie, makeSessionCookie } from "./cookie.ts";
2
2
  import { validateToken } from "./token.ts";
3
- import type { CookieConfig, TokenConfig, TokenPayload } from "./types.ts";
3
+ import type { CookieConfig, TokenPayload, VerifyConfig } from "./types.ts";
4
4
 
5
5
  export function getClientIP(request: Request): string {
6
- return (
7
- request.headers.get("cf-connecting-ip") ??
8
- request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
9
- "unknown"
10
- );
6
+ return (
7
+ request.headers.get("cf-connecting-ip") ??
8
+ request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
9
+ "unknown"
10
+ );
11
11
  }
12
12
 
13
13
  export function checkBearerToken(request: Request, expectedToken: string): boolean {
14
- const header = request.headers.get("Authorization");
15
- return header === `Bearer ${expectedToken}`;
14
+ const header = request.headers.get("Authorization");
15
+ return header === `Bearer ${expectedToken}`;
16
16
  }
17
17
 
18
18
  export async function getAuthUser(
19
- request: Request,
20
- tokenConfig: TokenConfig,
21
- cookieConfig: CookieConfig,
19
+ request: Request,
20
+ verifyConfig: VerifyConfig,
21
+ cookieConfig: CookieConfig,
22
22
  ): Promise<TokenPayload | null> {
23
- const cookieHeader = request.headers.get("Cookie");
24
- if (!cookieHeader) return null;
23
+ const cookieHeader = request.headers.get("Cookie");
24
+ if (!cookieHeader) return null;
25
25
 
26
- const token = extractCookie(cookieHeader, cookieConfig.name);
27
- if (!token) return null;
26
+ const token = extractCookie(cookieHeader, cookieConfig.name);
27
+ if (!token) return null;
28
28
 
29
- return validateToken(tokenConfig, token);
29
+ return validateToken(verifyConfig, token);
30
30
  }
31
31
 
32
32
  export function redirectToLogin(currentUrl: string): Response {
33
- const loginUrl = `https://auth.uln.industries/login?redirect=${encodeURIComponent(currentUrl)}`;
34
- return Response.redirect(loginUrl, 302);
33
+ const loginUrl = `https://auth.uln.industries/login?redirect=${encodeURIComponent(currentUrl)}`;
34
+ return Response.redirect(loginUrl, 302);
35
+ }
36
+
37
+ /**
38
+ * Handle the auth callback on consuming sites.
39
+ * If the request URL has a `?token=` param, validates it, sets a session cookie,
40
+ * and redirects to the same URL without the token param.
41
+ * Returns null if there's no token param (not a callback request).
42
+ */
43
+ export async function handleCallback(
44
+ request: Request,
45
+ verifyConfig: VerifyConfig,
46
+ cookieConfig: CookieConfig,
47
+ ): Promise<Response | null> {
48
+ const url = new URL(request.url);
49
+ const token = url.searchParams.get("token");
50
+ if (!token) return null;
51
+
52
+ const payload = await validateToken(verifyConfig, token);
53
+ if (!payload) {
54
+ return redirectToLogin(url.origin);
55
+ }
56
+
57
+ url.searchParams.delete("token");
58
+ return new Response(null, {
59
+ status: 302,
60
+ headers: {
61
+ Location: url.toString(),
62
+ "Set-Cookie": makeSessionCookie(cookieConfig, token),
63
+ },
64
+ });
35
65
  }
package/src/password.ts CHANGED
@@ -3,55 +3,55 @@ const KEY_LENGTH = 64;
3
3
  const ITERATIONS = 100_000;
4
4
 
5
5
  function toHex(buffer: ArrayBuffer): string {
6
- return Array.from(new Uint8Array(buffer))
7
- .map((b) => b.toString(16).padStart(2, "0"))
8
- .join("");
6
+ return Array.from(new Uint8Array(buffer))
7
+ .map((b) => b.toString(16).padStart(2, "0"))
8
+ .join("");
9
9
  }
10
10
 
11
11
  function fromHex(hex: string): Uint8Array<ArrayBuffer> {
12
- const bytes = new Uint8Array(hex.length / 2);
13
- for (let i = 0; i < hex.length; i += 2) {
14
- bytes[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);
15
- }
16
- return bytes;
12
+ const bytes = new Uint8Array(hex.length / 2);
13
+ for (let i = 0; i < hex.length; i += 2) {
14
+ bytes[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);
15
+ }
16
+ return bytes;
17
17
  }
18
18
 
19
19
  async function deriveKey(password: string, salt: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer> {
20
- const encoder = new TextEncoder();
21
- const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveBits"]);
22
- return crypto.subtle.deriveBits(
23
- {
24
- name: "PBKDF2",
25
- salt,
26
- iterations: ITERATIONS,
27
- hash: "SHA-256",
28
- },
29
- keyMaterial,
30
- KEY_LENGTH * 8,
31
- );
20
+ const encoder = new TextEncoder();
21
+ const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveBits"]);
22
+ return crypto.subtle.deriveBits(
23
+ {
24
+ name: "PBKDF2",
25
+ salt,
26
+ iterations: ITERATIONS,
27
+ hash: "SHA-256",
28
+ },
29
+ keyMaterial,
30
+ KEY_LENGTH * 8,
31
+ );
32
32
  }
33
33
 
34
34
  export async function hashPassword(password: string): Promise<string> {
35
- const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
36
- const derived = await deriveKey(password, salt);
37
- return `${toHex(salt.buffer)}:${toHex(derived)}`;
35
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
36
+ const derived = await deriveKey(password, salt);
37
+ return `${toHex(salt.buffer)}:${toHex(derived)}`;
38
38
  }
39
39
 
40
40
  export async function verifyPassword(password: string, storedHash: string): Promise<boolean> {
41
- const separatorIndex = storedHash.indexOf(":");
42
- if (separatorIndex === -1) return false;
43
-
44
- const saltHex = storedHash.substring(0, separatorIndex);
45
- const keyHex = storedHash.substring(separatorIndex + 1);
46
- const salt = fromHex(saltHex);
47
- const expectedKey = fromHex(keyHex);
48
- const derived = new Uint8Array(await deriveKey(password, salt));
49
-
50
- if (derived.length !== expectedKey.length) return false;
51
-
52
- let diff = 0;
53
- for (let i = 0; i < derived.length; i++) {
54
- diff |= (derived[i] ?? 0) ^ (expectedKey[i] ?? 0);
55
- }
56
- return diff === 0;
41
+ const separatorIndex = storedHash.indexOf(":");
42
+ if (separatorIndex === -1) return false;
43
+
44
+ const saltHex = storedHash.substring(0, separatorIndex);
45
+ const keyHex = storedHash.substring(separatorIndex + 1);
46
+ const salt = fromHex(saltHex);
47
+ const expectedKey = fromHex(keyHex);
48
+ const derived = new Uint8Array(await deriveKey(password, salt));
49
+
50
+ if (derived.length !== expectedKey.length) return false;
51
+
52
+ let diff = 0;
53
+ for (let i = 0; i < derived.length; i++) {
54
+ diff |= (derived[i] ?? 0) ^ (expectedKey[i] ?? 0);
55
+ }
56
+ return diff === 0;
57
57
  }
package/src/rate-limit.ts CHANGED
@@ -1,38 +1,38 @@
1
1
  import type { RateLimitConfig, RateLimitStore } from "./types.ts";
2
2
 
3
3
  export interface RateLimitResult {
4
- blocked: boolean;
5
- remainingSeconds?: number;
4
+ blocked: boolean;
5
+ remainingSeconds?: number;
6
6
  }
7
7
 
8
8
  export async function checkRateLimit(
9
- store: RateLimitStore,
10
- key: string,
11
- config: RateLimitConfig,
9
+ store: RateLimitStore,
10
+ key: string,
11
+ config: RateLimitConfig,
12
12
  ): Promise<RateLimitResult> {
13
- const entry = await store.get(key);
14
- if (!entry) return { blocked: false };
13
+ const entry = await store.get(key);
14
+ if (!entry) return { blocked: false };
15
15
 
16
- if (entry.attempts >= config.maxAttempts) {
17
- const elapsed = Math.floor((Date.now() - entry.firstAttempt) / 1000);
18
- const remaining = config.windowSeconds - elapsed;
19
- if (remaining > 0) return { blocked: true, remainingSeconds: remaining };
20
- await store.delete(key);
21
- return { blocked: false };
22
- }
16
+ if (entry.attempts >= config.maxAttempts) {
17
+ const elapsed = Math.floor((Date.now() - entry.firstAttempt) / 1000);
18
+ const remaining = config.windowSeconds - elapsed;
19
+ if (remaining > 0) return { blocked: true, remainingSeconds: remaining };
20
+ await store.delete(key);
21
+ return { blocked: false };
22
+ }
23
23
 
24
- return { blocked: false };
24
+ return { blocked: false };
25
25
  }
26
26
 
27
27
  export async function recordFailedAttempt(store: RateLimitStore, key: string, config: RateLimitConfig): Promise<void> {
28
- const entry = await store.get(key);
29
- if (entry) {
30
- await store.set(key, { attempts: entry.attempts + 1, firstAttempt: entry.firstAttempt }, config.windowSeconds);
31
- } else {
32
- await store.set(key, { attempts: 1, firstAttempt: Date.now() }, config.windowSeconds);
33
- }
28
+ const entry = await store.get(key);
29
+ if (entry) {
30
+ await store.set(key, { attempts: entry.attempts + 1, firstAttempt: entry.firstAttempt }, config.windowSeconds);
31
+ } else {
32
+ await store.set(key, { attempts: 1, firstAttempt: Date.now() }, config.windowSeconds);
33
+ }
34
34
  }
35
35
 
36
36
  export async function clearRateLimit(store: RateLimitStore, key: string): Promise<void> {
37
- await store.delete(key);
37
+ await store.delete(key);
38
38
  }
package/src/service.ts CHANGED
@@ -8,23 +8,34 @@ export type { TokenPayload, CookieConfig } from "./types.ts";
8
8
  * Returns the token payload or null if invalid.
9
9
  */
10
10
  export async function verifyToken(authService: Fetcher, token: string): Promise<TokenPayload | null> {
11
- const res = await authService.fetch("https://auth/api/verify-token", {
12
- method: "POST",
13
- headers: { "Content-Type": "application/json" },
14
- body: JSON.stringify({ token }),
15
- });
16
- if (!res.ok) return null;
17
- const data: { user: { id: string; email: string; name: string; admin: boolean; scopes: string[] } } =
18
- await res.json();
19
- return {
20
- sub: data.user.id,
21
- email: data.user.email,
22
- name: data.user.name,
23
- admin: data.user.admin,
24
- scopes: data.user.scopes,
25
- iat: 0,
26
- exp: 0,
27
- };
11
+ const res = await authService.fetch("https://auth/api/verify-token", {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify({ token }),
15
+ });
16
+ if (!res.ok) return null;
17
+ const data = (await res.json()) as {
18
+ user: {
19
+ id: string;
20
+ email: string;
21
+ name: string;
22
+ admin: boolean;
23
+ scopes: string[];
24
+ flags: Record<string, string[]>;
25
+ kid: string;
26
+ };
27
+ };
28
+ return {
29
+ sub: data.user.id,
30
+ email: data.user.email,
31
+ name: data.user.name,
32
+ admin: data.user.admin,
33
+ scopes: data.user.scopes,
34
+ flags: data.user.flags,
35
+ kid: data.user.kid,
36
+ iat: 0,
37
+ exp: 0,
38
+ };
28
39
  }
29
40
 
30
41
  /**
@@ -32,23 +43,23 @@ export async function verifyToken(authService: Fetcher, token: string): Promise<
32
43
  * Returns the token payload or null if not authenticated.
33
44
  */
34
45
  export async function getAuthUser(
35
- request: Request,
36
- authService: Fetcher,
37
- cookieConfig: CookieConfig,
46
+ request: Request,
47
+ authService: Fetcher,
48
+ cookieConfig: CookieConfig,
38
49
  ): Promise<TokenPayload | null> {
39
- const cookieHeader = request.headers.get("Cookie");
40
- if (!cookieHeader) return null;
50
+ const cookieHeader = request.headers.get("Cookie");
51
+ if (!cookieHeader) return null;
41
52
 
42
- const token = extractCookie(cookieHeader, cookieConfig.name);
43
- if (!token) return null;
53
+ const token = extractCookie(cookieHeader, cookieConfig.name);
54
+ if (!token) return null;
44
55
 
45
- return verifyToken(authService, token);
56
+ return verifyToken(authService, token);
46
57
  }
47
58
 
48
59
  /**
49
60
  * Redirect to the centralized login page at auth.uln.industries.
50
61
  */
51
62
  export function redirectToLogin(currentUrl: string): Response {
52
- const loginUrl = `https://auth.uln.industries/login?redirect=${encodeURIComponent(currentUrl)}`;
53
- return Response.redirect(loginUrl, 302);
63
+ const loginUrl = `https://auth.uln.industries/login?redirect=${encodeURIComponent(currentUrl)}`;
64
+ return Response.redirect(loginUrl, 302);
54
65
  }
package/src/token.ts CHANGED
@@ -1,87 +1,109 @@
1
- import type { TokenConfig, TokenPayload } from "./types.ts";
1
+ import type { SigningConfig, TokenPayload, VerifyConfig } from "./types.ts";
2
2
 
3
3
  const encoder = new TextEncoder();
4
4
 
5
5
  function base64urlEncode(data: Uint8Array): string {
6
- let binary = "";
7
- for (const byte of data) {
8
- binary += String.fromCharCode(byte);
9
- }
10
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6
+ let binary = "";
7
+ for (const byte of data) {
8
+ binary += String.fromCharCode(byte);
9
+ }
10
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
11
11
  }
12
12
 
13
13
  function base64urlDecode(str: string): Uint8Array {
14
- const padded = str.replace(/-/g, "+").replace(/_/g, "/");
15
- const binary = atob(padded);
16
- const bytes = new Uint8Array(binary.length);
17
- for (let i = 0; i < binary.length; i++) {
18
- bytes[i] = binary.charCodeAt(i);
19
- }
20
- return bytes;
14
+ const padded = str.replace(/-/g, "+").replace(/_/g, "/");
15
+ const binary = atob(padded);
16
+ const bytes = new Uint8Array(binary.length);
17
+ for (let i = 0; i < binary.length; i++) {
18
+ bytes[i] = binary.charCodeAt(i);
19
+ }
20
+ return bytes;
21
21
  }
22
22
 
23
- async function hmacSign(secret: string, data: string): Promise<string> {
24
- const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [
25
- "sign",
26
- ]);
27
- const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
28
- return base64urlEncode(new Uint8Array(signature));
23
+ const ECDSA_PARAMS = { name: "ECDSA", namedCurve: "P-256" } as const;
24
+ const SIGN_PARAMS = { name: "ECDSA", hash: "SHA-256" } as const;
25
+
26
+ export async function generateKeyPair(): Promise<{ kid: string; privateKeyJwk: JsonWebKey; publicKeyJwk: JsonWebKey }> {
27
+ const kid = crypto.randomUUID().slice(0, 8);
28
+ const keyPair = await crypto.subtle.generateKey(ECDSA_PARAMS, true, ["sign", "verify"]);
29
+ if (!("privateKey" in keyPair)) throw new Error("Expected CryptoKeyPair from ECDSA generateKey");
30
+ const privateExport = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
31
+ const publicExport = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
32
+ if (privateExport instanceof ArrayBuffer || publicExport instanceof ArrayBuffer)
33
+ throw new Error("Expected JWK from exportKey");
34
+ return { kid, privateKeyJwk: privateExport, publicKeyJwk: publicExport };
35
+ }
36
+
37
+ export async function importPrivateKey(jwk: JsonWebKey): Promise<CryptoKey> {
38
+ return crypto.subtle.importKey("jwk", jwk, ECDSA_PARAMS, false, ["sign"]);
29
39
  }
30
40
 
31
- function timingSafeEqual(a: string, b: string): boolean {
32
- if (a.length !== b.length) return false;
33
- let diff = 0;
34
- for (let i = 0; i < a.length; i++) {
35
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
36
- }
37
- return diff === 0;
41
+ export async function importPublicKey(jwk: JsonWebKey): Promise<CryptoKey> {
42
+ return crypto.subtle.importKey("jwk", jwk, ECDSA_PARAMS, false, ["verify"]);
38
43
  }
39
44
 
40
45
  export interface CreateTokenInput {
41
- id: string;
42
- email: string;
43
- name: string;
44
- isAdmin: boolean;
45
- scopes: string[];
46
+ id: string;
47
+ email: string;
48
+ name: string;
49
+ isAdmin: boolean;
50
+ scopes: string[];
51
+ flags: Record<string, string[]>;
46
52
  }
47
53
 
48
- export async function createToken(config: TokenConfig, user: CreateTokenInput): Promise<string> {
49
- const now = Math.floor(Date.now() / 1000);
50
- const payload: TokenPayload = {
51
- sub: user.id,
52
- email: user.email,
53
- name: user.name,
54
- admin: user.isAdmin,
55
- scopes: user.scopes,
56
- iat: now,
57
- exp: now + config.maxAgeSeconds,
58
- };
59
- const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
60
- const sig = await hmacSign(config.secret, payloadB64);
61
- return `${payloadB64}.${sig}`;
54
+ export async function createToken(config: SigningConfig, user: CreateTokenInput): Promise<string> {
55
+ const now = Math.floor(Date.now() / 1000);
56
+ const payload: TokenPayload = {
57
+ sub: user.id,
58
+ email: user.email,
59
+ name: user.name,
60
+ admin: user.isAdmin,
61
+ scopes: user.scopes,
62
+ flags: user.flags,
63
+ kid: config.kid,
64
+ iat: now,
65
+ exp: now + config.maxAgeSeconds,
66
+ };
67
+ const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
68
+ const payloadBytes = encoder.encode(payloadB64);
69
+ const signature = await crypto.subtle.sign(SIGN_PARAMS, config.privateKey, payloadBytes);
70
+ const sigB64 = base64urlEncode(new Uint8Array(signature));
71
+ return `${payloadB64}.${sigB64}`;
62
72
  }
63
73
 
64
- export async function validateToken(config: TokenConfig, token: string): Promise<TokenPayload | null> {
65
- const dotIndex = token.indexOf(".");
66
- if (dotIndex === -1) return null;
74
+ export async function validateToken(config: VerifyConfig, token: string): Promise<TokenPayload | null> {
75
+ const dotIndex = token.indexOf(".");
76
+ if (dotIndex === -1) return null;
77
+
78
+ const payloadB64 = token.substring(0, dotIndex);
79
+ const sigB64 = token.substring(dotIndex + 1);
67
80
 
68
- const payloadB64 = token.substring(0, dotIndex);
69
- const sig = token.substring(dotIndex + 1);
81
+ let payload: TokenPayload;
82
+ try {
83
+ const decoded = new TextDecoder().decode(base64urlDecode(payloadB64));
84
+ payload = JSON.parse(decoded) as TokenPayload;
85
+ } catch {
86
+ return null;
87
+ }
70
88
 
71
- const expectedSig = await hmacSign(config.secret, payloadB64);
72
- if (!timingSafeEqual(sig, expectedSig)) return null;
89
+ if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || typeof payload.kid !== "string") {
90
+ return null;
91
+ }
73
92
 
74
- try {
75
- const decoded = new TextDecoder().decode(base64urlDecode(payloadB64));
76
- const payload = JSON.parse(decoded) as TokenPayload;
93
+ const now = Math.floor(Date.now() / 1000);
94
+ if (now > payload.exp) return null;
77
95
 
78
- if (typeof payload.sub !== "string" || typeof payload.exp !== "number") return null;
96
+ const entry = config.publicKeys.find((k) => k.kid === payload.kid);
97
+ if (!entry) return null;
79
98
 
80
- const now = Math.floor(Date.now() / 1000);
81
- if (now > payload.exp) return null;
99
+ try {
100
+ const signatureBytes = base64urlDecode(sigB64);
101
+ const payloadBytes = encoder.encode(payloadB64);
102
+ const valid = await crypto.subtle.verify(SIGN_PARAMS, entry.key, signatureBytes, payloadBytes);
103
+ if (!valid) return null;
104
+ } catch {
105
+ return null;
106
+ }
82
107
 
83
- return payload;
84
- } catch {
85
- return null;
86
- }
108
+ return payload;
87
109
  }
package/src/types.ts CHANGED
@@ -1,55 +1,68 @@
1
1
  export interface User {
2
- id: string;
3
- email: string;
4
- name: string;
5
- passwordHash: string;
6
- isAdmin: boolean;
7
- emailVerified: boolean;
8
- createdAt: string;
9
- updatedAt: string;
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ passwordHash: string;
6
+ isAdmin: boolean;
7
+ emailVerified: boolean;
8
+ createdAt: string;
9
+ updatedAt: string;
10
10
  }
11
11
 
12
12
  export interface UserStore {
13
- getUser(id: string): Promise<User | null>;
14
- getUserByEmail(email: string): Promise<User | null>;
15
- listUsers(): Promise<User[]>;
16
- saveUser(user: User): Promise<void>;
17
- deleteUser(id: string): Promise<void>;
13
+ getUser(id: string): Promise<User | null>;
14
+ getUserByEmail(email: string): Promise<User | null>;
15
+ listUsers(): Promise<User[]>;
16
+ saveUser(user: User): Promise<void>;
17
+ deleteUser(id: string): Promise<void>;
18
18
  }
19
19
 
20
20
  export interface RateLimitEntry {
21
- attempts: number;
22
- firstAttempt: number;
21
+ attempts: number;
22
+ firstAttempt: number;
23
23
  }
24
24
 
25
25
  export interface RateLimitStore {
26
- get(key: string): Promise<RateLimitEntry | null>;
27
- set(key: string, entry: RateLimitEntry, ttlSeconds: number): Promise<void>;
28
- delete(key: string): Promise<void>;
26
+ get(key: string): Promise<RateLimitEntry | null>;
27
+ set(key: string, entry: RateLimitEntry, ttlSeconds: number): Promise<void>;
28
+ delete(key: string): Promise<void>;
29
29
  }
30
30
 
31
31
  export interface RateLimitConfig {
32
- maxAttempts: number;
33
- windowSeconds: number;
32
+ maxAttempts: number;
33
+ windowSeconds: number;
34
34
  }
35
35
 
36
36
  export interface TokenPayload {
37
- sub: string;
38
- email: string;
39
- name: string;
40
- admin: boolean;
41
- scopes: string[];
42
- iat: number;
43
- exp: number;
37
+ sub: string;
38
+ email: string;
39
+ name: string;
40
+ admin: boolean;
41
+ scopes: string[];
42
+ flags: Record<string, string[]>;
43
+ kid: string;
44
+ iat: number;
45
+ exp: number;
44
46
  }
45
47
 
46
- export interface TokenConfig {
47
- secret: string;
48
- maxAgeSeconds: number;
48
+ export interface PublicKeyEntry {
49
+ kid: string;
50
+ key: CryptoKey;
51
+ }
52
+
53
+ export interface SigningConfig {
54
+ privateKey: CryptoKey;
55
+ kid: string;
56
+ maxAgeSeconds: number;
57
+ }
58
+
59
+ export interface VerifyConfig {
60
+ publicKeys: PublicKeyEntry[];
61
+ maxAgeSeconds: number;
49
62
  }
50
63
 
51
64
  export interface CookieConfig {
52
- name: string;
53
- maxAgeSeconds: number;
54
- secure: boolean;
65
+ name: string;
66
+ maxAgeSeconds: number;
67
+ secure: boolean;
55
68
  }