@qauth-labs/plugin-sdk 0.0.0 → 0.2.0-rc.1

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,424 @@
1
+ import type { z } from 'zod';
2
+ import type { AUTH_METHOD_CONTRACT, AUTH_METHOD_ICON_MEDIA_TYPES, AuthMethodStepUrn, UpstreamFailureReason } from './contract.js';
3
+ import type { CredentialProvider } from './credential-provider.js';
4
+ /**
5
+ * The AuthMethod contract (#599): a sign-in method that plugs into QAuth's
6
+ * engine, such as an upstream OpenID Connect provider.
7
+ *
8
+ * EXPERIMENTAL. This is the plugin API of ADR-018 §4. It is public and
9
+ * documented, but outside the 1.0 stability promise: it may change in a minor
10
+ * release, and `contract` names the version a method is written against.
11
+ *
12
+ * ## What it adds to `CredentialProvider`
13
+ *
14
+ * `CredentialProvider` stays the verification half (ADR-003, as amended):
15
+ * `type`, `verify()` and `extractAttributes()` keep their meaning. An
16
+ * AuthMethod adds what a method needs beyond verification: a stable `id`, the
17
+ * contract version, default display data, a config schema, the Interaction API
18
+ * steps it contributes, the routes it mounts, and `start()`.
19
+ *
20
+ * ## The upstream step
21
+ *
22
+ * 1. The engine offers the upstream step and the person picks the method.
23
+ * 2. The engine calls {@link AuthMethod.start}. The method returns where the
24
+ * browser goes, the transaction it needs back (`state`, `nonce`, a PKCE
25
+ * verifier) and the binding: the random value the authorization request
26
+ * carries to the provider and the callback carries back (`state`). The
27
+ * engine keeps the transaction and the binding server-side, bound to the
28
+ * interaction and its flow cookie; the transaction never reaches the
29
+ * browser.
30
+ * 3. The provider sends the browser back to one of the method's callback
31
+ * routes, under `/upstream/<type>/`. The host compares the binding the
32
+ * callback carries (in the route's `bindingParameter`, `state` by default)
33
+ * with the pending transaction's, in constant time, before it takes the
34
+ * transaction: with no pending transaction (no flow cookie, another
35
+ * interaction, a replay) or a binding that is missing, repeated or
36
+ * another's, it answers `transaction_mismatch` itself, leaves the
37
+ * transaction pending, and the method never runs. Otherwise it takes the
38
+ * transaction (single use) and the route's handler gets it, checks the
39
+ * provider's answer and returns an outcome.
40
+ * 4. The engine turns a `result` outcome into a `VerifiedIdentity` by calling
41
+ * `verify(result)`, checks it, and takes the attributes from
42
+ * `extractAttributes()`. Every attribute's `source` must equal `type`.
43
+ * The identity's `rawClaims.iss` names the issuer that assigned its
44
+ * `externalSub`: a subject is unique only within its issuer (OpenID
45
+ * Connect Core 1.0 §5.7). The sign-in flow stores the issuer with the
46
+ * credential, refuses a later sign-in under another one, and refuses an
47
+ * identity that names none.
48
+ *
49
+ * `verify()` of an AuthMethod receives only what its own callback returned;
50
+ * the engine calls it, once per result, and nothing else does. It still
51
+ * rejects anything that is not such a result, a copy of a real result and a
52
+ * result already used among them: the contract test kit checks that it does.
53
+ *
54
+ * ## What a method is handed (ADR-019 Decision 12)
55
+ *
56
+ * Plugin code is trusted code that runs in-process. The contract hands it only
57
+ * what it needs, through {@link AuthMethodContext}: its config, the egress
58
+ * client, the signer role and a logger. No database handle, no Redis handle,
59
+ * no private key, no Fastify instance, no cookie and no request header.
60
+ *
61
+ * @typeParam TConfig - the parsed config, the output of `configSchema`
62
+ * @typeParam TTransaction - what `start()` asks the engine to keep
63
+ * @experimental
64
+ */
65
+ export interface AuthMethod<TConfig = unknown, TTransaction = unknown> extends CredentialProvider {
66
+ /**
67
+ * The contract version the method is written against:
68
+ * `qauth-auth-method/1-rc.0`. Its presence is what makes a registered
69
+ * provider an AuthMethod.
70
+ */
71
+ readonly contract: typeof AUTH_METHOD_CONTRACT;
72
+ /**
73
+ * The stable name of the method's implementation, such as `oidc` or
74
+ * `corp-sso`. Instances of one implementation (one per configured provider)
75
+ * share it; `type` tells them apart.
76
+ */
77
+ readonly id: string;
78
+ /** What the sign-in page shows for the method until an admin changes it. */
79
+ readonly display: AuthMethodDisplay;
80
+ /**
81
+ * The schema of the method's config. The host parses the stored config with
82
+ * it before it builds a context, so `context.config` is its output. A
83
+ * secret member, such as a client secret, is marked with
84
+ * `.meta({ writeOnly: true })`, so an admin API can keep it write-only.
85
+ */
86
+ readonly configSchema: z.ZodType<TConfig>;
87
+ /** The Interaction API steps the method contributes, by versioned URN. */
88
+ readonly steps: readonly AuthMethodStepUrn[];
89
+ /** The routes the method mounts, below `/upstream/<type>`. */
90
+ readonly routes: readonly AuthMethodRoute<TConfig, TTransaction>[];
91
+ /**
92
+ * Start an upstream transaction: where the browser goes, and what the
93
+ * method needs back at its callback. Rejects when it cannot start, such as
94
+ * when the provider's discovery document cannot be read.
95
+ */
96
+ start(request: UpstreamStartRequest, context: AuthMethodContext<TConfig>): Promise<UpstreamStart<TTransaction>>;
97
+ }
98
+ /**
99
+ * A method's default display data. An admin may override both per realm.
100
+ *
101
+ * @experimental
102
+ */
103
+ export interface AuthMethodDisplay {
104
+ /**
105
+ * The provider's name, as the button says it: `Example Corp`. At most 64
106
+ * characters, under the Interaction API's display rule.
107
+ */
108
+ readonly name: string;
109
+ /** An icon for the button, or none. */
110
+ readonly icon?: AuthMethodIcon;
111
+ }
112
+ /**
113
+ * A default icon: a PNG image of at most 16 KiB. The host serves it from the
114
+ * realm origin.
115
+ *
116
+ * @experimental
117
+ */
118
+ export interface AuthMethodIcon {
119
+ readonly mediaType: (typeof AUTH_METHOD_ICON_MEDIA_TYPES)[number];
120
+ readonly bytes: Uint8Array;
121
+ }
122
+ /**
123
+ * What a method is handed for one realm (ADR-019 Decision 12): its config,
124
+ * the egress client, the signer role and a logger. Nothing else: no database
125
+ * or cache handle, no key material.
126
+ *
127
+ * @experimental
128
+ */
129
+ export interface AuthMethodContext<TConfig = unknown> {
130
+ /** The method's config for this realm, parsed by its `configSchema`. Frozen. */
131
+ readonly config: TConfig;
132
+ /** Every outbound call goes through here (ADR-019 Decision 8). */
133
+ readonly egress: AuthMethodEgress;
134
+ /** Signatures without keys (ADR-019 Decision 9). */
135
+ readonly signer: AuthMethodSigner;
136
+ /** A logger. Never log a secret, a code or a token. */
137
+ readonly log: AuthMethodLogger;
138
+ /**
139
+ * The absolute URL of one of the method's own routes on this realm's host,
140
+ * such as the `redirect_uri` of an authorization request:
141
+ * `routeUrl('/callback')` is `https://<realm host>/upstream/<type>/callback`.
142
+ */
143
+ routeUrl(path: string): string;
144
+ }
145
+ /**
146
+ * The egress client as a method sees it. `createAuthMethodContext` builds it
147
+ * over the host's client: it passes on only the members below and adds the
148
+ * realm itself, so an allowlist entry of another realm never applies and a
149
+ * method cannot lift the client's caps.
150
+ *
151
+ * @experimental
152
+ */
153
+ export interface AuthMethodEgress {
154
+ /**
155
+ * Send one request. Resolves with the final response, whatever its status;
156
+ * rejects when a check refuses the call (a private address, a redirect to
157
+ * one, a cap) or the call fails.
158
+ */
159
+ request(request: AuthMethodEgressRequest): Promise<AuthMethodEgressResponse>;
160
+ }
161
+ /**
162
+ * One outbound call of a method.
163
+ *
164
+ * @experimental
165
+ */
166
+ export interface AuthMethodEgressRequest {
167
+ readonly method: 'GET' | 'POST';
168
+ /** An absolute `https` URL without credentials. */
169
+ readonly url: string;
170
+ readonly headers?: Readonly<Record<string, string>>;
171
+ /** Not allowed with `GET`. */
172
+ readonly body?: string | Uint8Array;
173
+ /**
174
+ * What the call is for, a fixed label: `upstream-discovery`,
175
+ * `upstream-token`. Lower-case letters, digits and `_ . : -`, starting with
176
+ * a letter, at most 64 characters.
177
+ */
178
+ readonly purpose: string;
179
+ /**
180
+ * Who set the destination: `operator` (the method's own code, installed
181
+ * with the deployment), `admin` (the realm's config of the method) or
182
+ * `remote_document` (a document the method fetched, such as a discovery
183
+ * document). A method never sends to a destination a client or an end user
184
+ * chose.
185
+ */
186
+ readonly source: 'operator' | 'admin' | 'remote_document';
187
+ /**
188
+ * What a redirect answer does: `follow` (the default) follows it, each
189
+ * target checked as the first URL was, within the host client's redirect
190
+ * cap; `error` follows none, and a redirect answer rejects the call before
191
+ * anything is sent to its target. A call that carries a credential in its
192
+ * body (a client secret, an assertion, a code) uses `error`: a `307` or
193
+ * `308` would otherwise send that body on to wherever the redirect points.
194
+ * It can only tighten the host client's caps, never lift them.
195
+ */
196
+ readonly redirect?: 'follow' | 'error';
197
+ }
198
+ /**
199
+ * The final response of a call, after the redirects it followed.
200
+ *
201
+ * @experimental
202
+ */
203
+ export interface AuthMethodEgressResponse {
204
+ readonly status: number;
205
+ /** Lower-case names. */
206
+ readonly headers: Readonly<Record<string, string>>;
207
+ readonly body: Uint8Array;
208
+ /** The URL that answered. */
209
+ readonly url: string;
210
+ }
211
+ /**
212
+ * The signer role as a method sees it: signatures and public keys, never a
213
+ * key. `createAuthMethodContext` binds the realm, the provider and the caller
214
+ * role, and passes on only the members below, so a
215
+ * method cannot sign for another provider or another realm.
216
+ *
217
+ * @experimental
218
+ */
219
+ export interface AuthMethodSigner {
220
+ /**
221
+ * Sign a client assertion for `private_key_jwt` toward this method's
222
+ * provider (RFC 7523): `iss` and `sub` are the client id the realm's config
223
+ * names, `aud` is `audience`.
224
+ */
225
+ signClientAssertion(request: AuthMethodClientAssertionRequest): Promise<string>;
226
+ /** The public keys of those assertions, for the provider to register or fetch. */
227
+ getUpstreamJwks(): Promise<{
228
+ readonly keys: readonly Readonly<Record<string, unknown>>[];
229
+ }>;
230
+ }
231
+ /**
232
+ * Input of {@link AuthMethodSigner.signClientAssertion}.
233
+ *
234
+ * @experimental
235
+ */
236
+ export interface AuthMethodClientAssertionRequest {
237
+ /** The provider's issuer identifier, the assertion's sole `aud`. */
238
+ readonly audience: string;
239
+ /** The other claims, `exp` and `jti` among them, without `iss`, `sub` or `aud`. */
240
+ readonly claims: Readonly<Record<string, unknown>>;
241
+ /** The algorithm of the key registered with the provider. */
242
+ readonly algorithm?: 'RS256' | 'ES256' | 'EdDSA';
243
+ }
244
+ /**
245
+ * The part of a pino-style logger a method uses.
246
+ *
247
+ * @experimental
248
+ */
249
+ export interface AuthMethodLogger {
250
+ debug(fields: Record<string, unknown>, message: string): void;
251
+ info(fields: Record<string, unknown>, message: string): void;
252
+ warn(fields: Record<string, unknown>, message: string): void;
253
+ error(fields: Record<string, unknown>, message: string): void;
254
+ }
255
+ /**
256
+ * What the engine tells a method when it starts.
257
+ *
258
+ * @experimental
259
+ */
260
+ export interface UpstreamStartRequest {
261
+ /**
262
+ * The person's preferred languages, BCP 47 tags, most preferred first, for
263
+ * the provider's own pages (`ui_locales`). Empty when none is known.
264
+ */
265
+ readonly uiLocales: readonly string[];
266
+ }
267
+ /**
268
+ * What a method answers when it starts.
269
+ *
270
+ * @experimental
271
+ */
272
+ export interface UpstreamStart<TTransaction = unknown> {
273
+ /**
274
+ * Where the browser goes: the provider's authorization endpoint with the
275
+ * request's parameters. An absolute `https` URL, no credentials, no
276
+ * fragment, at most 8192 characters.
277
+ */
278
+ readonly redirectTo: string;
279
+ /**
280
+ * What the method needs back at its callback: `state`, `nonce`, a PKCE
281
+ * verifier. An object of JSON data (objects, arrays, strings, finite
282
+ * numbers, booleans, `null`), at most 4096 bytes as JSON. The engine keeps it server-side and
283
+ * hands it, once, to the callback the same browser reaches.
284
+ */
285
+ readonly transaction: TTransaction;
286
+ /**
287
+ * The value that binds the callback to this start: fresh and random for
288
+ * each start, sent to the provider in the authorization request (as
289
+ * `state`, usually) so the provider sends it back. 22 to 512 base64url
290
+ * characters (`AUTH_METHOD_BINDING_PATTERN`): 22 is the length of 128
291
+ * random bits. The host compares it with what the callback carries before
292
+ * it hands over the transaction.
293
+ */
294
+ readonly binding: string;
295
+ }
296
+ /**
297
+ * What the host keeps for a started upstream step until its callback: the
298
+ * `transaction` and `binding` that `startUpstreamStep` answered.
299
+ *
300
+ * @experimental
301
+ */
302
+ export interface PendingUpstreamTransaction<TTransaction = unknown> {
303
+ readonly binding: string;
304
+ readonly transaction: TTransaction;
305
+ }
306
+ /**
307
+ * A route a method mounts below `/upstream/<type>`, on the realm host.
308
+ *
309
+ * @experimental
310
+ */
311
+ export type AuthMethodRoute<TConfig = unknown, TTransaction = unknown> = AuthMethodCallbackRoute<TConfig, TTransaction> | AuthMethodBounceRoute;
312
+ /**
313
+ * Members every route has.
314
+ *
315
+ * @experimental
316
+ */
317
+ export interface AuthMethodRouteBase {
318
+ readonly method: 'GET' | 'POST';
319
+ /**
320
+ * The path below the method's prefix: `/callback`. One to four segments of
321
+ * lower-case letters, digits, `_` and `-`.
322
+ */
323
+ readonly path: string;
324
+ }
325
+ /**
326
+ * The route the provider sends the browser back to. Its handler checks the
327
+ * provider's answer against the pending transaction and hands an outcome to
328
+ * the engine.
329
+ *
330
+ * @experimental
331
+ */
332
+ export interface AuthMethodCallbackRoute<TConfig = unknown, TTransaction = unknown> extends AuthMethodRouteBase {
333
+ readonly kind: 'callback';
334
+ /**
335
+ * The parameter the provider carries the binding back in: a query
336
+ * parameter of a `GET` route, a form member of a `POST` one. Default
337
+ * `state`. The host reads it, once and unrepeated, and compares it with
338
+ * the pending transaction's binding before the handler runs.
339
+ */
340
+ readonly bindingParameter?: string;
341
+ handle(request: AuthMethodCallbackRequest<TTransaction>, context: AuthMethodContext<TConfig>): Promise<UpstreamCallbackOutcome>;
342
+ }
343
+ /**
344
+ * A route that only sends the browser on to one of the method's own `GET`
345
+ * routes, on the same origin: a provider's `form_post` arrives cross-site,
346
+ * without the `SameSite=Lax` flow cookie, and the bounce makes the browser
347
+ * bring it. It is handed no transaction and no context.
348
+ *
349
+ * @experimental
350
+ */
351
+ export interface AuthMethodBounceRoute extends AuthMethodRouteBase {
352
+ readonly kind: 'bounce';
353
+ /**
354
+ * Name where the browser goes. No context: anyone can send a cross-site
355
+ * POST here, so a bounce holds no egress client, no signer and no config.
356
+ */
357
+ handle(request: AuthMethodRouteRequest): Promise<AuthMethodBounce>;
358
+ }
359
+ /**
360
+ * Query and form parameters, by name. A repeated parameter is a list.
361
+ *
362
+ * @experimental
363
+ */
364
+ export type AuthMethodParameters = Readonly<Record<string, string | readonly string[]>>;
365
+ /**
366
+ * A request to one of a method's routes, as the method sees it: its
367
+ * parameters, and no header and no cookie.
368
+ *
369
+ * @experimental
370
+ */
371
+ export interface AuthMethodRouteRequest {
372
+ readonly method: 'GET' | 'POST';
373
+ readonly query: AuthMethodParameters;
374
+ /** The form members of a `POST`; empty for a `GET`. */
375
+ readonly form: AuthMethodParameters;
376
+ }
377
+ /**
378
+ * A request to a callback route, with the transaction the engine kept.
379
+ *
380
+ * @experimental
381
+ */
382
+ export interface AuthMethodCallbackRequest<TTransaction = unknown> extends AuthMethodRouteRequest {
383
+ /**
384
+ * The transaction `start()` returned for this method, which the request's
385
+ * flow cookie names, taken: a second request never sees it again. Always
386
+ * present, and always the one whose binding the request carries: without a
387
+ * pending transaction, or with a binding that does not match, the host
388
+ * answers `transaction_mismatch` and never calls the handler. A handler may
389
+ * still compare `state` with the transaction itself; it costs nothing.
390
+ */
391
+ readonly transaction: TTransaction;
392
+ }
393
+ /**
394
+ * Where a bounce route sends the browser: one of the method's own `GET`
395
+ * routes, by its path, with the query to carry.
396
+ *
397
+ * @experimental
398
+ */
399
+ export interface AuthMethodBounce {
400
+ /** The path of one of the method's own `GET` routes: `/callback`. */
401
+ readonly to: string;
402
+ readonly query: Readonly<Record<string, string>>;
403
+ }
404
+ /**
405
+ * What a callback hands the engine:
406
+ *
407
+ * - `result`: the provider's answer passed every check. The engine passes
408
+ * `result` to `verify()`.
409
+ * - `declined`: the person cancelled or declined at the provider
410
+ * (`access_denied`). Nothing is sent to the client: the person has not
411
+ * refused the client's request, and may pick another method.
412
+ * - `failed`: anything else, with its reason.
413
+ *
414
+ * @experimental
415
+ */
416
+ export type UpstreamCallbackOutcome<TResult = unknown> = {
417
+ readonly kind: 'result';
418
+ readonly result: TResult;
419
+ } | {
420
+ readonly kind: 'declined';
421
+ } | {
422
+ readonly kind: 'failed';
423
+ readonly reason: UpstreamFailureReason;
424
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,163 @@
1
+ import type { AuthMethod, AuthMethodContext, AuthMethodEgressResponse, AuthMethodLogger } from './auth-method.js';
2
+ /**
3
+ * The egress client as the host has it: the shape of `EgressClient.request`
4
+ * in `@qauth-labs/server-egress`, which takes the realm a destination belongs
5
+ * to and per-call caps. The context's adapter sets the realm itself, and
6
+ * passes one cap only: `maxRedirects: 0`, when the method asks to follow no
7
+ * redirect. No other cap, so a method can tighten the client's redirect cap
8
+ * and lift nothing.
9
+ *
10
+ * @experimental
11
+ */
12
+ export interface AuthMethodHostEgress {
13
+ request(request: {
14
+ method: 'GET' | 'POST';
15
+ url: string;
16
+ headers?: Record<string, string>;
17
+ body?: string | Uint8Array;
18
+ purpose: string;
19
+ source: 'operator' | 'admin' | 'remote_document';
20
+ realm?: string;
21
+ limits?: {
22
+ maxRedirects: 0;
23
+ };
24
+ }): Promise<AuthMethodEgressResponse>;
25
+ }
26
+ /**
27
+ * The signer as the host has it: the shape of the two `Signer` operations of
28
+ * `@qauth-labs/server-keys` a method needs, each of which names its caller
29
+ * role, the realm and the provider. The context's adapter fixes all three.
30
+ *
31
+ * @experimental
32
+ */
33
+ export interface AuthMethodHostSigner {
34
+ signClientAssertion(caller: string, request: {
35
+ readonly realmId: string;
36
+ readonly providerId: string;
37
+ readonly audience: string;
38
+ readonly claims: Readonly<Record<string, unknown>>;
39
+ readonly algorithm?: 'RS256' | 'ES256' | 'EdDSA';
40
+ }): Promise<string>;
41
+ getUpstreamJwks(caller: string, realmId: string, providerId: string): Promise<{
42
+ readonly keys: readonly unknown[];
43
+ }>;
44
+ }
45
+ /**
46
+ * What a host passes to build a method's context for one realm.
47
+ *
48
+ * @experimental
49
+ */
50
+ export interface AuthMethodContextParts {
51
+ /** The realm's stored config of the method, before parsing. */
52
+ readonly config: unknown;
53
+ /**
54
+ * The realm host's origin, as the browser reaches it:
55
+ * `https://auth.example.com`. Route URLs are built on it.
56
+ */
57
+ readonly origin: string;
58
+ /** The realm's name: the egress allowlist entries that name it apply. */
59
+ readonly realmName: string;
60
+ /** The realm's id, for the signer. */
61
+ readonly realmId: string;
62
+ /** The upstream provider, the scope of its client-assertion keys. */
63
+ readonly providerId: string;
64
+ /** The signer caller role the method's calls are made for, such as `api`. */
65
+ readonly signerCaller: string;
66
+ readonly egress: AuthMethodHostEgress;
67
+ readonly signer: AuthMethodHostSigner;
68
+ /** A logger, such as a pino child logger; only its four levels are used. */
69
+ readonly log: AuthMethodLogger;
70
+ /**
71
+ * The method's secret members as the host decrypted them into `config`, by
72
+ * path (`clientSecret`, `mtls.keyPassword`). The context keeps them out of
73
+ * two places: its egress labels, so a secret that parses as a URL opens no
74
+ * allowlisted destination; and its log lines, where a field named as a
75
+ * secret path's last segment, and every occurrence of a secret's value in a
76
+ * string or the message, is redacted.
77
+ */
78
+ readonly secrets?: ReadonlyMap<string, string>;
79
+ }
80
+ /**
81
+ * The longest a client assertion signed through the context stays valid, in
82
+ * seconds: its `exp` is lowered to at most this far past the moment it is
83
+ * signed. Five minutes covers a token request and clock skew; an assertion
84
+ * that outlives it is a replayable credential at the provider.
85
+ *
86
+ * @experimental
87
+ */
88
+ export declare const MAX_AUTH_METHOD_CLIENT_ASSERTION_LIFETIME_SECONDS = 300;
89
+ /**
90
+ * The fields a method's log line may not carry, at any depth, matched without
91
+ * regard to case, `_` or `-` (`clientSecret` matches `client_secret`): the
92
+ * value is replaced with `[Redacted]`. Codes, verifiers, client secrets and
93
+ * assertions, tokens, nonces and passwords: what an upstream sign-in handles
94
+ * and a log must never hold. The last segment of each of the method's own
95
+ * secret paths joins them (`AuthMethodContextParts.secrets`), so does every
96
+ * occurrence of a secret's value.
97
+ *
98
+ * @experimental
99
+ */
100
+ export declare const AUTH_METHOD_LOG_REDACTED_FIELDS: readonly string[];
101
+ /**
102
+ * The key a method's log fields are nested under, so they never overwrite
103
+ * the host's own fields of the line (`reqId`, `realm`, …).
104
+ *
105
+ * @experimental
106
+ */
107
+ export declare const AUTH_METHOD_LOG_FIELDS_KEY = "authMethod";
108
+ /**
109
+ * Build the context a method is handed for one realm (ADR-019 Decision 12).
110
+ *
111
+ * The context holds exactly five members: the config, parsed by the method's
112
+ * `configSchema`, copied and deep-frozen (it must be plain data: objects,
113
+ * arrays and primitives, so a Map, Set, Date or other class instance is
114
+ * refused, as none of them freezes); `egress`; `signer`; `log`; and
115
+ * `routeUrl`. Each is an adapter that calls the host's object with the members
116
+ * the contract names and nothing else:
117
+ *
118
+ * - `egress.request` passes `method` (`GET` or `POST`), `url`, `headers`,
119
+ * `body`, `purpose` and `source`, and adds this realm's name. One cap only,
120
+ * `maxRedirects: 0` when the method asks for `redirect: 'error'`; no other
121
+ * cap, no other realm, no other method. `source` is `operator` or `admin` (the
122
+ * labels the realm's egress allowlist applies to, for non-public
123
+ * addresses) only when the URL's origin is the origin of a URL among the
124
+ * plain members of the method's parsed config, never a secret one; any
125
+ * other call goes as `remote_document`, so a method cannot reach an
126
+ * allowlisted address its config does not name.
127
+ * - `signer.signClientAssertion` passes `audience`, `claims` and `algorithm`,
128
+ * with this realm, this provider and the host's caller role. It sets `iat`
129
+ * when the claims carry none, and lowers `exp` to at most
130
+ * {@link MAX_AUTH_METHOD_CLIENT_ASSERTION_LIFETIME_SECONDS} from now.
131
+ * `signer.getUpstreamJwks` takes no argument and asks for this realm and
132
+ * provider.
133
+ * - `log` calls the logger's four levels with a message and the method's
134
+ * fields nested under {@link AUTH_METHOD_LOG_FIELDS_KEY}, as a copy in which
135
+ * every field {@link AUTH_METHOD_LOG_REDACTED_FIELDS} names, or a secret
136
+ * path's last segment, is redacted at any depth, and every secret value is
137
+ * cut out of the message and of every string.
138
+ * - `routeUrl` answers URLs only for the paths the method's own `routes`
139
+ * declare.
140
+ *
141
+ * So the host can hand over its whole egress client, signer and logger, and a
142
+ * method still reaches no `validateDestination`, no `decrypt`, no `child`, no
143
+ * database or cache handle and no key material.
144
+ *
145
+ * @throws AuthMethodConfigError when the config does not pass `configSchema`,
146
+ * or its parsed form is not plain data; the error names the failing
147
+ * members, never their values
148
+ * @experimental
149
+ */
150
+ export declare function createAuthMethodContext<TConfig>(method: AuthMethod<TConfig>, parts: AuthMethodContextParts): AuthMethodContext<TConfig>;
151
+ /**
152
+ * A method's config as a context holds it: parsed by its `configSchema`, and
153
+ * a copy of plain data (objects with the plain prototype or none, arrays,
154
+ * primitives). The admin API checks a plugin's config with it before it
155
+ * writes it (#600, #639), so a config the context would refuse is refused at
156
+ * the configure, never at every sign-in.
157
+ *
158
+ * @throws AuthMethodConfigError when the config does not pass the schema, or
159
+ * its parsed form is not plain data; the error names the failing member's
160
+ * path, never its value
161
+ * @experimental
162
+ */
163
+ export declare function parseAuthMethodConfig<TConfig>(method: AuthMethod<TConfig>, raw: unknown): TConfig;