@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
|
@@ -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 {
|
|
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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
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"
|
|
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
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
57
|
+
/** See {@link UserRepository.findSubjectByFederatedIdentity}. Present with the probe. */
|
|
86
58
|
readonly findSubjectByFederatedIdentity?: (identity: FederatedIdentityLookup) => Promise<FederatedIdentityLookupResult>;
|
|
87
|
-
|
|
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
|
-
/**
|
|
69
|
+
/** Optional; the Store's link endpoint. Same https rule as the other two. */
|
|
91
70
|
linkFederatedIdentityUrl?: string;
|
|
92
|
-
/**
|
|
71
|
+
/** Optional; the Store's identity lookup. Same https rule; carries a verified identity. */
|
|
93
72
|
findSubjectByFederatedIdentityUrl?: string;
|
|
94
|
-
/**
|
|
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
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* other reading of the status
|
|
112
|
-
* accept is every login "no such user" and every
|
|
113
|
-
*
|
|
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
|
|
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
|
|
122
|
-
*
|
|
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
|
-
*
|
|
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
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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,
|
|
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"}
|