@fabrika/jwks 0.8.1 → 0.9.0-dev.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabrika/jwks",
3
- "version": "0.8.1",
3
+ "version": "0.9.0-dev.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,12 +12,13 @@
12
12
  },
13
13
  "files": [
14
14
  "src",
15
- "!src/**/__tests__/**"
15
+ "!src/**/__tests__/**",
16
+ "types"
16
17
  ],
17
18
  "exports": {
18
19
  ".": {
19
20
  "bun": "./src/index.ts",
20
- "types": "./src/index.ts",
21
+ "types": "./types/src/index.d.ts",
21
22
  "default": "./src/index.ts"
22
23
  }
23
24
  },
@@ -27,7 +28,7 @@
27
28
  },
28
29
  "dependencies": {
29
30
  "zod": "^3.25.76",
30
- "@fabrika/auth-core": "0.8.1",
31
+ "@fabrika/auth-core": "0.9.0-dev.2",
31
32
  "jose": "^5.9.0"
32
33
  }
33
34
  }
@@ -0,0 +1,3 @@
1
+ export { parseJwks } from './parse';
2
+ export { createJwksCache, JWKS_REFETCH_COOLDOWN_SECONDS, JWKS_TTL_SECONDS, TokenVerifier } from './verifier';
3
+ export type { JwksCache, TokenVerifierOptions, VerifyResult } from './verifier';
@@ -0,0 +1,3 @@
1
+ import type { Jwks } from '@fabrika/auth-core';
2
+ /** Reject the whole set when a member cannot be represented; dropping one would misreport an outage as an invalid token. */
3
+ export declare function parseJwks(body: unknown): Jwks | null;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The ONE JWKS-backed access-token verifier: fetch IAM's key set, cache it, verify every token
3
+ * locally with no network call on the warm path.
4
+ *
5
+ * `verify` is THREE-STATE, and the distinction is load-bearing: "this token is bad" is the caller's
6
+ * fault (401) and "we could not check" is ours (503). Both deny; only the second is an incident.
7
+ * Collapsing them would let a JWKS outage read as a forged token.
8
+ */
9
+ import { type AccessTokenClaims, type Jwks, type TokenAudienceProfile } from '@fabrika/auth-core';
10
+ import { createLocalJWKSet } from 'jose';
11
+ export type VerifyResult = {
12
+ ok: true;
13
+ claims: AccessTokenClaims;
14
+ }
15
+ /** Signature, issuer, audience, expiry or claim-shape failure — the token is not trustworthy. */
16
+ | {
17
+ ok: false;
18
+ reason: 'invalid';
19
+ }
20
+ /** The key set could not be obtained, so nothing could be checked. Deny, but say why. */
21
+ | {
22
+ ok: false;
23
+ reason: 'unavailable';
24
+ };
25
+ /** How long a fetched JWKS is reused before a refetch (a kid rotation also forces one on demand). */
26
+ export declare const JWKS_TTL_SECONDS = 600;
27
+ /**
28
+ * Minimum gap between two unknown-`kid` refetches. Bounds how often a token nobody can verify may
29
+ * cost an IAM round trip — the token is denied either way, and a real rotation is picked up within it.
30
+ */
31
+ export declare const JWKS_REFETCH_COOLDOWN_SECONDS = 30;
32
+ /** jose's local key-set resolver — what `jwtVerify` is handed once the JWKS is in hand. */
33
+ type LocalJwks = ReturnType<typeof createLocalJWKSet>;
34
+ /**
35
+ * The mutable state verification carries between calls: the cached key set and when an unknown `kid`
36
+ * last forced a refetch. Injectable because the app SDK keys it by the IAM BINDING — `@fabrika/app`
37
+ * builds a new SDK object per request, so per-instance state would refetch, and reset the cooldown,
38
+ * on every request.
39
+ */
40
+ export interface JwksCache {
41
+ entry: {
42
+ keys: LocalJwks;
43
+ expiresAt: number;
44
+ } | null;
45
+ lastForcedAt: number | null;
46
+ }
47
+ export declare function createJwksCache(): JwksCache;
48
+ export interface TokenVerifierOptions {
49
+ getJwks: () => Promise<Jwks>;
50
+ /** Byte-exact: it is the token's `iss` and jose compares it verbatim. */
51
+ issuer: string;
52
+ /** Unix seconds; injectable for tests. Defaults to the wall clock. */
53
+ now?: () => number;
54
+ ttlSeconds?: number;
55
+ refetchCooldownSeconds?: number;
56
+ /** Where the key set and the cooldown live. Defaults to state private to this verifier. */
57
+ cache?: JwksCache;
58
+ }
59
+ export declare class TokenVerifier {
60
+ private readonly getJwks;
61
+ private readonly issuer;
62
+ private readonly now;
63
+ private readonly ttlSeconds;
64
+ private readonly refetchCooldownSeconds;
65
+ private readonly cache;
66
+ constructor(options: TokenVerifierOptions);
67
+ /**
68
+ * Verify `token` for `audience` and narrow it to access claims. A bare string is an app id
69
+ * (`{ kind: 'app', app }`). An unknown `kid` forces one JWKS refetch (a key rotated in), then a
70
+ * retry — but at most once per cooldown, because otherwise any caller can turn every request into
71
+ * an IAM round trip just by inventing a `kid`.
72
+ */
73
+ verify(token: string, audience: string | TokenAudienceProfile): Promise<VerifyResult>;
74
+ /**
75
+ * May an unknown `kid` force a refetch right now? A rotation is rare and the token is denied either
76
+ * way, so rate-limiting this costs a few seconds of rotation lag and removes an amplification
77
+ * primitive: without it, N requests with N invented kids are N IAM fetches.
78
+ */
79
+ private mayForceRefetch;
80
+ /** Returns the payload, `null` on a genuine verification failure, or `NO_KEY` on a kid miss. */
81
+ private verifyWith;
82
+ private keySet;
83
+ }
84
+ export {};