@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.
Files changed (39) hide show
  1. package/README.ja.md +154 -29
  2. package/README.md +482 -45
  3. package/config/reference.conf +20 -0
  4. package/dist/endpointUrl.d.mts +21 -47
  5. package/dist/endpointUrl.d.mts.map +1 -1
  6. package/dist/endpointUrl.mjs +23 -50
  7. package/dist/index.d.mts +5 -1
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +18 -35
  10. package/dist/mfa/HttpMfaFactorStore.d.mts +55 -0
  11. package/dist/mfa/HttpMfaFactorStore.d.mts.map +1 -0
  12. package/dist/mfa/HttpMfaFactorStore.mjs +183 -0
  13. package/dist/mfa/markEnrolled.d.mts +4 -0
  14. package/dist/mfa/markEnrolled.d.mts.map +1 -0
  15. package/dist/mfa/markEnrolled.mjs +31 -0
  16. package/dist/mfa/module.d.mts +27 -0
  17. package/dist/mfa/module.d.mts.map +1 -0
  18. package/dist/mfa/module.mjs +56 -0
  19. package/dist/mfa/section.d.mts +45 -0
  20. package/dist/mfa/section.d.mts.map +1 -0
  21. package/dist/mfa/section.mjs +99 -0
  22. package/dist/mfa/storeFailure.d.mts +52 -0
  23. package/dist/mfa/storeFailure.d.mts.map +1 -0
  24. package/dist/mfa/storeFailure.mjs +107 -0
  25. package/dist/repositories/HttpUserRepository.d.mts +54 -79
  26. package/dist/repositories/HttpUserRepository.d.mts.map +1 -1
  27. package/dist/repositories/HttpUserRepository.mjs +117 -413
  28. package/dist/repositories/storeErrors.d.mts +18 -27
  29. package/dist/repositories/storeErrors.d.mts.map +1 -1
  30. package/dist/repositories/storeErrors.mjs +30 -40
  31. package/dist/repositories/wwwAuthenticate.d.mts.map +1 -1
  32. package/dist/repositories/wwwAuthenticate.mjs +12 -16
  33. package/dist/storeTransport.d.mts +97 -0
  34. package/dist/storeTransport.d.mts.map +1 -0
  35. package/dist/storeTransport.mjs +290 -0
  36. package/dist/testing/index.d.mts +28 -0
  37. package/dist/testing/index.d.mts.map +1 -0
  38. package/dist/testing/index.mjs +58 -0
  39. package/package.json +18 -7
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The module that keeps a subject's MFA factors in the Store:
3
+ * `foundation-mfa-factor-store` provides `mfaFactorStore` as an
4
+ * `HttpMfaFactorStore`, built at boot whether or not anything requires it.
5
+ *
6
+ * Guarantees: the section's four URLs are read first, so a composition that
7
+ * installs the module with any of them unset refuses the boot; the Store's
8
+ * credential, deadline and response cap are the Store transport settings the
9
+ * composition root must hand it — the user repository's HTTP settings, read
10
+ * as that repository's builder reads them — so one token goes to every Store
11
+ * endpoint, and settings absent or not a section of keys refuse the boot
12
+ * rather than send no credential. It requires no slot.
13
+ */
14
+ import { type Module } from "@o3co/auth-provider-core";
15
+ export interface FoundationMfaFactorStoreModuleOptions {
16
+ /**
17
+ * The Store transport settings: the user repository's HTTP settings as the
18
+ * configuration holds them (`repositories.user.http` in the standalone
19
+ * template, which hands them here), whose `bearerToken`,
20
+ * `timeout` and `maxResponseBytes` are the Store's, text read as numbers.
21
+ * `{}` states none: no credential is sent, and the defaults apply.
22
+ */
23
+ readonly storeTransport: unknown;
24
+ }
25
+ /** The module, over the Store transport settings `options` hands it. */
26
+ export declare function foundationMfaFactorStoreModule(options: FoundationMfaFactorStoreModuleOptions): Module;
27
+ //# sourceMappingURL=module.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.mts","sourceRoot":"","sources":["../../src/mfa/module.mts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,0BAA0B,CAAC;AASrE,MAAM,WAAW,qCAAqC;IACrD;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CACjC;AAYD,wEAAwE;AACxE,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,qCAAqC,GAC5C,MAAM,CAeR"}
@@ -0,0 +1,56 @@
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 module that keeps a subject's MFA factors in the Store:
18
+ * `foundation-mfa-factor-store` provides `mfaFactorStore` as an
19
+ * `HttpMfaFactorStore`, built at boot whether or not anything requires it.
20
+ *
21
+ * Guarantees: the section's four URLs are read first, so a composition that
22
+ * installs the module with any of them unset refuses the boot; the Store's
23
+ * credential, deadline and response cap are the Store transport settings the
24
+ * composition root must hand it — the user repository's HTTP settings, read
25
+ * as that repository's builder reads them — so one token goes to every Store
26
+ * endpoint, and settings absent or not a section of keys refuse the boot
27
+ * rather than send no credential. It requires no slot.
28
+ */
29
+ import { defineModule } from "@o3co/auth-provider-core";
30
+ import { readStoreTransportConfig } from "../storeTransport.mjs";
31
+ import { HttpMfaFactorStore } from "./HttpMfaFactorStore.mjs";
32
+ import { foundationMfaFactorStoreLifecycle, foundationMfaFactorStoreSection, readFoundationMfaFactorStoreUrls, } from "./section.mjs";
33
+ /** The settings as a section of keys, or a refusal for anything else, absent included. */
34
+ function settingsOf(value) {
35
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
36
+ throw new RangeError("foundation-mfa-factor-store: storeTransport, the Store transport settings, must be a section of keys ({} for none)");
37
+ }
38
+ return value;
39
+ }
40
+ /** The module, over the Store transport settings `options` hands it. */
41
+ export function foundationMfaFactorStoreModule(options) {
42
+ return defineModule({
43
+ name: "foundation-mfa-factor-store",
44
+ section: foundationMfaFactorStoreSection,
45
+ provides: {
46
+ mfaFactorStore: ({ section }) => {
47
+ const urls = readFoundationMfaFactorStoreUrls(section);
48
+ return new HttpMfaFactorStore({
49
+ ...urls,
50
+ ...readStoreTransportConfig(settingsOf(options?.storeTransport)),
51
+ });
52
+ },
53
+ },
54
+ lifecycle: foundationMfaFactorStoreLifecycle,
55
+ });
56
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ /** The section's name: the module's. */
3
+ export declare const FOUNDATION_MFA_FACTOR_STORE_SECTION = "foundation-mfa-factor-store";
4
+ /** The section: the four URLs, each optional here, and no other key. */
5
+ declare const schema: z.ZodObject<{
6
+ listUrl: z.ZodOptional<z.ZodString>;
7
+ createUrl: z.ZodOptional<z.ZodString>;
8
+ updateUrl: z.ZodOptional<z.ZodString>;
9
+ deleteUrl: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strict>;
11
+ /** The section as the module declares it: its schema, and the package's reference. */
12
+ export declare const foundationMfaFactorStoreSection: {
13
+ readonly schema: z.ZodObject<{
14
+ listUrl: z.ZodOptional<z.ZodString>;
15
+ createUrl: z.ZodOptional<z.ZodString>;
16
+ updateUrl: z.ZodOptional<z.ZodString>;
17
+ deleteUrl: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$strict>;
19
+ readonly reference: import("node:url").URL;
20
+ };
21
+ /**
22
+ * The lifecycle of a module that reads the section: its store is built at
23
+ * boot whether or not anything requires it, so the reader always runs.
24
+ */
25
+ export declare const foundationMfaFactorStoreLifecycle: {
26
+ readonly mfaFactorStore: {
27
+ readonly eager: true;
28
+ };
29
+ };
30
+ /** The section as its schema reads it. */
31
+ export type FoundationMfaFactorStoreSection = z.output<typeof schema>;
32
+ /** The Store's four MFA factor endpoints. */
33
+ export interface FoundationMfaFactorStoreUrls {
34
+ readonly listUrl: string;
35
+ readonly createUrl: string;
36
+ readonly updateUrl: string;
37
+ readonly deleteUrl: string;
38
+ }
39
+ /**
40
+ * The four URLs of a parsed section, or a `RangeError` naming each one
41
+ * missing by its path and its variable.
42
+ */
43
+ export declare function readFoundationMfaFactorStoreUrls(section: FoundationMfaFactorStoreSection): FoundationMfaFactorStoreUrls;
44
+ export {};
45
+ //# sourceMappingURL=section.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.d.mts","sourceRoot":"","sources":["../../src/mfa/section.mts"],"names":[],"mappings":"AAiCA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,wCAAwC;AACxC,eAAO,MAAM,mCAAmC,gCAAgC,CAAC;AA2BjF,wEAAwE;AACxE,QAAA,MAAM,MAAM;;;;;kBAeX,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,+BAA+B;;;;;;;;CAGK,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,iCAAiC;;;;CAEpC,CAAC;AAEX,0CAA0C;AAC1C,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC;AAEtE,6CAA6C;AAC7C,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAC/C,OAAO,EAAE,+BAA+B,GACtC,4BAA4B,CAe9B"}
@@ -0,0 +1,99 @@
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 configuration section of the module that keeps a subject's MFA factors
18
+ * in the Store: `foundation-mfa-factor-store`, named after that module, its
19
+ * four factor endpoints bound in the package's `config/reference.conf`
20
+ * without a default.
21
+ *
22
+ * Guarantees: the schema refuses an unknown key and a URL that is not https
23
+ * or loopback http, naming the key in printable characters and quoting no
24
+ * value; a URL left out passes the schema — the reference binds each one to
25
+ * a variable that may be unset — and {@link readFoundationMfaFactorStoreUrls}
26
+ * refuses it. A module that reads the section provides its store eagerly
27
+ * ({@link foundationMfaFactorStoreLifecycle}) and calls the reader first, so
28
+ * a composition that installs it with any URL missing refuses the boot,
29
+ * whether or not anything requires the store.
30
+ */
31
+ import { auditErrorText } from "@o3co/auth-provider-core";
32
+ import { z } from "zod";
33
+ import { checkSecureEndpoint, describeEndpointRejection } from "../endpointUrl.mjs";
34
+ /** The section's name: the module's. */
35
+ export const FOUNDATION_MFA_FACTOR_STORE_SECTION = "foundation-mfa-factor-store";
36
+ /** The four endpoints, in the order a refusal names them. */
37
+ const URL_KEYS = ["listUrl", "createUrl", "updateUrl", "deleteUrl"];
38
+ /** Each key's variable, named after its path. */
39
+ const VARIABLES = {
40
+ listUrl: "FOUNDATION_MFA_FACTOR_STORE_LIST_URL",
41
+ createUrl: "FOUNDATION_MFA_FACTOR_STORE_CREATE_URL",
42
+ updateUrl: "FOUNDATION_MFA_FACTOR_STORE_UPDATE_URL",
43
+ deleteUrl: "FOUNDATION_MFA_FACTOR_STORE_DELETE_URL",
44
+ };
45
+ const SECTION_MISSING = "is missing: layer @o3co/auth-provider-foundation/reference.conf beneath the composition's configuration";
46
+ /** A Store endpoint, when present: the rule the user repository holds its URLs to. */
47
+ const storeUrl = z
48
+ .string({ error: "must be a string" })
49
+ .superRefine((value, ctx) => {
50
+ const rejection = checkSecureEndpoint(value);
51
+ if (rejection !== null) {
52
+ ctx.addIssue({ code: "custom", message: describeEndpointRejection(rejection) });
53
+ }
54
+ })
55
+ .optional();
56
+ /** The section: the four URLs, each optional here, and no other key. */
57
+ const schema = z.strictObject({
58
+ listUrl: storeUrl,
59
+ createUrl: storeUrl,
60
+ updateUrl: storeUrl,
61
+ deleteUrl: storeUrl,
62
+ }, {
63
+ error: (issue) => {
64
+ if (issue.code === "unrecognized_keys") {
65
+ return `has a key it does not know: ${issue.keys.map((key) => `"${auditErrorText(key)}"`).join(", ")}`;
66
+ }
67
+ return issue.input === undefined ? SECTION_MISSING : "must be a section of keys";
68
+ },
69
+ });
70
+ /** The section as the module declares it: its schema, and the package's reference. */
71
+ export const foundationMfaFactorStoreSection = {
72
+ schema,
73
+ reference: new URL("../../config/reference.conf", import.meta.url),
74
+ };
75
+ /**
76
+ * The lifecycle of a module that reads the section: its store is built at
77
+ * boot whether or not anything requires it, so the reader always runs.
78
+ */
79
+ export const foundationMfaFactorStoreLifecycle = {
80
+ mfaFactorStore: { eager: true },
81
+ };
82
+ /**
83
+ * The four URLs of a parsed section, or a `RangeError` naming each one
84
+ * missing by its path and its variable.
85
+ */
86
+ export function readFoundationMfaFactorStoreUrls(section) {
87
+ const missing = URL_KEYS.filter((key) => section[key] === undefined);
88
+ if (missing.length > 0) {
89
+ throw new RangeError(`${missing
90
+ .map((key) => `${FOUNDATION_MFA_FACTOR_STORE_SECTION}.${key} (${VARIABLES[key]})`)
91
+ .join(", ")} must be set: the Store keeps the MFA factors only with all four endpoints`);
92
+ }
93
+ return {
94
+ listUrl: section.listUrl,
95
+ createUrl: section.createUrl,
96
+ updateUrl: section.updateUrl,
97
+ deleteUrl: section.deleteUrl,
98
+ };
99
+ }
@@ -0,0 +1,52 @@
1
+ import type { StoreRequestMessages } from "../storeTransport.mjs";
2
+ /** The Store's MFA endpoints, by what the provider asks of each. */
3
+ export type MfaStoreOperation = "list" | "create" | "update" | "delete" | "markMfaEnrolled";
4
+ /** Why an answer is not one the contract gives the operation. */
5
+ export type MfaStoreFailure =
6
+ /** A status the contract does not give the operation. */
7
+ "unexpected_status"
8
+ /** `markMfaEnrolled` answered `404`: the Store holds no such subject. */
9
+ | "unknown_subject"
10
+ /** A `2xx` whose body is not the contract's. */
11
+ | "malformed_answer"
12
+ /** A list holding a record the provider cannot read: the list is refused, never read as none. */
13
+ | "unreadable_record"
14
+ /** An update answered a version other than the expected one plus one. */
15
+ | "version_skipped";
16
+ /** An MFA endpoint answered outside the contract. `name`, `reason`, `operation` and `storeStatus` are part of the contract. */
17
+ export declare class MfaStoreError extends Error {
18
+ readonly reason: MfaStoreFailure;
19
+ readonly operation: MfaStoreOperation;
20
+ /** The Store's status, for a status the contract does not give the operation. */
21
+ readonly storeStatus: number | undefined;
22
+ constructor(message: string, reason: MfaStoreFailure, operation: MfaStoreOperation, storeStatus?: number);
23
+ }
24
+ /**
25
+ * What a transport failure of `operation` at `url` says, for the client
26
+ * `owner` that sends it: one wording for every client of these endpoints,
27
+ * naming the endpoint by origin and path.
28
+ */
29
+ export declare function mfaStoreRequestMessages(owner: string, operation: MfaStoreOperation, url: string): StoreRequestMessages;
30
+ /**
31
+ * `response`'s status as an error, when the contract does not give it to
32
+ * `operation`: `unknown_subject` for `markMfaEnrolled`'s `404`,
33
+ * `unexpected_status` for any other. Reads the status alone and releases the
34
+ * body without awaiting it, which a Store could otherwise hold open.
35
+ */
36
+ export declare function mfaStoreStatusError(operation: MfaStoreOperation, url: string, response: Response): MfaStoreError;
37
+ /** A `2xx` from `operation` whose body is not the contract's. */
38
+ export declare function mfaStoreMalformedAnswer(operation: MfaStoreOperation, url: string): MfaStoreError;
39
+ /** A list holding a record the provider cannot read. */
40
+ export declare function mfaStoreUnreadableRecord(url: string): MfaStoreError;
41
+ /**
42
+ * An update of `(subject, id)` at `expectedVersion` answered another version
43
+ * than `expectedVersion + 1`. The subject and the factor id lead the
44
+ * message, so a log line that cuts it (`loggableError`, 256 characters) still
45
+ * names both.
46
+ */
47
+ export declare function mfaStoreVersionSkipped(url: string, update: {
48
+ readonly subject: string;
49
+ readonly id: string;
50
+ readonly expectedVersion: number;
51
+ }): MfaStoreError;
52
+ //# sourceMappingURL=storeFailure.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storeFailure.d.mts","sourceRoot":"","sources":["../../src/mfa/storeFailure.mts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,iBAAiB,CAAC;AAE5F,iEAAiE;AACjE,MAAM,MAAM,eAAe;AAC1B,yDAAyD;AACvD,mBAAmB;AACrB,yEAAyE;GACvE,iBAAiB;AACnB,gDAAgD;GAC9C,kBAAkB;AACpB,iGAAiG;GAC/F,mBAAmB;AACrB,yEAAyE;GACvE,iBAAiB,CAAC;AAErB,+HAA+H;AAC/H,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;gBAGxC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,iBAAiB,EAC5B,WAAW,CAAC,EAAE,MAAM;CAQrB;AAKD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,MAAM,GACT,oBAAoB,CAStB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,GAChB,aAAa,CAiBf;AAED,iEAAiE;AACjE,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAMhG;AAED,wDAAwD;AACxD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAMnE;AAgBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACzF,aAAa,CAOf"}
@@ -0,0 +1,107 @@
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
+ * What the Store adapter throws when an MFA endpoint answers outside the
18
+ * contract (README, "The Store's MFA endpoints"). Transport failures, a
19
+ * deadline and a refused credential are `StoreTransportError`,
20
+ * `TimeoutError` and `StoreCredentialRefusedError`, as for the user
21
+ * repository.
22
+ *
23
+ * Guarantees: an error is built from an allowlist — the operation, the
24
+ * endpoint by origin and path, the Store's status as a number, and for an
25
+ * unexpected version the subject and factor id through `auditErrorText`, at
26
+ * most 64 characters each and ahead of the rest —
27
+ * never from a body, a status text or a header the Store sent, and a body is
28
+ * released unread. No `status`, `statusCode`, `expose` or `cause`: an HTTP
29
+ * layer reading one would answer with the Store's status, and the client
30
+ * gets the provider's generic answer for an outage, whatever went wrong.
31
+ */
32
+ import { auditErrorText } from "@o3co/auth-provider-core";
33
+ import { endpointForMessage } from "../endpointUrl.mjs";
34
+ /** An MFA endpoint answered outside the contract. `name`, `reason`, `operation` and `storeStatus` are part of the contract. */
35
+ export class MfaStoreError extends Error {
36
+ reason;
37
+ operation;
38
+ /** The Store's status, for a status the contract does not give the operation. */
39
+ storeStatus;
40
+ constructor(message, reason, operation, storeStatus) {
41
+ super(message);
42
+ this.name = "MfaStoreError";
43
+ this.reason = reason;
44
+ this.operation = operation;
45
+ this.storeStatus = storeStatus;
46
+ }
47
+ }
48
+ const endpointOf = (operation, url) => `the Store's MFA ${operation} endpoint at ${endpointForMessage(url)}`;
49
+ /**
50
+ * What a transport failure of `operation` at `url` says, for the client
51
+ * `owner` that sends it: one wording for every client of these endpoints,
52
+ * naming the endpoint by origin and path.
53
+ */
54
+ export function mfaStoreRequestMessages(owner, operation, url) {
55
+ const endpoint = endpointOf(operation, url);
56
+ return {
57
+ owner,
58
+ unreachable: `${owner}: ${endpoint} could not be reached`,
59
+ closed: `${owner}: the connection to ${endpoint} closed before a complete response arrived`,
60
+ malformed: `${owner}: ${endpoint} answered with a malformed HTTP response`,
61
+ unreadable: `${owner}: the answer of ${endpoint} could not be read`,
62
+ };
63
+ }
64
+ /**
65
+ * `response`'s status as an error, when the contract does not give it to
66
+ * `operation`: `unknown_subject` for `markMfaEnrolled`'s `404`,
67
+ * `unexpected_status` for any other. Reads the status alone and releases the
68
+ * body without awaiting it, which a Store could otherwise hold open.
69
+ */
70
+ export function mfaStoreStatusError(operation, url, response) {
71
+ const status = response.status;
72
+ response.body?.cancel().catch(() => { });
73
+ if (operation === "markMfaEnrolled" && status === 404) {
74
+ return new MfaStoreError(`${endpointOf(operation, url)} answered HTTP 404: it holds no such subject`, "unknown_subject", operation, status);
75
+ }
76
+ return new MfaStoreError(`${endpointOf(operation, url)} answered HTTP ${status}`, "unexpected_status", operation, status);
77
+ }
78
+ /** A `2xx` from `operation` whose body is not the contract's. */
79
+ export function mfaStoreMalformedAnswer(operation, url) {
80
+ return new MfaStoreError(`${endpointOf(operation, url)} answered a body that is not the contract's`, "malformed_answer", operation);
81
+ }
82
+ /** A list holding a record the provider cannot read. */
83
+ export function mfaStoreUnreadableRecord(url) {
84
+ return new MfaStoreError(`${endpointOf("list", url)} answered a factor record the provider cannot read: the subject's factors are unavailable, never read as none`, "unreadable_record", "list");
85
+ }
86
+ /** The most characters of a subject or a factor id an error names. */
87
+ const IDENTIFIER_MAX_LENGTH = 64;
88
+ /**
89
+ * `text` as an error names it: `auditErrorText`'s printable ASCII, cut at
90
+ * {@link IDENTIFIER_MAX_LENGTH} with `...`.
91
+ */
92
+ const identifier = (text) => {
93
+ const safe = auditErrorText(text);
94
+ return safe.length <= IDENTIFIER_MAX_LENGTH
95
+ ? safe
96
+ : `${safe.slice(0, IDENTIFIER_MAX_LENGTH - 3)}...`;
97
+ };
98
+ /**
99
+ * An update of `(subject, id)` at `expectedVersion` answered another version
100
+ * than `expectedVersion + 1`. The subject and the factor id lead the
101
+ * message, so a log line that cuts it (`loggableError`, 256 characters) still
102
+ * names both.
103
+ */
104
+ export function mfaStoreVersionSkipped(url, update) {
105
+ return new MfaStoreError(`subject ${identifier(update.subject)}, factor ${identifier(update.id)}: ` +
106
+ `${endpointOf("update", url)} answered a version other than ${update.expectedVersion + 1}`, "version_skipped", "update");
107
+ }
@@ -1,20 +1,14 @@
1
- import { type FederatedIdentityLink, type FederatedIdentityLookup, type FederatedIdentityLookupResult, type FederatedIdentityRegistration, type LinkFederatedIdentityResult, type User, type UserRepository } from "@o3co/auth-provider-core";
1
+ import type { FederatedIdentityLink, FederatedIdentityLookup, FederatedIdentityLookupResult, FederatedIdentityRegistration, LinkFederatedIdentityResult, User, UserRepository } from "@o3co/auth-provider-core";
2
+ import { DEFAULT_MAX_RESPONSE_BYTES } from "../storeTransport.mjs";
3
+ export { DEFAULT_MAX_RESPONSE_BYTES };
2
4
  /**
3
- * Default ceiling on an upstream response body, in bytes.
4
- *
5
- * A `User` record is a few hundred bytes; 1 MiB is generous for one carrying
6
- * custom claims and small enough that a hostile or broken Store cannot walk the
7
- * process out of memory one login at a time (#285).
8
- */
9
- export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
10
- /**
11
- * What the Store declares it can place (#613): identities issued under one
12
- * registration — the federation's name, the issuer and the client, exactly as
13
- * a federation-grant connection is configured — given at least these claims
14
- * from the verified id_token. The synchronous probe D7 check 5 asks at boot
15
- * cannot reach the Store, so the operator relays the Store's own claim here;
16
- * boot then holds every connection to it. `requiredClaims = []` is a strategy
17
- * on the registration and the `sub` alone.
5
+ * What the Store declares it can place: identities issued under one
6
+ * registration (the federation's name, the issuer and the client, exactly as
7
+ * a federation-grant connection is configured), given at least these claims
8
+ * from the verified id_token. The boot probe is synchronous and cannot reach
9
+ * the Store, so the operator relays the Store's own claim here; boot then
10
+ * holds every connection to it. `requiredClaims = []` is a strategy on the
11
+ * registration and the `sub` alone.
18
12
  */
19
13
  export interface FederatedIdentityLookupCoverage {
20
14
  readonly provider: string;
@@ -23,41 +17,19 @@ export interface FederatedIdentityLookupCoverage {
23
17
  readonly requiredClaims: readonly string[];
24
18
  }
25
19
  /**
26
- * `UserRepository` backed by "the Store" — the upstream user service defined
20
+ * `UserRepository` backed by "the Store", the upstream user service defined
27
21
  * on core's `User` doc (`@o3co/auth-provider-core`, `src/repositories/types.mts`).
22
+ * See README, The wire contract and Constructor validation.
28
23
  *
29
24
  * Every endpoint receives a plaintext credential or a verified identity, so
30
- * each is validated at **construction**: a deployment configured with an
31
- * `http://` Store URL fails at boot rather than leaking the first user's
32
- * password (#285). `http://` is accepted for loopback hosts only — see
33
- * `src/endpointUrl.mts` for the carve-out and its rationale.
34
- *
35
- * No request follows a redirect, so a URL that passed that check is the only
36
- * place its body is ever sent and the only one whose answer is taken: a `3xx`
37
- * from the Store is an upstream failure, thrown like any other unexpected
38
- * status, and its `Location` is never contacted.
39
- *
40
- * With `bearerToken` configured, every request — authentication, linking and
41
- * the identity lookup alike — carries `Authorization: Bearer <token>`, so the
42
- * Store can refuse a caller that is not this deployment; without it, no
43
- * request carries an `Authorization` header. The Store says it refused THIS
44
- * deployment by answering `401` or `403` with a `Bearer` challenge (RFC 6750
45
- * §3), and that answer, while a token was sent, throws a
46
- * {@link StoreCredentialRefusedError} on every request — never "no such user"
47
- * or a refused link, which is what either status without the challenge still
48
- * means.
49
- *
50
- * A transport's own error is never thrown: undici's parser errors quote the
51
- * bytes they rejected, and a peer that reflects the request puts the
52
- * `Authorization` header — or a password — there. A request that cannot be
53
- * made, or an answer that cannot be read, throws a `StoreTransportError`
54
- * (`src/repositories/storeErrors.mts`): a fixed message naming the endpoint
55
- * and what failed, at most an allowlisted transport code, no cause.
56
- *
57
- * Every message names an endpoint by its origin and path alone
58
- * (`endpointForMessage`, `src/endpointUrl.mts`): a Store URL may carry a
59
- * query string, and a query may carry a credential, while every caller logs
60
- * what this throws.
25
+ * each is validated at **construction** (`src/endpointUrl.mts`), and no
26
+ * request follows a redirect. With `bearerToken`, every request carries
27
+ * `Authorization: Bearer <token>`, and a `401` or `403` with a `Bearer`
28
+ * challenge throws a `StoreCredentialRefusedError`. A transport's own
29
+ * error is never thrown, because it may quote the request: a failure throws a
30
+ * `StoreTransportError` with a fixed message, at most an allowlisted transport
31
+ * code, and no cause. Every message names an endpoint by origin and path
32
+ * alone.
61
33
  */
62
34
  export declare class HttpUserRepository implements UserRepository {
63
35
  #private;
@@ -69,30 +41,39 @@ export declare class HttpUserRepository implements UserRepository {
69
41
  private findSubjectByFederatedIdentityUrl?;
70
42
  private readonly coverage;
71
43
  /**
72
- * #482 — see {@link UserRepository.linkFederatedIdentity}. Present only when
44
+ * See {@link UserRepository.linkFederatedIdentity}. Present only when
73
45
  * `linkFederatedIdentityUrl` is configured, which is how the federation routes
74
46
  * know to refuse `?link=1` up front instead of at the callback.
75
47
  */
76
48
  readonly linkFederatedIdentity?: (userId: string, identity: FederatedIdentityLink) => Promise<LinkFederatedIdentityResult>;
77
49
  /**
78
- * #613 — see {@link UserRepository.supportsFederatedIdentityLookup}. Present
50
+ * See {@link UserRepository.supportsFederatedIdentityLookup}. Present
79
51
  * together with the lookup, only when `findSubjectByFederatedIdentityUrl` is
80
52
  * configured: absent, a deployment that requires the lookup is refused at
81
53
  * boot by the method's name, which is the message that says what to set.
82
54
  * Answers from the declaration alone — the Store is not asked at boot.
83
55
  */
84
56
  readonly supportsFederatedIdentityLookup?: (registration: FederatedIdentityRegistration, identityClaims: readonly string[]) => boolean;
85
- /** #613 — see {@link UserRepository.findSubjectByFederatedIdentity}. Present with the probe. */
57
+ /** See {@link UserRepository.findSubjectByFederatedIdentity}. Present with the probe. */
86
58
  readonly findSubjectByFederatedIdentity?: (identity: FederatedIdentityLookup) => Promise<FederatedIdentityLookupResult>;
87
- constructor({ authenticateUrl, authenticateByTokenUrl, linkFederatedIdentityUrl, findSubjectByFederatedIdentityUrl, federatedIdentityLookupCoverage, bearerToken, timeout, maxResponseBytes, }: {
59
+ /**
60
+ * See {@link UserRepository.markMfaEnrolled}. Present only when
61
+ * `markMfaEnrolledUrl` is configured, which is how
62
+ * `supportsMfaEnrollmentWitness` detects it; written as the Store's MFA
63
+ * endpoints are (`src/mfa/markEnrolled.mts`).
64
+ */
65
+ readonly markMfaEnrolled?: (subject: string, enrolled: boolean) => Promise<void>;
66
+ constructor({ authenticateUrl, authenticateByTokenUrl, linkFederatedIdentityUrl, findSubjectByFederatedIdentityUrl, federatedIdentityLookupCoverage, markMfaEnrolledUrl, bearerToken, timeout, maxResponseBytes, }: {
88
67
  authenticateUrl: string;
89
68
  authenticateByTokenUrl: string;
90
- /** #482: optional; the Store's link endpoint. Same https rule as the other two. */
69
+ /** Optional; the Store's link endpoint. Same https rule as the other two. */
91
70
  linkFederatedIdentityUrl?: string;
92
- /** #613: optional; the Store's identity lookup. Same https rule; carries a verified identity. */
71
+ /** Optional; the Store's identity lookup. Same https rule; carries a verified identity. */
93
72
  findSubjectByFederatedIdentityUrl?: string;
94
- /** #613: which registrations the Store's lookup covers. Needs the URL; `[]` covers none. */
73
+ /** Which registrations the Store's lookup covers. Needs the URL; `[]` covers none. */
95
74
  federatedIdentityLookupCoverage?: readonly FederatedIdentityLookupCoverage[];
75
+ /** Optional; the Store's MFA enrollment witness endpoint. Same https rule. */
76
+ markMfaEnrolledUrl?: string;
96
77
  /**
97
78
  * Optional; sent to the Store on every request as `Authorization: Bearer
98
79
  * <token>`. A bare RFC 6750 token (no scheme) of at least
@@ -102,24 +83,20 @@ export declare class HttpUserRepository implements UserRepository {
102
83
  timeout: number;
103
84
  maxResponseBytes?: number;
104
85
  });
105
- /** What every request carries: the body's type and, when configured, the credential. */
106
- private headers;
107
86
  /**
108
- * Throws {@link StoreCredentialRefusedError} when a non-`2xx` answer is the
109
- * Store refusing this deployment's credential: a `401` or `403` with a
110
- * `Bearer` challenge, to a request that carried the token. Read before any
111
- * other reading of the status — without it a token the Store does not
112
- * accept is every login "no such user" and every link "refused", with
113
- * nothing logged. Without a token sent, a challenge is not about one, and a
114
- * Store whose stack challenges every refusal keeps the wire meaning it has
115
- * always had.
87
+ * What every request is sent with: the credential, when configured, the
88
+ * deadline and the cap. A `401` or `403` with a `Bearer` challenge to a
89
+ * request that carried the credential throws `StoreCredentialRefusedError`
90
+ * before any other reading of the status (`postToStore`): otherwise a
91
+ * token the Store does not accept is every login "no such user" and every
92
+ * link "refused", with nothing logged.
116
93
  */
117
- private assertCredentialAccepted;
94
+ private settings;
118
95
  authenticate(username: string, password: string): Promise<User | null>;
119
96
  authenticateByToken(token: string): Promise<User | null>;
120
97
  /**
121
- * POST `{ userId, provider, sub, token, claims }` to the Store's link endpoint
122
- * (#482). A `2xx` `User` is the linked account; `401` / `403` is the Store's
98
+ * POST `{ userId, provider, sub, token, claims }` to the Store's link
99
+ * endpoint. A `2xx` `User` is the linked account; `401` / `403` is the Store's
123
100
  * refusal (policy — an unverified or relay address, one identity per
124
101
  * provider, …); `409` says the identity is already someone else's.
125
102
  */
@@ -128,7 +105,7 @@ export declare class HttpUserRepository implements UserRepository {
128
105
  private declared;
129
106
  /**
130
107
  * POST `{ provider, issuer, clientId, sub, claims }` to the Store's lookup
131
- * (#613) and read one of the port's three answers. Answered locally, with
108
+ * and read one of the port's three answers. Answered locally, with
132
109
  * no request, where the declaration already decides: a registration nobody
133
110
  * declared is `registration_not_covered`, and a declared one arriving
134
111
  * without a claim its strategy needs is `identity_not_resolvable`. Every
@@ -138,16 +115,14 @@ export declare class HttpUserRepository implements UserRepository {
138
115
  private lookupViaHttp;
139
116
  /**
140
117
  * The lookup's transport: the same deadline, cap and abort as {@link post},
141
- * and like it never follows a redirect — the body carries a verified
142
- * identity, and a `Location` is not a configured endpoint. None of
143
- * {@link post}'s readings, though. A `401`/`403` is not "nobody", a `409` is
144
- * not a conflict, a `404` is not an absence: only a `2xx` carrying one of the
145
- * three answers is an answer, and everything else throws — as an outage,
146
- * which is what a lookup that could not be made is — a `401` or `403` with a
147
- * `Bearer` challenge, to a request that carried the token, as the refused
148
- * credential it is. What is thrown names the endpoint by origin and path — and, for an answer
149
- * with a status, the status; for a transport failure, at most its code — and
150
- * never the body, the identity, a status text or an underlying cause.
118
+ * and like it never follows a redirect (the body carries a verified identity,
119
+ * and a `Location` is not a configured endpoint). None of {@link post}'s
120
+ * readings: only a `2xx` carrying one of the three answers is an answer.
121
+ * Everything else throws as an outage, except a `401` or `403` with a
122
+ * `Bearer` challenge to a request that carried the token, which throws as the
123
+ * refused credential it is. What is thrown names the endpoint by origin and
124
+ * path, plus the status for an answer or at most the code for a transport
125
+ * failure, and never the body, the identity, a status text or a cause.
151
126
  */
152
127
  private postLookup;
153
128
  private post;
@@ -1 +1 @@
1
- {"version":3,"file":"HttpUserRepository.d.mts","sourceRoot":"","sources":["../../src/repositories/HttpUserRepository.mts"],"names":[],"mappings":"AAgBA,OAAO,EAEN,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAGhC,KAAK,IAAI,EACT,KAAK,cAAc,EACnB,MAAM,0BAA0B,CAAC;AAQlC;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,QAAc,CAAC;AA8BtD;;;;;;;;GAQG;AACH,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AA4RD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,kBAAmB,YAAW,cAAc;;IAOxD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,wBAAwB,CAAC,CAAS;IAC1C,OAAO,CAAC,iCAAiC,CAAC,CAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6C;IACtE;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,qBAAqB,KAC3B,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,+BAA+B,CAAC,EAAE,CAC1C,YAAY,EAAE,6BAA6B,EAC3C,cAAc,EAAE,SAAS,MAAM,EAAE,KAC7B,OAAO,CAAC;IACb,gGAAgG;IAChG,QAAQ,CAAC,8BAA8B,CAAC,EAAE,CACzC,QAAQ,EAAE,uBAAuB,KAC7B,OAAO,CAAC,6BAA6B,CAAC,CAAC;gBAEhC,EACX,eAAe,EACf,sBAAsB,EACtB,wBAAwB,EACxB,iCAAiC,EACjC,+BAA+B,EAC/B,WAAW,EACX,OAAO,EACP,gBAA6C,GAC7C,EAAE;QACF,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,mFAAmF;QACnF,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,iGAAiG;QACjG,iCAAiC,CAAC,EAAE,MAAM,CAAC;QAC3C,4FAA4F;QAC5F,+BAA+B,CAAC,EAAE,SAAS,+BAA+B,EAAE,CAAC;QAC7E;;;;WAIG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC1B;IAmDD,wFAAwF;IACxF,OAAO,CAAC,OAAO;IAMf;;;;;;;;;OASG;IACH,OAAO,CAAC,wBAAwB;IAU1B,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAKtE,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAI9D;;;;;OAKG;YACW,WAAW;IAWzB,wFAAwF;IACxF,OAAO,CAAC,QAAQ;IAWhB;;;;;;;;OAQG;YACW,aAAa;IAoB3B;;;;;;;;;;;;OAYG;YACW,UAAU;YA8EV,IAAI;CAiHlB"}
1
+ {"version":3,"file":"HttpUserRepository.d.mts","sourceRoot":"","sources":["../../src/repositories/HttpUserRepository.mts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACX,qBAAqB,EACrB,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,IAAI,EACJ,cAAc,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAIN,0BAA0B,EAG1B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,0BAA0B,EAAE,CAAC;AA6BtC;;;;;;;;GAQG;AACH,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAwHD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,kBAAmB,YAAW,cAAc;;IAOxD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,wBAAwB,CAAC,CAAS;IAC1C,OAAO,CAAC,iCAAiC,CAAC,CAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6C;IACtE;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,qBAAqB,KAC3B,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,+BAA+B,CAAC,EAAE,CAC1C,YAAY,EAAE,6BAA6B,EAC3C,cAAc,EAAE,SAAS,MAAM,EAAE,KAC7B,OAAO,CAAC;IACb,yFAAyF;IACzF,QAAQ,CAAC,8BAA8B,CAAC,EAAE,CACzC,QAAQ,EAAE,uBAAuB,KAC7B,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;gBAErE,EACX,eAAe,EACf,sBAAsB,EACtB,wBAAwB,EACxB,iCAAiC,EACjC,+BAA+B,EAC/B,kBAAkB,EAClB,WAAW,EACX,OAAO,EACP,gBAA6C,GAC7C,EAAE;QACF,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,6EAA6E;QAC7E,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,2FAA2F;QAC3F,iCAAiC,CAAC,EAAE,MAAM,CAAC;QAC3C,sFAAsF;QACtF,+BAA+B,CAAC,EAAE,SAAS,+BAA+B,EAAE,CAAC;QAC7E,8EAA8E;QAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B;;;;WAIG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC1B;IA8CD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ;IAQV,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAKtE,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAI9D;;;;;OAKG;YACW,WAAW;IAWzB,wFAAwF;IACxF,OAAO,CAAC,QAAQ;IAWhB;;;;;;;;OAQG;YACW,aAAa;IAoB3B;;;;;;;;;;OAUG;YACW,UAAU;YAqCV,IAAI;CAgDlB"}