@o3co/auth-provider-foundation 0.16.0 → 0.17.0-rc.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/README.ja.md +154 -29
- package/README.md +482 -45
- package/config/reference.conf +20 -0
- package/dist/endpointUrl.d.mts +21 -47
- package/dist/endpointUrl.d.mts.map +1 -1
- package/dist/endpointUrl.mjs +23 -50
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +18 -35
- package/dist/mfa/HttpMfaFactorStore.d.mts +55 -0
- package/dist/mfa/HttpMfaFactorStore.d.mts.map +1 -0
- package/dist/mfa/HttpMfaFactorStore.mjs +183 -0
- package/dist/mfa/markEnrolled.d.mts +4 -0
- package/dist/mfa/markEnrolled.d.mts.map +1 -0
- package/dist/mfa/markEnrolled.mjs +31 -0
- package/dist/mfa/module.d.mts +27 -0
- package/dist/mfa/module.d.mts.map +1 -0
- package/dist/mfa/module.mjs +56 -0
- package/dist/mfa/section.d.mts +45 -0
- package/dist/mfa/section.d.mts.map +1 -0
- package/dist/mfa/section.mjs +99 -0
- package/dist/mfa/storeFailure.d.mts +52 -0
- package/dist/mfa/storeFailure.d.mts.map +1 -0
- package/dist/mfa/storeFailure.mjs +107 -0
- package/dist/repositories/HttpUserRepository.d.mts +54 -79
- package/dist/repositories/HttpUserRepository.d.mts.map +1 -1
- package/dist/repositories/HttpUserRepository.mjs +117 -413
- package/dist/repositories/storeErrors.d.mts +18 -27
- package/dist/repositories/storeErrors.d.mts.map +1 -1
- package/dist/repositories/storeErrors.mjs +30 -40
- package/dist/repositories/wwwAuthenticate.d.mts.map +1 -1
- package/dist/repositories/wwwAuthenticate.mjs +12 -16
- package/dist/storeTransport.d.mts +97 -0
- package/dist/storeTransport.d.mts.map +1 -0
- package/dist/storeTransport.mjs +290 -0
- package/dist/testing/index.d.mts +28 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +58 -0
- package/package.json +18 -7
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Store refused the credential this deployment presented: a `401` or a
|
|
3
|
-
* `403` carrying a `Bearer` challenge (RFC 6750 §3
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
* any Store failure.
|
|
3
|
+
* `403` carrying a `Bearer` challenge (RFC 6750 §3) to a request that sent
|
|
4
|
+
* `bearerToken`. An outage, not an answer about a user or a factor. See
|
|
5
|
+
* README, The wire contract.
|
|
7
6
|
*
|
|
8
|
-
* `name`
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* the status
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* The Store's status is `storeStatus`, never `status` or `statusCode`: those
|
|
17
|
-
* are what Express's finalhandler, http-errors and the standalone's terminal
|
|
18
|
-
* handler read as the status to ANSWER with, and a 4xx there is taken for a
|
|
19
|
-
* client error — the Store's 401 would reach the browser as its own, and go
|
|
20
|
-
* unlogged.
|
|
7
|
+
* `name` and `storeStatus` are part of the contract. The message leads with
|
|
8
|
+
* the client that was refused, and names the endpoint's origin and path, the
|
|
9
|
+
* status and the option to check; never the
|
|
10
|
+
* token, and nothing the Store wrote. Not `status` or `statusCode`: Express's
|
|
11
|
+
* finalhandler, http-errors and the standalone's terminal handler read those
|
|
12
|
+
* as the status to ANSWER with, so the Store's 401 would reach the browser as
|
|
13
|
+
* its own, unlogged.
|
|
21
14
|
*/
|
|
22
15
|
export declare class StoreCredentialRefusedError extends Error {
|
|
23
16
|
readonly storeStatus: 401 | 403;
|
|
24
|
-
constructor(url: string, status: 401 | 403);
|
|
17
|
+
constructor(url: string, status: 401 | 403, owner?: string);
|
|
25
18
|
}
|
|
26
19
|
/** What went wrong with the exchange, when it was not the Store's answer. */
|
|
27
20
|
export type StoreTransportFailure =
|
|
@@ -47,17 +40,15 @@ export type StoreTransportFailure =
|
|
|
47
40
|
/** An HTTP answer arrived, and its body broke before it was read. */
|
|
48
41
|
| "unreadable";
|
|
49
42
|
/**
|
|
50
|
-
* The Store could not be reached, or what it answered could not be read
|
|
51
|
-
* transport failure rather than an answer
|
|
52
|
-
* as it answers any Store failure
|
|
53
|
-
*
|
|
54
|
-
* error's `name`), as are `reason` and `code`.
|
|
43
|
+
* The Store could not be reached, or what it answered could not be read: a
|
|
44
|
+
* transport failure rather than an answer, thrown so every caller answers it
|
|
45
|
+
* as it answers any Store failure. `name`, `reason` and `code` are part of the
|
|
46
|
+
* contract.
|
|
55
47
|
*
|
|
56
48
|
* Built only from what an operator can act on: a fixed message naming the
|
|
57
|
-
* endpoint and the failure, and `code
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* {@link StoreCredentialRefusedError}).
|
|
49
|
+
* endpoint and the failure, and `code`, an allowlisted transport code when
|
|
50
|
+
* there is one. Never a `cause`: the transport's own error may quote what was
|
|
51
|
+
* sent or received. And no `status` (see {@link StoreCredentialRefusedError}).
|
|
61
52
|
*/
|
|
62
53
|
export declare class StoreTransportError extends Error {
|
|
63
54
|
readonly reason: StoreTransportFailure;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storeErrors.d.mts","sourceRoot":"","sources":["../../src/repositories/storeErrors.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"storeErrors.d.mts","sourceRoot":"","sources":["../../src/repositories/storeErrors.mts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;GAaG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACrD,QAAQ,CAAC,WAAW,EAAE,GAAG,GAAG,GAAG,CAAC;gBAEpB,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,GAAG,EAAE,KAAK,SAAuB;CASxE;AAED,6EAA6E;AAC7E,MAAM,MAAM,qBAAqB;AAChC;;;GAGG;AACD,aAAa;AACf;;;;;;GAMG;GACD,mBAAmB;AACrB;;;;GAIG;GACD,oBAAoB;AACtB,qEAAqE;GACnE,YAAY,CAAC;AAEhB;;;;;;;;;;GAUG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEtB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,EAAE,IAAI,CAAC,EAAE,MAAM;CAMzE;AA4DD,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAW9D;AAsCD;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE;IACT,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,GACC,mBAAmB,CAUrB;AAED,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAG9E"}
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
/**
|
|
17
|
-
* The failures
|
|
18
|
-
*
|
|
17
|
+
* The failures the Store clients — `HttpUserRepository` and
|
|
18
|
+
* `HttpMfaFactorStore` — throw about the exchange with the Store rather than
|
|
19
|
+
* about what they asked: the Store refusing this deployment's credential,
|
|
19
20
|
* and a transport that failed. Each is built from an allowlist — the
|
|
20
21
|
* endpoint (origin and path), a status, a transport code — and never from
|
|
21
22
|
* what the Store or the transport said, which may quote the request.
|
|
@@ -23,47 +24,38 @@
|
|
|
23
24
|
import { endpointForMessage } from "../endpointUrl.mjs";
|
|
24
25
|
/**
|
|
25
26
|
* The Store refused the credential this deployment presented: a `401` or a
|
|
26
|
-
* `403` carrying a `Bearer` challenge (RFC 6750 §3
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
* any Store failure.
|
|
27
|
+
* `403` carrying a `Bearer` challenge (RFC 6750 §3) to a request that sent
|
|
28
|
+
* `bearerToken`. An outage, not an answer about a user or a factor. See
|
|
29
|
+
* README, The wire contract.
|
|
30
30
|
*
|
|
31
|
-
* `name`
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* the status
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* The Store's status is `storeStatus`, never `status` or `statusCode`: those
|
|
40
|
-
* are what Express's finalhandler, http-errors and the standalone's terminal
|
|
41
|
-
* handler read as the status to ANSWER with, and a 4xx there is taken for a
|
|
42
|
-
* client error — the Store's 401 would reach the browser as its own, and go
|
|
43
|
-
* unlogged.
|
|
31
|
+
* `name` and `storeStatus` are part of the contract. The message leads with
|
|
32
|
+
* the client that was refused, and names the endpoint's origin and path, the
|
|
33
|
+
* status and the option to check; never the
|
|
34
|
+
* token, and nothing the Store wrote. Not `status` or `statusCode`: Express's
|
|
35
|
+
* finalhandler, http-errors and the standalone's terminal handler read those
|
|
36
|
+
* as the status to ANSWER with, so the Store's 401 would reach the browser as
|
|
37
|
+
* its own, unlogged.
|
|
44
38
|
*/
|
|
45
39
|
export class StoreCredentialRefusedError extends Error {
|
|
46
40
|
storeStatus;
|
|
47
|
-
constructor(url, status) {
|
|
48
|
-
super(
|
|
49
|
-
`(HTTP ${status} with a Bearer challenge) — bearerToken (
|
|
41
|
+
constructor(url, status, owner = "HttpUserRepository") {
|
|
42
|
+
super(`${owner}: the Store at ${endpointForMessage(url)} refused this deployment's credential ` +
|
|
43
|
+
`(HTTP ${status} with a Bearer challenge) — bearerToken (REPOSITORIES_USER_HTTP_BEARER_TOKEN) is ` +
|
|
50
44
|
"not a token the Store accepts");
|
|
51
45
|
this.name = "StoreCredentialRefusedError";
|
|
52
46
|
this.storeStatus = status;
|
|
53
47
|
}
|
|
54
48
|
}
|
|
55
49
|
/**
|
|
56
|
-
* The Store could not be reached, or what it answered could not be read
|
|
57
|
-
* transport failure rather than an answer
|
|
58
|
-
* as it answers any Store failure
|
|
59
|
-
*
|
|
60
|
-
* error's `name`), as are `reason` and `code`.
|
|
50
|
+
* The Store could not be reached, or what it answered could not be read: a
|
|
51
|
+
* transport failure rather than an answer, thrown so every caller answers it
|
|
52
|
+
* as it answers any Store failure. `name`, `reason` and `code` are part of the
|
|
53
|
+
* contract.
|
|
61
54
|
*
|
|
62
55
|
* Built only from what an operator can act on: a fixed message naming the
|
|
63
|
-
* endpoint and the failure, and `code
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* {@link StoreCredentialRefusedError}).
|
|
56
|
+
* endpoint and the failure, and `code`, an allowlisted transport code when
|
|
57
|
+
* there is one. Never a `cause`: the transport's own error may quote what was
|
|
58
|
+
* sent or received. And no `status` (see {@link StoreCredentialRefusedError}).
|
|
67
59
|
*/
|
|
68
60
|
export class StoreTransportError extends Error {
|
|
69
61
|
reason;
|
|
@@ -144,15 +136,13 @@ const CLOSED_CODES = new Set([
|
|
|
144
136
|
"ECONNABORTED",
|
|
145
137
|
]);
|
|
146
138
|
/**
|
|
147
|
-
* Which failure a rejected request is, read by the error's name and code
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* undici
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* connection closed between two requests has read an entire earlier answer.
|
|
155
|
-
* Anything else is `unreachable`.
|
|
139
|
+
* Which failure a rejected request is, read by the error's name and code only.
|
|
140
|
+
* A head the transport cannot take (`HTTPParserError`, an `HPE_*` code,
|
|
141
|
+
* `UND_ERR_HEADERS_OVERFLOW`) is `malformed_response`. A connection closed or
|
|
142
|
+
* reset under the request (`CLOSED_CODES`) is `connection_closed`, whatever the
|
|
143
|
+
* socket had read: undici counts bytes over the socket's life, and a pooled
|
|
144
|
+
* keep-alive connection closed between two requests has read an entire
|
|
145
|
+
* earlier answer. Anything else is `unreachable`.
|
|
156
146
|
*/
|
|
157
147
|
function requestFailureReason(err) {
|
|
158
148
|
let closed = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wwwAuthenticate.d.mts","sourceRoot":"","sources":["../../src/repositories/wwwAuthenticate.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wwwAuthenticate.d.mts","sourceRoot":"","sources":["../../src/repositories/wwwAuthenticate.mts"],"names":[],"mappings":"AA+CA;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEhE"}
|
|
@@ -23,26 +23,22 @@
|
|
|
23
23
|
* reach an error this adapter throws.
|
|
24
24
|
*/
|
|
25
25
|
/**
|
|
26
|
-
* A quoted-string (RFC 9110 §5.6.4), escapes included
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* value turned into seconds per answer.
|
|
26
|
+
* A quoted-string (RFC 9110 §5.6.4), escapes included; one left unterminated
|
|
27
|
+
* runs to the end of the value, so what follows its opening quote is its
|
|
28
|
+
* content, never a challenge. Each character is consumed once (a backslash
|
|
29
|
+
* with the character after it, when there is one), so a value of escaped
|
|
30
|
+
* quotes with no closing one costs one scan rather than a retry from every
|
|
31
|
+
* quote, which on a 64 KiB value takes seconds per answer.
|
|
33
32
|
*/
|
|
34
33
|
const QUOTED_STRING = /"(?:[^"\\]|\\[\s\S]?)*(?:"|$)/g;
|
|
35
34
|
/**
|
|
36
35
|
* An auth-scheme `Bearer`, case-insensitive (RFC 9110 §11.1), where a
|
|
37
|
-
* challenge begins
|
|
38
|
-
* value's end, a comma, or whitespace that does not lead to `=` (
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* lookahead is never re-entered, so a long run followed by `=` is not
|
|
44
|
-
* retried from every shorter run. `(?<=[ \t])` then requires that the run
|
|
45
|
-
* was not empty, so `bearerish` is not the scheme.
|
|
36
|
+
* challenge begins (the value's start or after a comma), followed by the
|
|
37
|
+
* value's end, a comma, or whitespace that does not lead to `=` (then `bearer`
|
|
38
|
+
* is a parameter's name, as in `bearer = 1`). The lookahead captures the
|
|
39
|
+
* whole whitespace run and the backreference consumes exactly it, so a long
|
|
40
|
+
* run followed by `=` is not retried from every shorter run; `(?<=[ \t])`
|
|
41
|
+
* requires the run was not empty, so `bearerish` is not the scheme.
|
|
46
42
|
*/
|
|
47
43
|
const BEARER_CHALLENGE = /(?:^|,)[ \t]*bearer(?=([ \t]*))\1(?:$|,|(?<=[ \t])(?!=))/i;
|
|
48
44
|
/**
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default ceiling on an upstream response body, in bytes.
|
|
3
|
+
*
|
|
4
|
+
* A `User` record is a few hundred bytes; 1 MiB is generous for one carrying
|
|
5
|
+
* custom claims and small enough that a hostile or broken Store cannot walk the
|
|
6
|
+
* process out of memory one login at a time.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
9
|
+
/**
|
|
10
|
+
* The credential presented to the Store, checked when `owner` is built (so
|
|
11
|
+
* an unusable one fails at boot) and turned into the `Authorization` value;
|
|
12
|
+
* `undefined` when none is configured, which sends no `Authorization` header.
|
|
13
|
+
*
|
|
14
|
+
* The shape is refused here, not left to `fetch`, which QUOTES a header value
|
|
15
|
+
* it refuses in the `TypeError` it throws, where the session routes log it.
|
|
16
|
+
* The strength is core's shared-secret floor (`MIN_SECRET_ENTROPY_BYTES`, on
|
|
17
|
+
* the decoded length): whoever holds this token speaks to the Store as
|
|
18
|
+
* auth.provider. No message quotes the value.
|
|
19
|
+
*/
|
|
20
|
+
export declare function bearerAuthorization(value: unknown, owner: string): string | undefined;
|
|
21
|
+
/** `timeout`, the whole exchange's deadline in milliseconds, or an error naming it for `owner`. */
|
|
22
|
+
export declare function checkStoreTimeout(timeout: unknown, owner: string): number;
|
|
23
|
+
/** `maxResponseBytes`, the response cap, or an error naming it for `owner`. */
|
|
24
|
+
export declare function checkStoreResponseCap(maxResponseBytes: unknown, owner: string): number;
|
|
25
|
+
/**
|
|
26
|
+
* The transport settings of a Store client's configuration block — the user
|
|
27
|
+
* repository's `http` block — as a client is built from them: `timeout` and
|
|
28
|
+
* `maxResponseBytes` read from text too and defaulted only when absent, and
|
|
29
|
+
* `bearerToken` forwarded whenever set, whatever it holds. The client's
|
|
30
|
+
* constructor refuses what the transport cannot honour.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readStoreTransportConfig(block: Readonly<Record<string, unknown>>): {
|
|
33
|
+
readonly bearerToken?: string;
|
|
34
|
+
readonly timeout: number;
|
|
35
|
+
readonly maxResponseBytes: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Releases a response body we are not going to read. Left unconsumed, undici
|
|
39
|
+
* holds the socket until the response is garbage collected instead of
|
|
40
|
+
* returning it to the keep-alive pool: a slow leak on the failure path.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately not awaited: that would hand a hostile Store a second way to
|
|
43
|
+
* stall the caller, the one the request deadline exists to close, and some
|
|
44
|
+
* interceptors never settle it at all.
|
|
45
|
+
*/
|
|
46
|
+
export declare function discardBody(res: Response): void;
|
|
47
|
+
/**
|
|
48
|
+
* Reads at most `limit` bytes of `res` as text, throwing once it is passed.
|
|
49
|
+
* `Content-Length` refuses an honest oversized response before a byte is read,
|
|
50
|
+
* but the streaming count is the load-bearing half: a hostile Store omits the
|
|
51
|
+
* header or lies, and chunked encoding has none. Everything it throws is the
|
|
52
|
+
* adapter's own (the cap, the deadline's rejection, what `unreadable` makes of
|
|
53
|
+
* a transport error mid-read), never the transport's error as it is.
|
|
54
|
+
*/
|
|
55
|
+
export declare function readBodyCapped(res: Response, limit: number, endpoint: string, deadline: Promise<never>, unreadable: (err: unknown) => Error, owner: string): Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Whether `err` is the abort our own deadline raised on the `fetch` itself,
|
|
58
|
+
* where the response headers never arrive. Deliberately shallow: an aborted
|
|
59
|
+
* `fetch` rejects with the `AbortError` directly. A runtime that wrapped one
|
|
60
|
+
* would still fail the request, as a `StoreTransportError` rather than a
|
|
61
|
+
* `TimeoutError`: misnamed, not missed. A stalled *body* is the deadline race
|
|
62
|
+
* in `readBodyCapped`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isAbortError(err: unknown): boolean;
|
|
65
|
+
/** What a request to the Store is sent with. */
|
|
66
|
+
export interface StoreRequestSettings {
|
|
67
|
+
/** `Bearer <token>`, or `undefined` to send no `Authorization` header. */
|
|
68
|
+
readonly authorization: string | undefined;
|
|
69
|
+
/** The whole exchange's deadline, in milliseconds. */
|
|
70
|
+
readonly timeout: number;
|
|
71
|
+
/** The most bytes of a body read. */
|
|
72
|
+
readonly maxResponseBytes: number;
|
|
73
|
+
}
|
|
74
|
+
/** What a failure of the exchange says, each naming the endpoint. */
|
|
75
|
+
export interface StoreRequestMessages {
|
|
76
|
+
/** Who is asking, leading every message. */
|
|
77
|
+
readonly owner: string;
|
|
78
|
+
readonly unreachable: string;
|
|
79
|
+
readonly closed: string;
|
|
80
|
+
readonly malformed: string;
|
|
81
|
+
readonly unreadable: string;
|
|
82
|
+
}
|
|
83
|
+
/** An answer: the response, whose body is released, and the body as text when it was read. */
|
|
84
|
+
export interface StoreAnswer {
|
|
85
|
+
readonly response: Response;
|
|
86
|
+
readonly text: string | undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* `POST`s `body` as JSON to `url` and answers what came back, the body read
|
|
90
|
+
* (up to the cap, within the deadline) only when `readsBody` says so for the
|
|
91
|
+
* status, and released otherwise. A `401` or `403` with a `Bearer` challenge
|
|
92
|
+
* to a request that carried the credential throws
|
|
93
|
+
* `StoreCredentialRefusedError`; a deadline passed throws an error named
|
|
94
|
+
* `TimeoutError`; a transport failure throws a `StoreTransportError`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function postToStore(url: string, body: unknown, settings: StoreRequestSettings, messages: StoreRequestMessages, readsBody: (status: number) => boolean): Promise<StoreAnswer>;
|
|
97
|
+
//# sourceMappingURL=storeTransport.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storeTransport.d.mts","sourceRoot":"","sources":["../src/storeTransport.mts"],"names":[],"mappings":"AA4CA;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,QAAc,CAAC;AAqBtD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA2BrF;AAOD,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQzE;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtF;AAkBD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IACnF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CAClC,CASA;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAI/C;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CACnC,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,EACxB,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,EACnC,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA2CjB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAMlD;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CAClC;AAED,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAED,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,oBAAoB,EAC9B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GACpC,OAAO,CAAC,WAAW,CAAC,CAqEtB"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 1o1 Co. Ltd.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The transport every request to the Store rides on: the credential it
|
|
18
|
+
* presents, the deadline, the response cap, and a `POST` of JSON that
|
|
19
|
+
* follows no redirect.
|
|
20
|
+
*
|
|
21
|
+
* Guarantees: a setting the transport cannot honour is refused when the
|
|
22
|
+
* client is built, naming the setting and quoting no value; one deadline
|
|
23
|
+
* covers a whole exchange, the body read included; a body is read up to the
|
|
24
|
+
* cap and no further, and a body not read is released unawaited; what is
|
|
25
|
+
* thrown is the adapter's own — never a transport's error, which may quote
|
|
26
|
+
* the request — and names an endpoint by origin and path alone.
|
|
27
|
+
*/
|
|
28
|
+
import { describeWeakSecret, MIN_SECRET_ENTROPY_BYTES, measureSecretEntropyBytes, wholeNumberFromEnv, } from "@o3co/auth-provider-core";
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import { endpointForMessage } from "./endpointUrl.mjs";
|
|
31
|
+
import { readFailure, requestFailure, StoreCredentialRefusedError, } from "./repositories/storeErrors.mjs";
|
|
32
|
+
import { hasBearerChallenge } from "./repositories/wwwAuthenticate.mjs";
|
|
33
|
+
/**
|
|
34
|
+
* Default ceiling on an upstream response body, in bytes.
|
|
35
|
+
*
|
|
36
|
+
* A `User` record is a few hundred bytes; 1 MiB is generous for one carrying
|
|
37
|
+
* custom claims and small enough that a hostile or broken Store cannot walk the
|
|
38
|
+
* process out of memory one login at a time.
|
|
39
|
+
*/
|
|
40
|
+
export const DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
41
|
+
/** Default request deadline, in milliseconds, when the configuration names none. */
|
|
42
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
43
|
+
/**
|
|
44
|
+
* Largest delay Node's timer subsystem represents. Anything above it is
|
|
45
|
+
* silently clamped to 1ms — so an operator writing a very large number meaning
|
|
46
|
+
* "be patient" would otherwise get the most impatient timeout possible.
|
|
47
|
+
*/
|
|
48
|
+
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
49
|
+
/**
|
|
50
|
+
* RFC 6750 §2.1 `b64token` — the characters a bearer credential may carry.
|
|
51
|
+
* Nothing in it is whitespace or a control character, so a token that
|
|
52
|
+
* matches cannot break the header it rides in.
|
|
53
|
+
*/
|
|
54
|
+
const B64TOKEN = /^[A-Za-z0-9\-._~+/]+=*$/;
|
|
55
|
+
const BEARER_TOKEN_FIELD = "bearerToken";
|
|
56
|
+
/**
|
|
57
|
+
* The credential presented to the Store, checked when `owner` is built (so
|
|
58
|
+
* an unusable one fails at boot) and turned into the `Authorization` value;
|
|
59
|
+
* `undefined` when none is configured, which sends no `Authorization` header.
|
|
60
|
+
*
|
|
61
|
+
* The shape is refused here, not left to `fetch`, which QUOTES a header value
|
|
62
|
+
* it refuses in the `TypeError` it throws, where the session routes log it.
|
|
63
|
+
* The strength is core's shared-secret floor (`MIN_SECRET_ENTROPY_BYTES`, on
|
|
64
|
+
* the decoded length): whoever holds this token speaks to the Store as
|
|
65
|
+
* auth.provider. No message quotes the value.
|
|
66
|
+
*/
|
|
67
|
+
export function bearerAuthorization(value, owner) {
|
|
68
|
+
if (value === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
const refuse = (problem) => new Error(`${owner}: "${BEARER_TOKEN_FIELD}" ${problem}`);
|
|
71
|
+
if (typeof value !== "string")
|
|
72
|
+
throw refuse("must be a string");
|
|
73
|
+
if (value === "") {
|
|
74
|
+
throw refuse('must not be empty — HOCON substitutes an exported-but-empty variable as ""; ' +
|
|
75
|
+
"leave it unset to send no Authorization header");
|
|
76
|
+
}
|
|
77
|
+
if (!B64TOKEN.test(value)) {
|
|
78
|
+
throw refuse("must be a bare RFC 6750 token: letters, digits and - . _ ~ + /, then optional = padding — " +
|
|
79
|
+
'no whitespace, no line break, and no "Bearer " prefix (the scheme is added)');
|
|
80
|
+
}
|
|
81
|
+
const actualBytes = measureSecretEntropyBytes(value);
|
|
82
|
+
if (actualBytes < MIN_SECRET_ENTROPY_BYTES) {
|
|
83
|
+
throw new Error(`${owner}: ${describeWeakSecret(actualBytes, {
|
|
84
|
+
configKey: "repositories.user.http.bearerToken",
|
|
85
|
+
envVar: "REPOSITORIES_USER_HTTP_BEARER_TOKEN",
|
|
86
|
+
})}`);
|
|
87
|
+
}
|
|
88
|
+
return `Bearer ${value}`;
|
|
89
|
+
}
|
|
90
|
+
/** Whether `value` is a positive integer that fits `bound`. */
|
|
91
|
+
function isPositiveIntegerWithin(value, bound) {
|
|
92
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= bound;
|
|
93
|
+
}
|
|
94
|
+
/** `timeout`, the whole exchange's deadline in milliseconds, or an error naming it for `owner`. */
|
|
95
|
+
export function checkStoreTimeout(timeout, owner) {
|
|
96
|
+
if (!isPositiveIntegerWithin(timeout, MAX_TIMEOUT_MS)) {
|
|
97
|
+
throw new Error(`${owner}: "timeout" must be a positive integer no greater than ` +
|
|
98
|
+
`${MAX_TIMEOUT_MS} milliseconds`);
|
|
99
|
+
}
|
|
100
|
+
return timeout;
|
|
101
|
+
}
|
|
102
|
+
/** `maxResponseBytes`, the response cap, or an error naming it for `owner`. */
|
|
103
|
+
export function checkStoreResponseCap(maxResponseBytes, owner) {
|
|
104
|
+
if (!isPositiveIntegerWithin(maxResponseBytes, Number.MAX_SAFE_INTEGER)) {
|
|
105
|
+
throw new Error(`${owner}: "maxResponseBytes" must be a positive integer`);
|
|
106
|
+
}
|
|
107
|
+
return maxResponseBytes;
|
|
108
|
+
}
|
|
109
|
+
/** A number, or a string of decimal digits as an environment variable carries one. */
|
|
110
|
+
const numberOrDecimalDigits = wholeNumberFromEnv(z.number());
|
|
111
|
+
/**
|
|
112
|
+
* A numeric config value that may arrive as a string (HOCON environment
|
|
113
|
+
* substitution yields strings), read as core reads one: a number, or decimal
|
|
114
|
+
* digits. Only an *absent* key takes `fallback`. Anything else present (a
|
|
115
|
+
* blank variable, `"0x10"`, `"1e3"`, `"5.0"`, `"+5"`, a boolean) becomes `NaN`
|
|
116
|
+
* for the constructor to reject: a boot failure, not a silent default.
|
|
117
|
+
*/
|
|
118
|
+
const toNumber = (value, fallback) => {
|
|
119
|
+
if (value === undefined || value === null)
|
|
120
|
+
return fallback;
|
|
121
|
+
const read = numberOrDecimalDigits.safeParse(value);
|
|
122
|
+
return read.success ? read.data : Number.NaN;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The transport settings of a Store client's configuration block — the user
|
|
126
|
+
* repository's `http` block — as a client is built from them: `timeout` and
|
|
127
|
+
* `maxResponseBytes` read from text too and defaulted only when absent, and
|
|
128
|
+
* `bearerToken` forwarded whenever set, whatever it holds. The client's
|
|
129
|
+
* constructor refuses what the transport cannot honour.
|
|
130
|
+
*/
|
|
131
|
+
export function readStoreTransportConfig(block) {
|
|
132
|
+
return {
|
|
133
|
+
// Forwarded whenever SET, not only when well-typed: a token that vanished
|
|
134
|
+
// would be a deployment that believes its Store calls authenticated and
|
|
135
|
+
// sends them bare.
|
|
136
|
+
...(block.bearerToken !== undefined ? { bearerToken: block.bearerToken } : {}),
|
|
137
|
+
timeout: toNumber(block.timeout, DEFAULT_TIMEOUT_MS),
|
|
138
|
+
maxResponseBytes: toNumber(block.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Releases a response body we are not going to read. Left unconsumed, undici
|
|
143
|
+
* holds the socket until the response is garbage collected instead of
|
|
144
|
+
* returning it to the keep-alive pool: a slow leak on the failure path.
|
|
145
|
+
*
|
|
146
|
+
* Deliberately not awaited: that would hand a hostile Store a second way to
|
|
147
|
+
* stall the caller, the one the request deadline exists to close, and some
|
|
148
|
+
* interceptors never settle it at all.
|
|
149
|
+
*/
|
|
150
|
+
export function discardBody(res) {
|
|
151
|
+
res.body?.cancel().catch(() => {
|
|
152
|
+
// Already consumed, already errored, or aborted — nothing to release.
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Reads at most `limit` bytes of `res` as text, throwing once it is passed.
|
|
157
|
+
* `Content-Length` refuses an honest oversized response before a byte is read,
|
|
158
|
+
* but the streaming count is the load-bearing half: a hostile Store omits the
|
|
159
|
+
* header or lies, and chunked encoding has none. Everything it throws is the
|
|
160
|
+
* adapter's own (the cap, the deadline's rejection, what `unreadable` makes of
|
|
161
|
+
* a transport error mid-read), never the transport's error as it is.
|
|
162
|
+
*/
|
|
163
|
+
export async function readBodyCapped(res, limit, endpoint, deadline, unreadable, owner) {
|
|
164
|
+
const declared = Number(res.headers.get("content-length"));
|
|
165
|
+
if (Number.isFinite(declared) && declared > limit) {
|
|
166
|
+
discardBody(res);
|
|
167
|
+
throw new Error(`${owner}: upstream ${endpoint} response exceeds the ${limit}-byte cap ` +
|
|
168
|
+
`(Content-Length: ${declared})`);
|
|
169
|
+
}
|
|
170
|
+
if (res.body === null)
|
|
171
|
+
return "";
|
|
172
|
+
const reader = res.body.getReader();
|
|
173
|
+
const decoder = new TextDecoder();
|
|
174
|
+
let text = "";
|
|
175
|
+
let read = 0;
|
|
176
|
+
try {
|
|
177
|
+
for (;;) {
|
|
178
|
+
// Raced against the deadline rather than relying on `signal` alone:
|
|
179
|
+
// aborting a request does not reliably interrupt a `read()` already
|
|
180
|
+
// in flight, which is exactly the slow-loris shape — headers arrive
|
|
181
|
+
// promptly, then the body dribbles or stops. One absolute deadline
|
|
182
|
+
// for the whole exchange, not a fresh one per chunk.
|
|
183
|
+
const { done, value } = await Promise.race([
|
|
184
|
+
reader.read().catch((err) => {
|
|
185
|
+
throw unreadable(err);
|
|
186
|
+
}),
|
|
187
|
+
deadline,
|
|
188
|
+
]);
|
|
189
|
+
if (done)
|
|
190
|
+
break;
|
|
191
|
+
read += value.byteLength;
|
|
192
|
+
if (read > limit) {
|
|
193
|
+
throw new Error(`${owner}: upstream ${endpoint} response exceeds the ${limit}-byte cap`);
|
|
194
|
+
}
|
|
195
|
+
text += decoder.decode(value, { stream: true });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
// Tears down the connection when we bail out early; a no-op once the
|
|
200
|
+
// stream has completed on its own. Not awaited, for the reason given on
|
|
201
|
+
// `discardBody`.
|
|
202
|
+
reader.cancel().catch(() => { });
|
|
203
|
+
}
|
|
204
|
+
return text + decoder.decode();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Whether `err` is the abort our own deadline raised on the `fetch` itself,
|
|
208
|
+
* where the response headers never arrive. Deliberately shallow: an aborted
|
|
209
|
+
* `fetch` rejects with the `AbortError` directly. A runtime that wrapped one
|
|
210
|
+
* would still fail the request, as a `StoreTransportError` rather than a
|
|
211
|
+
* `TimeoutError`: misnamed, not missed. A stalled *body* is the deadline race
|
|
212
|
+
* in `readBodyCapped`.
|
|
213
|
+
*/
|
|
214
|
+
export function isAbortError(err) {
|
|
215
|
+
// Optional chaining rather than a `typeof` guard: it covers `null`,
|
|
216
|
+
// `undefined` and a thrown primitive in the same expression, with no
|
|
217
|
+
// branch that only a contrived throw could reach.
|
|
218
|
+
const name = err?.name;
|
|
219
|
+
return name === "AbortError" || name === "TimeoutError";
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* `POST`s `body` as JSON to `url` and answers what came back, the body read
|
|
223
|
+
* (up to the cap, within the deadline) only when `readsBody` says so for the
|
|
224
|
+
* status, and released otherwise. A `401` or `403` with a `Bearer` challenge
|
|
225
|
+
* to a request that carried the credential throws
|
|
226
|
+
* `StoreCredentialRefusedError`; a deadline passed throws an error named
|
|
227
|
+
* `TimeoutError`; a transport failure throws a `StoreTransportError`.
|
|
228
|
+
*/
|
|
229
|
+
export async function postToStore(url, body, settings, messages, readsBody) {
|
|
230
|
+
const endpoint = endpointForMessage(url);
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
let timedOut = false;
|
|
233
|
+
const timeoutError = () => {
|
|
234
|
+
const error = new Error(`${messages.owner}: request to ${endpoint} timed out after ${settings.timeout}ms`);
|
|
235
|
+
error.name = "TimeoutError";
|
|
236
|
+
return error;
|
|
237
|
+
};
|
|
238
|
+
let fireDeadline = () => { };
|
|
239
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
240
|
+
fireDeadline = () => reject(timeoutError());
|
|
241
|
+
});
|
|
242
|
+
deadline.catch(() => { });
|
|
243
|
+
const timer = setTimeout(() => {
|
|
244
|
+
timedOut = true;
|
|
245
|
+
controller.abort();
|
|
246
|
+
fireDeadline();
|
|
247
|
+
}, settings.timeout);
|
|
248
|
+
try {
|
|
249
|
+
let response;
|
|
250
|
+
try {
|
|
251
|
+
response = await fetch(url, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: settings.authorization === undefined
|
|
254
|
+
? { "Content-Type": "application/json" }
|
|
255
|
+
: { "Content-Type": "application/json", Authorization: settings.authorization },
|
|
256
|
+
body: JSON.stringify(body),
|
|
257
|
+
signal: controller.signal,
|
|
258
|
+
// Never followed: a redirect would re-send the body to a `Location`
|
|
259
|
+
// no endpoint check has seen, and take its answer as the Store's.
|
|
260
|
+
redirect: "manual",
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
if (timedOut && isAbortError(err))
|
|
265
|
+
throw timeoutError();
|
|
266
|
+
throw requestFailure(err, messages);
|
|
267
|
+
}
|
|
268
|
+
if (!readsBody(response.status)) {
|
|
269
|
+
discardBody(response);
|
|
270
|
+
if (settings.authorization !== undefined &&
|
|
271
|
+
(response.status === 401 || response.status === 403) &&
|
|
272
|
+
hasBearerChallenge(response.headers.get("www-authenticate"))) {
|
|
273
|
+
throw new StoreCredentialRefusedError(url, response.status, messages.owner);
|
|
274
|
+
}
|
|
275
|
+
return { response, text: undefined };
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const text = await readBodyCapped(response, settings.maxResponseBytes, endpoint, deadline, (err) => readFailure(err, messages.unreadable), messages.owner);
|
|
279
|
+
return { response, text };
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
if (timedOut)
|
|
283
|
+
throw timeoutError();
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
clearTimeout(timer);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@o3co/auth-provider-foundation/testing`: what a test uses to configure
|
|
3
|
+
* this package's modules. Test code imports it; production code never does.
|
|
4
|
+
*/
|
|
5
|
+
import { FOUNDATION_MFA_FACTOR_STORE_SECTION, type FoundationMfaFactorStoreUrls } from "../mfa/section.mjs";
|
|
6
|
+
/** The Store URLs the `"http"` user adapter's builder reads (`registerBuiltinAdapters`). */
|
|
7
|
+
declare const USER_REPOSITORY_URL_KEYS: readonly ["authenticateUrl", "authenticateByTokenUrl", "linkFederatedIdentityUrl", "findSubjectByFederatedIdentityUrl", "markMfaEnrolledUrl"];
|
|
8
|
+
/** The user repository's Store URLs, as the `"http"` builder names them. */
|
|
9
|
+
export type FoundationUserRepositoryUrls = Readonly<Record<(typeof USER_REPOSITORY_URL_KEYS)[number], unknown>>;
|
|
10
|
+
/**
|
|
11
|
+
* The `foundation-mfa-factor-store` section as a configuration fragment to
|
|
12
|
+
* lay over a test's configuration: the four URLs `urls` holds — a fake
|
|
13
|
+
* Store's `urls` included, its other endpoints left behind — and `extra`
|
|
14
|
+
* as given.
|
|
15
|
+
*/
|
|
16
|
+
export declare function foundationMfaFactorStoreConfig(urls: Partial<Readonly<Record<keyof FoundationMfaFactorStoreUrls, unknown>>>, extra?: Readonly<Record<string, unknown>>): {
|
|
17
|
+
readonly [FOUNDATION_MFA_FACTOR_STORE_SECTION]: Readonly<Record<string, unknown>>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The user repository's `http` block, as the `"http"` builder reads it: the
|
|
21
|
+
* Store URLs `urls` holds — a fake Store's `urls` included, its MFA factor
|
|
22
|
+
* endpoints left behind — and `extra` as given. A test hands it to the
|
|
23
|
+
* `"http"` builder, or to `foundationMfaFactorStoreModule` as its
|
|
24
|
+
* `storeTransport`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function foundationUserRepositoryHttpConfig(urls: Partial<FoundationUserRepositoryUrls>, extra?: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=index.d.mts.map
|