@ever-works/oidc-identity 1.0.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.
@@ -0,0 +1,321 @@
1
+ /**
2
+ * APW-12 T8 — a **fake OpenID Connect provider**, published only through this
3
+ * package's `./testing` subpath (plan §10.4).
4
+ *
5
+ * The API integration spec (`apps/api/src/auth/ever-id.flow.integration.spec.ts`,
6
+ * T20/T25) and the Playwright lanes both need a provider that answers the way Ever
7
+ * ID will: a discovery document, a key set, an authorization endpoint that
8
+ * approves a configured person, a token endpoint that **checks PKCE**, device
9
+ * authorization with FR-41's polling rules, an end-session endpoint, and helpers
10
+ * that mint the access and logout tokens the verifiers are given. One fake, in one
11
+ * place, because a second one would be a second set of provider behaviours to keep
12
+ * in step.
13
+ *
14
+ * ## Why the subpath, and why that is load-bearing
15
+ *
16
+ * Plan §10.4: "published only through a `./testing` subpath export, never from the
17
+ * main entry". The main entry (`src/index.ts`) is what the plugin loader imports in
18
+ * production, and a fake provider that reached it would be dead weight in every
19
+ * API process — and, worse, an HTTP server behind a class the platform already
20
+ * instantiates. `package.json`'s `exports["./testing"]` and `tsup`'s second entry
21
+ * are what make that separation real; `src/__tests__/fake-oidc-provider.spec.ts`
22
+ * asserts it from both the source side and (when a build exists) the built side.
23
+ *
24
+ * ## What it is not
25
+ *
26
+ * It is not a security reference. It signs with ES256 keys it generates
27
+ * (`node:crypto`, plan §10.4 — not `jose`, so a bug in the library under test
28
+ * cannot hide a bug in the fake), it keeps device grants and authorization codes
29
+ * in memory, and it makes no attempt to be a *correct* provider in the corners the
30
+ * epic does not read. Where it deliberately *is* strict — PKCE S256, single-use
31
+ * codes, `client_secret_basic`, FR-41's poll interval — the strictness is the
32
+ * point: those are the behaviours the relying party has to get right, and a fake
33
+ * that accepted anything would prove nothing about them.
34
+ *
35
+ * ## Time
36
+ *
37
+ * Every clock read goes through the injected `now` (epoch **milliseconds**,
38
+ * defaulting to `Date.now`), and every minted claim is stamped from it. A spec
39
+ * therefore drives token lifetimes, the device-poll interval and `iat`/`exp` with
40
+ * literal values and an injected clock rather than by sleeping — the rule this
41
+ * package learned the hard way in T6.
42
+ */
43
+ /**
44
+ * The `events` member of a back-channel logout notice — OpenID Connect
45
+ * Back-Channel Logout 1.0 §2.4's event identifier.
46
+ *
47
+ * Spelled here rather than imported from the plugin on purpose: this module stands
48
+ * in for an **external** provider, and a provider does not read the relying
49
+ * party's constants. `fake-oidc-provider.spec.ts` asserts that this literal and
50
+ * `OIDC_BACKCHANNEL_LOGOUT_EVENT` are the same string, so the two cannot drift
51
+ * without a red test.
52
+ */
53
+ declare const FAKE_BACKCHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
54
+ /** The public client an exchange token names by default. */
55
+ declare const FAKE_DEFAULT_LOCAL_CLIENT_ID = "ever-works-cli";
56
+ /** The person the provider approves when a spec configures nobody (§10.4). */
57
+ declare const FAKE_DEFAULT_USER: {
58
+ readonly subject: "ever-id-subject-1";
59
+ readonly email: "person@example.com";
60
+ readonly emailVerified: true;
61
+ readonly name: "A Person";
62
+ };
63
+ /** The document fields a spec may ask the fake to leave out (FR-3's rows, FR-36's `null`). */
64
+ type FakeOidcOmittableField = 'authorization_endpoint' | 'token_endpoint' | 'jwks_uri' | 'end_session_endpoint' | 'device_authorization_endpoint' | 'code_challenge_methods_supported' | 'backchannel_logout_supported';
65
+ /** The person this provider signs in. */
66
+ interface FakeOidcUser {
67
+ readonly subject: string;
68
+ readonly email: string;
69
+ readonly emailVerified?: boolean;
70
+ readonly name?: string;
71
+ }
72
+ /** Construction options for {@link FakeOidcProvider.start}. */
73
+ interface FakeOidcProviderOptions {
74
+ /** The person auto-approved at `/authorize` and at device authorization. */
75
+ readonly user?: FakeOidcUser;
76
+ /** The relying party's client id. Default `ever-works-web`. */
77
+ readonly clientId?: string;
78
+ /**
79
+ * The `client_secret_basic` secret the token endpoint requires, or `null` to
80
+ * accept any (a spec that is not exercising client authentication).
81
+ */
82
+ readonly clientSecret?: string | null;
83
+ /** The audience a minted access token carries. Default `ever-works`. */
84
+ readonly apiAudience?: string;
85
+ /** The public clients an exchange token may name in `azp`. */
86
+ readonly localClients?: readonly {
87
+ readonly kind: 'cli' | 'node';
88
+ readonly clientId: string;
89
+ }[];
90
+ /** FR-44's scope a delegated access token carries. Default `apps:read`. */
91
+ readonly delegatedScope?: string;
92
+ /** FR-39/FR-40's scope an exchange access token carries. Default `ever-works:session`. */
93
+ readonly exchangeScope?: string;
94
+ /** How long a minted access token lives when the caller does not say. Default 300. */
95
+ readonly accessTokenLifetimeSeconds?: number;
96
+ /** FR-41's poll interval the device endpoint returns, in seconds. Default 5. */
97
+ readonly devicePollIntervalSeconds?: number;
98
+ /** How long a device code lives. Default 900 (FR-41). */
99
+ readonly deviceCodeLifetimeSeconds?: number;
100
+ /** How long an authorization code lives. Default 60. */
101
+ readonly authorizationCodeLifetimeSeconds?: number;
102
+ /** Document fields to leave out, so a spec can drive the rows and the `null` answers. */
103
+ readonly omit?: readonly FakeOidcOmittableField[];
104
+ /** The clock, in epoch milliseconds. Defaults to `Date.now`. */
105
+ readonly now?: () => number;
106
+ }
107
+ /** One request the provider received, so a spec can assert on what actually went out. */
108
+ interface FakeOidcCall {
109
+ readonly method: string;
110
+ readonly path: string;
111
+ /** The query string, as a plain object (a repeated key keeps its first value). */
112
+ readonly query: Record<string, string>;
113
+ /** The `application/x-www-form-urlencoded` body, as a plain object. */
114
+ readonly form: Record<string, string>;
115
+ /** The request headers, lower-cased. */
116
+ readonly headers: Record<string, string>;
117
+ }
118
+ /** What a device-code poll answered. */
119
+ interface FakeDevicePoll {
120
+ readonly status: number;
121
+ readonly body: Record<string, unknown>;
122
+ }
123
+ /** What {@link FakeOidcProvider.start} resolves to. */
124
+ type FakeOidcProviderStarted = FakeOidcProvider;
125
+ /**
126
+ * A local OpenID Connect provider, for specs.
127
+ *
128
+ * `start()` binds an ephemeral port on the loopback interface and answers on
129
+ * `http://localhost:<port>` — the one non-TLS issuer `settings.schema.ts` accepts
130
+ * and the one the plugin's configuration tells `openid-client` about
131
+ * (`isInsecureIssuer`). The issuer therefore depends on the port and is only known
132
+ * after the server is listening, which is why construction goes through `start()`.
133
+ */
134
+ declare class FakeOidcProvider {
135
+ /** Every request, in order — method, path, query, form and headers. */
136
+ readonly calls: FakeOidcCall[];
137
+ private readonly options;
138
+ private readonly keys;
139
+ private readonly codes;
140
+ private readonly devices;
141
+ private server;
142
+ private issuerUrl;
143
+ private keySequence;
144
+ /** The person approved from now on, when a spec changed it after start (see {@link setUser}). */
145
+ private userOverride;
146
+ /** Extra claims every ID token minted from now on carries (see {@link setIdTokenClaims}). */
147
+ private extraIdTokenClaims;
148
+ /** The provider session id (`sid`) the next tokens carry (see {@link setSessionId}). */
149
+ private sessionId;
150
+ private constructor();
151
+ /** Bind a port and answer the discovery document a relying party will read. */
152
+ static start(options?: FakeOidcProviderOptions): Promise<FakeOidcProvider>;
153
+ /**
154
+ * Bind an ephemeral loopback port and answer `/…/.well-known/openid-configuration`.
155
+ *
156
+ * Kept additive and public so a caller that wants a provider on a **fixed** port
157
+ * (a Playwright lane that must tell the API where to find it before the process
158
+ * starts) can build one; `start()` is the ordinary entry point.
159
+ */
160
+ listen(port?: number): Promise<void>;
161
+ /** The issuer the discovery document advertises, and the settings' `issuerUrl`. */
162
+ get issuer(): string;
163
+ /** The bound port. */
164
+ get port(): number;
165
+ get discoveryUrl(): string;
166
+ get jwksUri(): string;
167
+ get authorizationEndpoint(): string;
168
+ get tokenEndpoint(): string;
169
+ get endSessionEndpoint(): string;
170
+ get deviceAuthorizationEndpoint(): string;
171
+ /** The relying party's client id the token endpoint expects. */
172
+ get clientId(): string;
173
+ /** The `client_secret_basic` secret the token endpoint expects, or `null` for "any". */
174
+ get clientSecret(): string | null;
175
+ /** The API audience a minted access token carries. */
176
+ get apiAudience(): string;
177
+ /** The `azp` a minted access token carries when the caller does not say. */
178
+ get defaultAuthorizedParty(): string;
179
+ /** The document this provider publishes, with the fields `omit` removed. */
180
+ discoveryDocument(): Record<string, unknown>;
181
+ /** FR-44's delegated scope this provider mints. */
182
+ get delegatedScope(): string;
183
+ /** FR-39/FR-40's exchange scope this provider mints. */
184
+ get exchangeScope(): string;
185
+ /** The current clock, in epoch milliseconds. */
186
+ now(): number;
187
+ /** The current clock, in whole seconds — the unit every JWT time claim uses. */
188
+ nowSeconds(): number;
189
+ /**
190
+ * Replace the signing key, the way a provider rotates one (FR-13, ACC-12-08).
191
+ *
192
+ * `keepPrevious` publishes the old key alongside the new one, which is what a
193
+ * rollover looks like while tokens signed with it are still in flight; the
194
+ * default is the harder case, where the old key is **gone** and a token signed
195
+ * with it must stop validating.
196
+ */
197
+ rotateKeys(options?: {
198
+ keepPrevious?: boolean;
199
+ }): Promise<void>;
200
+ /** The current key id, so a spec can name what it is signing with. */
201
+ /**
202
+ * Change the person the provider approves from now on — so one running fake
203
+ * can sign in a connected identity, a new one and one whose e-mail an account
204
+ * already uses, in turn. Additive test seam; the constructor option still sets
205
+ * the first person.
206
+ */
207
+ setUser(user: FakeOidcUser): void;
208
+ /**
209
+ * Extra claims every ID token minted from now on carries (for example the
210
+ * optional `urn:ever:` hint claims). Pass `{}` to stop adding any.
211
+ */
212
+ setIdTokenClaims(claims: Record<string, unknown>): void;
213
+ /** The provider session id (`sid`) the next ID tokens and logout tokens carry. */
214
+ setSessionId(sid: string): void;
215
+ /** The provider session id the next tokens carry. */
216
+ get currentSessionId(): string;
217
+ get currentKeyId(): string;
218
+ /**
219
+ * Sign an ID token for the configured person — what the token endpoint answers
220
+ * with, and what a spec can use to drive `exchangeAuthorizationCode` directly.
221
+ */
222
+ mintIdToken(overrides?: Record<string, unknown>): Promise<string>;
223
+ /**
224
+ * Sign an access token — the artifact FR-40's exchange and FR-45's delegated read
225
+ * are handed.
226
+ *
227
+ * The defaults are a **valid** token for the configured installation: `aud` is
228
+ * the API audience, `azp` is the first configured local client,
229
+ * `scope` is the exchange scope, `iat` is now and `exp` is now plus
230
+ * {@link FakeOidcProviderOptions.accessTokenLifetimeSeconds}. Every case in a
231
+ * spec is therefore "everything is valid, except this", and the literal numbers
232
+ * a case wants are passed in rather than derived from the plugin.
233
+ */
234
+ mintAccessToken(overrides?: {
235
+ readonly scopes?: readonly string[];
236
+ readonly authorizedParty?: string | null;
237
+ readonly audience?: string | readonly string[];
238
+ readonly subject?: string;
239
+ readonly issuedAt?: number;
240
+ readonly expiresAt?: number;
241
+ readonly jti?: string | null;
242
+ readonly claims?: Record<string, unknown>;
243
+ }): Promise<string>;
244
+ /**
245
+ * Sign a back-channel logout notice (FR-33).
246
+ *
247
+ * The default is a **valid** notice about the configured person's session:
248
+ * the event member, an `iat` of now, a fresh `jti`, `sid` and no `nonce`, `exp`
249
+ * or `sub`. `extraClaims` is how a spec adds the claims FR-33 forbids (or
250
+ * removes the ones it requires), so every case reads as one deliberate change.
251
+ */
252
+ mintLogoutToken(overrides?: {
253
+ readonly subject?: string | null;
254
+ readonly sid?: string | null;
255
+ readonly jti?: string | null;
256
+ readonly issuedAt?: number;
257
+ readonly expiresAt?: number | null;
258
+ readonly events?: Record<string, unknown> | null;
259
+ readonly extraClaims?: Record<string, unknown>;
260
+ }): Promise<string>;
261
+ /**
262
+ * Post a signed notice to a relying party's back-channel endpoint, the way the
263
+ * provider does (FR-33's "one public endpoint", form-encoded).
264
+ *
265
+ * Answers the status and the body text rather than throwing, so a spec can assert
266
+ * FR-34's "an invalid notice answers 400" — and FR-34's "200 with
267
+ * `Cache-Control: no-store`" — on a real HTTP exchange. A token is minted first
268
+ * when the caller does not pass one.
269
+ */
270
+ postBackchannelLogout(url: string, token?: string, overrides?: Parameters<FakeOidcProvider['mintLogoutToken']>[0]): Promise<{
271
+ readonly status: number;
272
+ readonly body: string;
273
+ readonly cacheControl: string | null;
274
+ }>;
275
+ /**
276
+ * Approve a pending device grant — what the person does in the browser at
277
+ * `verification_uri` (FR-39).
278
+ *
279
+ * With no argument every pending grant is approved, which is what a spec that has
280
+ * just asked for one wants; the `userCode` form is there for the case that reads
281
+ * the code off the wire.
282
+ */
283
+ approveDeviceAuthorization(userCode?: string): number;
284
+ /** Every device grant this provider has issued, approved or not. */
285
+ get deviceGrants(): readonly {
286
+ readonly userCode: string;
287
+ readonly deviceCode: string;
288
+ readonly approved: boolean;
289
+ }[];
290
+ /** The last authorization request's query, or `null` when none has been made. */
291
+ lastAuthorizationRequest(): Record<string, string> | null;
292
+ /** Stop listening. Safe to call twice, and safe to call after a failed start. */
293
+ stop(): Promise<void>;
294
+ private user;
295
+ /**
296
+ * Whether `clientId` is one of the PUBLIC local clients (CLI, node) — RFC 8628
297
+ * public clients authenticate with their `client_id` alone, no secret.
298
+ */
299
+ private isLocalClient;
300
+ private rotateKeysSync;
301
+ private currentKey;
302
+ private sign;
303
+ private handle;
304
+ /** §10.4's "authorize (auto-approve a configured user)", with S256 required. */
305
+ private handleAuthorize;
306
+ /** RFC 6749 §4.1.3 and RFC 8628 §3.4–3.5: two grants, both checked. */
307
+ private handleToken;
308
+ private handleAuthorizationCodeGrant;
309
+ private handleDeviceCodeGrant;
310
+ /** RFC 8628 §3.2's device authorization response. */
311
+ private handleDeviceAuthorization;
312
+ /** RP-Initiated Logout 1.0 §2: back to the relying party, with `state` preserved. */
313
+ private handleEndSession;
314
+ private tokensFor;
315
+ /** `client_secret_basic` (RFC 6749 §2.3.1), which is what plan §4.2 registers. */
316
+ private credentialsAccepted;
317
+ }
318
+ /** S256: `BASE64URL(SHA256(ASCII(code_verifier)))` (RFC 7636 §4.6). */
319
+ declare function s256(codeVerifier: string): string;
320
+
321
+ export { FAKE_BACKCHANNEL_LOGOUT_EVENT, FAKE_DEFAULT_LOCAL_CLIENT_ID, FAKE_DEFAULT_USER, type FakeDevicePoll, type FakeOidcCall, type FakeOidcOmittableField, FakeOidcProvider, type FakeOidcProviderOptions, type FakeOidcProviderStarted, type FakeOidcUser, s256 };
@@ -0,0 +1,321 @@
1
+ /**
2
+ * APW-12 T8 — a **fake OpenID Connect provider**, published only through this
3
+ * package's `./testing` subpath (plan §10.4).
4
+ *
5
+ * The API integration spec (`apps/api/src/auth/ever-id.flow.integration.spec.ts`,
6
+ * T20/T25) and the Playwright lanes both need a provider that answers the way Ever
7
+ * ID will: a discovery document, a key set, an authorization endpoint that
8
+ * approves a configured person, a token endpoint that **checks PKCE**, device
9
+ * authorization with FR-41's polling rules, an end-session endpoint, and helpers
10
+ * that mint the access and logout tokens the verifiers are given. One fake, in one
11
+ * place, because a second one would be a second set of provider behaviours to keep
12
+ * in step.
13
+ *
14
+ * ## Why the subpath, and why that is load-bearing
15
+ *
16
+ * Plan §10.4: "published only through a `./testing` subpath export, never from the
17
+ * main entry". The main entry (`src/index.ts`) is what the plugin loader imports in
18
+ * production, and a fake provider that reached it would be dead weight in every
19
+ * API process — and, worse, an HTTP server behind a class the platform already
20
+ * instantiates. `package.json`'s `exports["./testing"]` and `tsup`'s second entry
21
+ * are what make that separation real; `src/__tests__/fake-oidc-provider.spec.ts`
22
+ * asserts it from both the source side and (when a build exists) the built side.
23
+ *
24
+ * ## What it is not
25
+ *
26
+ * It is not a security reference. It signs with ES256 keys it generates
27
+ * (`node:crypto`, plan §10.4 — not `jose`, so a bug in the library under test
28
+ * cannot hide a bug in the fake), it keeps device grants and authorization codes
29
+ * in memory, and it makes no attempt to be a *correct* provider in the corners the
30
+ * epic does not read. Where it deliberately *is* strict — PKCE S256, single-use
31
+ * codes, `client_secret_basic`, FR-41's poll interval — the strictness is the
32
+ * point: those are the behaviours the relying party has to get right, and a fake
33
+ * that accepted anything would prove nothing about them.
34
+ *
35
+ * ## Time
36
+ *
37
+ * Every clock read goes through the injected `now` (epoch **milliseconds**,
38
+ * defaulting to `Date.now`), and every minted claim is stamped from it. A spec
39
+ * therefore drives token lifetimes, the device-poll interval and `iat`/`exp` with
40
+ * literal values and an injected clock rather than by sleeping — the rule this
41
+ * package learned the hard way in T6.
42
+ */
43
+ /**
44
+ * The `events` member of a back-channel logout notice — OpenID Connect
45
+ * Back-Channel Logout 1.0 §2.4's event identifier.
46
+ *
47
+ * Spelled here rather than imported from the plugin on purpose: this module stands
48
+ * in for an **external** provider, and a provider does not read the relying
49
+ * party's constants. `fake-oidc-provider.spec.ts` asserts that this literal and
50
+ * `OIDC_BACKCHANNEL_LOGOUT_EVENT` are the same string, so the two cannot drift
51
+ * without a red test.
52
+ */
53
+ declare const FAKE_BACKCHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
54
+ /** The public client an exchange token names by default. */
55
+ declare const FAKE_DEFAULT_LOCAL_CLIENT_ID = "ever-works-cli";
56
+ /** The person the provider approves when a spec configures nobody (§10.4). */
57
+ declare const FAKE_DEFAULT_USER: {
58
+ readonly subject: "ever-id-subject-1";
59
+ readonly email: "person@example.com";
60
+ readonly emailVerified: true;
61
+ readonly name: "A Person";
62
+ };
63
+ /** The document fields a spec may ask the fake to leave out (FR-3's rows, FR-36's `null`). */
64
+ type FakeOidcOmittableField = 'authorization_endpoint' | 'token_endpoint' | 'jwks_uri' | 'end_session_endpoint' | 'device_authorization_endpoint' | 'code_challenge_methods_supported' | 'backchannel_logout_supported';
65
+ /** The person this provider signs in. */
66
+ interface FakeOidcUser {
67
+ readonly subject: string;
68
+ readonly email: string;
69
+ readonly emailVerified?: boolean;
70
+ readonly name?: string;
71
+ }
72
+ /** Construction options for {@link FakeOidcProvider.start}. */
73
+ interface FakeOidcProviderOptions {
74
+ /** The person auto-approved at `/authorize` and at device authorization. */
75
+ readonly user?: FakeOidcUser;
76
+ /** The relying party's client id. Default `ever-works-web`. */
77
+ readonly clientId?: string;
78
+ /**
79
+ * The `client_secret_basic` secret the token endpoint requires, or `null` to
80
+ * accept any (a spec that is not exercising client authentication).
81
+ */
82
+ readonly clientSecret?: string | null;
83
+ /** The audience a minted access token carries. Default `ever-works`. */
84
+ readonly apiAudience?: string;
85
+ /** The public clients an exchange token may name in `azp`. */
86
+ readonly localClients?: readonly {
87
+ readonly kind: 'cli' | 'node';
88
+ readonly clientId: string;
89
+ }[];
90
+ /** FR-44's scope a delegated access token carries. Default `apps:read`. */
91
+ readonly delegatedScope?: string;
92
+ /** FR-39/FR-40's scope an exchange access token carries. Default `ever-works:session`. */
93
+ readonly exchangeScope?: string;
94
+ /** How long a minted access token lives when the caller does not say. Default 300. */
95
+ readonly accessTokenLifetimeSeconds?: number;
96
+ /** FR-41's poll interval the device endpoint returns, in seconds. Default 5. */
97
+ readonly devicePollIntervalSeconds?: number;
98
+ /** How long a device code lives. Default 900 (FR-41). */
99
+ readonly deviceCodeLifetimeSeconds?: number;
100
+ /** How long an authorization code lives. Default 60. */
101
+ readonly authorizationCodeLifetimeSeconds?: number;
102
+ /** Document fields to leave out, so a spec can drive the rows and the `null` answers. */
103
+ readonly omit?: readonly FakeOidcOmittableField[];
104
+ /** The clock, in epoch milliseconds. Defaults to `Date.now`. */
105
+ readonly now?: () => number;
106
+ }
107
+ /** One request the provider received, so a spec can assert on what actually went out. */
108
+ interface FakeOidcCall {
109
+ readonly method: string;
110
+ readonly path: string;
111
+ /** The query string, as a plain object (a repeated key keeps its first value). */
112
+ readonly query: Record<string, string>;
113
+ /** The `application/x-www-form-urlencoded` body, as a plain object. */
114
+ readonly form: Record<string, string>;
115
+ /** The request headers, lower-cased. */
116
+ readonly headers: Record<string, string>;
117
+ }
118
+ /** What a device-code poll answered. */
119
+ interface FakeDevicePoll {
120
+ readonly status: number;
121
+ readonly body: Record<string, unknown>;
122
+ }
123
+ /** What {@link FakeOidcProvider.start} resolves to. */
124
+ type FakeOidcProviderStarted = FakeOidcProvider;
125
+ /**
126
+ * A local OpenID Connect provider, for specs.
127
+ *
128
+ * `start()` binds an ephemeral port on the loopback interface and answers on
129
+ * `http://localhost:<port>` — the one non-TLS issuer `settings.schema.ts` accepts
130
+ * and the one the plugin's configuration tells `openid-client` about
131
+ * (`isInsecureIssuer`). The issuer therefore depends on the port and is only known
132
+ * after the server is listening, which is why construction goes through `start()`.
133
+ */
134
+ declare class FakeOidcProvider {
135
+ /** Every request, in order — method, path, query, form and headers. */
136
+ readonly calls: FakeOidcCall[];
137
+ private readonly options;
138
+ private readonly keys;
139
+ private readonly codes;
140
+ private readonly devices;
141
+ private server;
142
+ private issuerUrl;
143
+ private keySequence;
144
+ /** The person approved from now on, when a spec changed it after start (see {@link setUser}). */
145
+ private userOverride;
146
+ /** Extra claims every ID token minted from now on carries (see {@link setIdTokenClaims}). */
147
+ private extraIdTokenClaims;
148
+ /** The provider session id (`sid`) the next tokens carry (see {@link setSessionId}). */
149
+ private sessionId;
150
+ private constructor();
151
+ /** Bind a port and answer the discovery document a relying party will read. */
152
+ static start(options?: FakeOidcProviderOptions): Promise<FakeOidcProvider>;
153
+ /**
154
+ * Bind an ephemeral loopback port and answer `/…/.well-known/openid-configuration`.
155
+ *
156
+ * Kept additive and public so a caller that wants a provider on a **fixed** port
157
+ * (a Playwright lane that must tell the API where to find it before the process
158
+ * starts) can build one; `start()` is the ordinary entry point.
159
+ */
160
+ listen(port?: number): Promise<void>;
161
+ /** The issuer the discovery document advertises, and the settings' `issuerUrl`. */
162
+ get issuer(): string;
163
+ /** The bound port. */
164
+ get port(): number;
165
+ get discoveryUrl(): string;
166
+ get jwksUri(): string;
167
+ get authorizationEndpoint(): string;
168
+ get tokenEndpoint(): string;
169
+ get endSessionEndpoint(): string;
170
+ get deviceAuthorizationEndpoint(): string;
171
+ /** The relying party's client id the token endpoint expects. */
172
+ get clientId(): string;
173
+ /** The `client_secret_basic` secret the token endpoint expects, or `null` for "any". */
174
+ get clientSecret(): string | null;
175
+ /** The API audience a minted access token carries. */
176
+ get apiAudience(): string;
177
+ /** The `azp` a minted access token carries when the caller does not say. */
178
+ get defaultAuthorizedParty(): string;
179
+ /** The document this provider publishes, with the fields `omit` removed. */
180
+ discoveryDocument(): Record<string, unknown>;
181
+ /** FR-44's delegated scope this provider mints. */
182
+ get delegatedScope(): string;
183
+ /** FR-39/FR-40's exchange scope this provider mints. */
184
+ get exchangeScope(): string;
185
+ /** The current clock, in epoch milliseconds. */
186
+ now(): number;
187
+ /** The current clock, in whole seconds — the unit every JWT time claim uses. */
188
+ nowSeconds(): number;
189
+ /**
190
+ * Replace the signing key, the way a provider rotates one (FR-13, ACC-12-08).
191
+ *
192
+ * `keepPrevious` publishes the old key alongside the new one, which is what a
193
+ * rollover looks like while tokens signed with it are still in flight; the
194
+ * default is the harder case, where the old key is **gone** and a token signed
195
+ * with it must stop validating.
196
+ */
197
+ rotateKeys(options?: {
198
+ keepPrevious?: boolean;
199
+ }): Promise<void>;
200
+ /** The current key id, so a spec can name what it is signing with. */
201
+ /**
202
+ * Change the person the provider approves from now on — so one running fake
203
+ * can sign in a connected identity, a new one and one whose e-mail an account
204
+ * already uses, in turn. Additive test seam; the constructor option still sets
205
+ * the first person.
206
+ */
207
+ setUser(user: FakeOidcUser): void;
208
+ /**
209
+ * Extra claims every ID token minted from now on carries (for example the
210
+ * optional `urn:ever:` hint claims). Pass `{}` to stop adding any.
211
+ */
212
+ setIdTokenClaims(claims: Record<string, unknown>): void;
213
+ /** The provider session id (`sid`) the next ID tokens and logout tokens carry. */
214
+ setSessionId(sid: string): void;
215
+ /** The provider session id the next tokens carry. */
216
+ get currentSessionId(): string;
217
+ get currentKeyId(): string;
218
+ /**
219
+ * Sign an ID token for the configured person — what the token endpoint answers
220
+ * with, and what a spec can use to drive `exchangeAuthorizationCode` directly.
221
+ */
222
+ mintIdToken(overrides?: Record<string, unknown>): Promise<string>;
223
+ /**
224
+ * Sign an access token — the artifact FR-40's exchange and FR-45's delegated read
225
+ * are handed.
226
+ *
227
+ * The defaults are a **valid** token for the configured installation: `aud` is
228
+ * the API audience, `azp` is the first configured local client,
229
+ * `scope` is the exchange scope, `iat` is now and `exp` is now plus
230
+ * {@link FakeOidcProviderOptions.accessTokenLifetimeSeconds}. Every case in a
231
+ * spec is therefore "everything is valid, except this", and the literal numbers
232
+ * a case wants are passed in rather than derived from the plugin.
233
+ */
234
+ mintAccessToken(overrides?: {
235
+ readonly scopes?: readonly string[];
236
+ readonly authorizedParty?: string | null;
237
+ readonly audience?: string | readonly string[];
238
+ readonly subject?: string;
239
+ readonly issuedAt?: number;
240
+ readonly expiresAt?: number;
241
+ readonly jti?: string | null;
242
+ readonly claims?: Record<string, unknown>;
243
+ }): Promise<string>;
244
+ /**
245
+ * Sign a back-channel logout notice (FR-33).
246
+ *
247
+ * The default is a **valid** notice about the configured person's session:
248
+ * the event member, an `iat` of now, a fresh `jti`, `sid` and no `nonce`, `exp`
249
+ * or `sub`. `extraClaims` is how a spec adds the claims FR-33 forbids (or
250
+ * removes the ones it requires), so every case reads as one deliberate change.
251
+ */
252
+ mintLogoutToken(overrides?: {
253
+ readonly subject?: string | null;
254
+ readonly sid?: string | null;
255
+ readonly jti?: string | null;
256
+ readonly issuedAt?: number;
257
+ readonly expiresAt?: number | null;
258
+ readonly events?: Record<string, unknown> | null;
259
+ readonly extraClaims?: Record<string, unknown>;
260
+ }): Promise<string>;
261
+ /**
262
+ * Post a signed notice to a relying party's back-channel endpoint, the way the
263
+ * provider does (FR-33's "one public endpoint", form-encoded).
264
+ *
265
+ * Answers the status and the body text rather than throwing, so a spec can assert
266
+ * FR-34's "an invalid notice answers 400" — and FR-34's "200 with
267
+ * `Cache-Control: no-store`" — on a real HTTP exchange. A token is minted first
268
+ * when the caller does not pass one.
269
+ */
270
+ postBackchannelLogout(url: string, token?: string, overrides?: Parameters<FakeOidcProvider['mintLogoutToken']>[0]): Promise<{
271
+ readonly status: number;
272
+ readonly body: string;
273
+ readonly cacheControl: string | null;
274
+ }>;
275
+ /**
276
+ * Approve a pending device grant — what the person does in the browser at
277
+ * `verification_uri` (FR-39).
278
+ *
279
+ * With no argument every pending grant is approved, which is what a spec that has
280
+ * just asked for one wants; the `userCode` form is there for the case that reads
281
+ * the code off the wire.
282
+ */
283
+ approveDeviceAuthorization(userCode?: string): number;
284
+ /** Every device grant this provider has issued, approved or not. */
285
+ get deviceGrants(): readonly {
286
+ readonly userCode: string;
287
+ readonly deviceCode: string;
288
+ readonly approved: boolean;
289
+ }[];
290
+ /** The last authorization request's query, or `null` when none has been made. */
291
+ lastAuthorizationRequest(): Record<string, string> | null;
292
+ /** Stop listening. Safe to call twice, and safe to call after a failed start. */
293
+ stop(): Promise<void>;
294
+ private user;
295
+ /**
296
+ * Whether `clientId` is one of the PUBLIC local clients (CLI, node) — RFC 8628
297
+ * public clients authenticate with their `client_id` alone, no secret.
298
+ */
299
+ private isLocalClient;
300
+ private rotateKeysSync;
301
+ private currentKey;
302
+ private sign;
303
+ private handle;
304
+ /** §10.4's "authorize (auto-approve a configured user)", with S256 required. */
305
+ private handleAuthorize;
306
+ /** RFC 6749 §4.1.3 and RFC 8628 §3.4–3.5: two grants, both checked. */
307
+ private handleToken;
308
+ private handleAuthorizationCodeGrant;
309
+ private handleDeviceCodeGrant;
310
+ /** RFC 8628 §3.2's device authorization response. */
311
+ private handleDeviceAuthorization;
312
+ /** RP-Initiated Logout 1.0 §2: back to the relying party, with `state` preserved. */
313
+ private handleEndSession;
314
+ private tokensFor;
315
+ /** `client_secret_basic` (RFC 6749 §2.3.1), which is what plan §4.2 registers. */
316
+ private credentialsAccepted;
317
+ }
318
+ /** S256: `BASE64URL(SHA256(ASCII(code_verifier)))` (RFC 7636 §4.6). */
319
+ declare function s256(codeVerifier: string): string;
320
+
321
+ export { FAKE_BACKCHANNEL_LOGOUT_EVENT, FAKE_DEFAULT_LOCAL_CLIENT_ID, FAKE_DEFAULT_USER, type FakeDevicePoll, type FakeOidcCall, type FakeOidcOmittableField, FakeOidcProvider, type FakeOidcProviderOptions, type FakeOidcProviderStarted, type FakeOidcUser, s256 };