@yielded/auth-persistence 0.1.0-beta.23 → 0.1.0-beta.25
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.md +11 -0
- package/dist/Adapter.d.mts +12 -6
- package/dist/Adapter.mjs +8 -3
- package/dist/OAuthPersistence.d.mts +87 -0
- package/dist/OAuthPersistence.mjs +125 -0
- package/dist/OAuthPersistence.mjs.map +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/internal/configuration.d.mts +1 -1
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-kernel.d.mts +1 -1
- package/dist/internal/email-kernel.mjs +1 -1
- package/dist/internal/email-kernel.mjs.map +1 -1
- package/dist/internal/models/oauth-connected-model.d.mts +294 -0
- package/dist/internal/models/oauth-connected-model.mjs +22 -0
- package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
- package/dist/internal/models/oauth-model.d.mts +467 -0
- package/dist/internal/models/oauth-model.mjs +49 -0
- package/dist/internal/models/oauth-model.mjs.map +1 -0
- package/dist/internal/models/password-model.d.mts +6 -48
- package/dist/internal/models/password-model.mjs +0 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +4 -0
- package/dist/internal/native-sql-table.mjs +7 -0
- package/dist/internal/native-sql-table.mjs.map +1 -1
- package/dist/internal/oauth/accounts.d.mts +8 -0
- package/dist/internal/oauth/accounts.mjs +379 -0
- package/dist/internal/oauth/accounts.mjs.map +1 -0
- package/dist/internal/oauth/connected-access.d.mts +9 -0
- package/dist/internal/oauth/connected-access.mjs +229 -0
- package/dist/internal/oauth/connected-access.mjs.map +1 -0
- package/dist/internal/oauth/connected-collection.d.mts +7 -0
- package/dist/internal/oauth/connected-collection.mjs +214 -0
- package/dist/internal/oauth/connected-collection.mjs.map +1 -0
- package/dist/internal/oauth/connected-custody.d.mts +6 -0
- package/dist/internal/oauth/connected-custody.mjs +165 -0
- package/dist/internal/oauth/connected-custody.mjs.map +1 -0
- package/dist/internal/oauth/connected-flow.d.mts +7 -0
- package/dist/internal/oauth/connected-flow.mjs +324 -0
- package/dist/internal/oauth/connected-flow.mjs.map +1 -0
- package/dist/internal/oauth/connected-input.mjs +122 -0
- package/dist/internal/oauth/connected-input.mjs.map +1 -0
- package/dist/internal/oauth/connected-keys.d.mts +2 -0
- package/dist/internal/oauth/connected-keys.mjs +22 -0
- package/dist/internal/oauth/connected-keys.mjs.map +1 -0
- package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
- package/dist/internal/oauth/connected-maintenance.mjs +226 -0
- package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
- package/dist/internal/oauth/connected-management.d.mts +9 -0
- package/dist/internal/oauth/connected-management.mjs +243 -0
- package/dist/internal/oauth/connected-management.mjs.map +1 -0
- package/dist/internal/oauth/connected-reference.mjs +63 -0
- package/dist/internal/oauth/connected-reference.mjs.map +1 -0
- package/dist/internal/oauth/connected-settlement.d.mts +8 -0
- package/dist/internal/oauth/connected-settlement.mjs +141 -0
- package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
- package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
- package/dist/internal/oauth/connected-sign-in.mjs +281 -0
- package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
- package/dist/internal/oauth/connected-state.d.mts +14 -0
- package/dist/internal/oauth/connected-state.mjs +469 -0
- package/dist/internal/oauth/connected-state.mjs.map +1 -0
- package/dist/internal/oauth/connected-target.d.mts +21 -0
- package/dist/internal/oauth/connected-target.mjs +117 -0
- package/dist/internal/oauth/connected-target.mjs.map +1 -0
- package/dist/internal/oauth/flow.d.mts +5 -0
- package/dist/internal/oauth/flow.mjs +279 -0
- package/dist/internal/oauth/flow.mjs.map +1 -0
- package/dist/internal/oauth/input.mjs +127 -0
- package/dist/internal/oauth/input.mjs.map +1 -0
- package/dist/internal/oauth/owner.d.mts +10 -0
- package/dist/internal/oauth/owner.mjs +10 -0
- package/dist/internal/oauth/owner.mjs.map +1 -0
- package/dist/internal/oauth/query-compiler.d.mts +7 -0
- package/dist/internal/oauth/query-compiler.mjs +9 -0
- package/dist/internal/oauth/query-compiler.mjs.map +1 -0
- package/dist/internal/oauth/registration.d.mts +7 -0
- package/dist/internal/oauth/registration.mjs +393 -0
- package/dist/internal/oauth/registration.mjs.map +1 -0
- package/dist/internal/oauth/state.mjs +107 -0
- package/dist/internal/oauth/state.mjs.map +1 -0
- package/dist/internal/oauth/target.d.mts +31 -0
- package/dist/internal/oauth/target.mjs +273 -0
- package/dist/internal/oauth/target.mjs.map +1 -0
- package/dist/internal/oauth-kernel.d.mts +1423 -0
- package/dist/internal/oauth-kernel.mjs +187 -0
- package/dist/internal/oauth-kernel.mjs.map +1 -0
- package/dist/internal/oauth-proxy.d.mts +31 -0
- package/dist/internal/oauth-proxy.mjs +129 -0
- package/dist/internal/oauth-proxy.mjs.map +1 -0
- package/dist/internal/passkey/write-state.mjs +9 -1
- package/dist/internal/passkey/write-state.mjs.map +1 -1
- package/dist/internal/passkey-kernel.d.mts +9 -9
- package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
- package/dist/internal/password-credentials.mjs +121 -0
- package/dist/internal/password-credentials.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +8 -24
- package/dist/internal/password-kernel.mjs +5 -43
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +1 -1
- package/dist/internal/persistence.mjs +2 -2
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/query-operations.d.mts +2 -0
- package/dist/internal/session-kernel.d.mts +2 -2
- package/dist/internal/session-kernel.mjs +74 -18
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
- package/dist/internal/sql-oauth-connected-model.mjs +7 -0
- package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
- package/dist/internal/sql-oauth-model.d.mts +60 -0
- package/dist/internal/sql-oauth-model.mjs +10 -0
- package/dist/internal/sql-oauth-model.mjs.map +1 -0
- package/dist/internal/sql-query.d.mts +12 -0
- package/dist/internal/sql-query.mjs +15 -18
- package/dist/internal/sql-query.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +54 -0
- package/dist/internal/sql-storage-validation.mjs.map +1 -0
- package/dist/internal/storage-mapping.mjs +1 -37
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-plans.d.mts +7 -0
- package/dist/internal/storage-plans.mjs +189 -0
- package/dist/internal/storage-plans.mjs.map +1 -0
- package/dist/internal/storage-tables.d.mts +0 -56
- package/dist/internal/storage-tables.mjs +0 -17
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +6 -1
- package/dist/internal/storage-validation.mjs +42 -1
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-kernel.mjs +3 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +89 -5
- package/src/OAuthPersistence.ts +427 -0
- package/src/index.ts +3 -0
- package/src/internal/configuration.ts +1 -3
- package/src/internal/email-kernel.ts +2 -1
- package/src/internal/models/oauth-connected-model.ts +396 -0
- package/src/internal/models/oauth-model.ts +708 -0
- package/src/internal/models/password-model.ts +2 -57
- package/src/internal/native-sql-table.ts +14 -0
- package/src/internal/oauth/accounts.ts +839 -0
- package/src/internal/oauth/connected-access.ts +388 -0
- package/src/internal/oauth/connected-collection.ts +345 -0
- package/src/internal/oauth/connected-custody.ts +308 -0
- package/src/internal/oauth/connected-flow.ts +562 -0
- package/src/internal/oauth/connected-input.ts +147 -0
- package/src/internal/oauth/connected-keys.ts +31 -0
- package/src/internal/oauth/connected-maintenance.ts +379 -0
- package/src/internal/oauth/connected-management.ts +391 -0
- package/src/internal/oauth/connected-reference.ts +135 -0
- package/src/internal/oauth/connected-settlement.ts +278 -0
- package/src/internal/oauth/connected-sign-in.ts +472 -0
- package/src/internal/oauth/connected-state.ts +924 -0
- package/src/internal/oauth/connected-target.ts +307 -0
- package/src/internal/oauth/flow.ts +505 -0
- package/src/internal/oauth/input.ts +201 -0
- package/src/internal/oauth/owner.ts +22 -0
- package/src/internal/oauth/query-compiler.ts +15 -0
- package/src/internal/oauth/registration.ts +676 -0
- package/src/internal/oauth/state.ts +218 -0
- package/src/internal/oauth/target.ts +803 -0
- package/src/internal/oauth-kernel.ts +294 -0
- package/src/internal/oauth-proxy.ts +176 -0
- package/src/internal/passkey/write-state.ts +18 -4
- package/src/internal/password-credentials.ts +245 -0
- package/src/internal/password-kernel.ts +7 -99
- package/src/internal/persistence.ts +1 -1
- package/src/internal/query-operations.ts +2 -0
- package/src/internal/session-kernel.ts +143 -55
- package/src/internal/sql-oauth-connected-model.ts +145 -0
- package/src/internal/sql-oauth-model.ts +263 -0
- package/src/internal/sql-query.ts +16 -13
- package/src/internal/sql-storage-validation.ts +64 -0
- package/src/internal/storage-mapping.ts +0 -37
- package/src/internal/storage-plans.ts +218 -0
- package/src/internal/storage-tables.ts +0 -20
- package/src/internal/storage-validation.ts +58 -0
- package/src/internal/transaction-kernel.ts +6 -2
- package/dist/internal/password-attempts.mjs +0 -197
- package/dist/internal/password-attempts.mjs.map +0 -1
- package/src/internal/password-attempts.ts +0 -466
|
@@ -58,26 +58,6 @@ export const storageTables = {
|
|
|
58
58
|
["moduleId", "credentialId"],
|
|
59
59
|
],
|
|
60
60
|
),
|
|
61
|
-
passwordAttempts: spec(
|
|
62
|
-
{
|
|
63
|
-
moduleId: text,
|
|
64
|
-
action: text,
|
|
65
|
-
attemptId: text,
|
|
66
|
-
identifierNamespace: text,
|
|
67
|
-
identifierValue: text,
|
|
68
|
-
subjectId: optionalText,
|
|
69
|
-
credentialId: optionalText,
|
|
70
|
-
securityRevision: optionalText,
|
|
71
|
-
credentialRevision: optionalText,
|
|
72
|
-
verifierVersion: optionalText,
|
|
73
|
-
identifierBindingRevision: optionalText,
|
|
74
|
-
admittedAt: integer,
|
|
75
|
-
deadline: integer,
|
|
76
|
-
retentionUntil: integer,
|
|
77
|
-
state: text,
|
|
78
|
-
},
|
|
79
|
-
[["moduleId", "attemptId"]],
|
|
80
|
-
),
|
|
81
61
|
passwordCommands: spec(
|
|
82
62
|
{
|
|
83
63
|
moduleId: text,
|
|
@@ -3,6 +3,7 @@ import { SqlClient } from "effect/sql/SqlClient";
|
|
|
3
3
|
|
|
4
4
|
import { PersistenceConfigurationError } from "./configuration";
|
|
5
5
|
import type { StorageTable } from "./storage-tables";
|
|
6
|
+
import { NativeDatabase } from "./transaction-kernel";
|
|
6
7
|
|
|
7
8
|
const Columns = Schema.Array(Schema.String);
|
|
8
9
|
const Keys = Schema.Array(Columns);
|
|
@@ -40,6 +41,63 @@ export interface PhysicalStorageTable {
|
|
|
40
41
|
export const qualifiedTableName = (table: Pick<StorageTable, "name" | "schema">) =>
|
|
41
42
|
(table.schema === undefined ? "" : quote(table.schema) + ".") + quote(table.name);
|
|
42
43
|
|
|
44
|
+
export interface PhysicalTextColumn {
|
|
45
|
+
readonly table: Pick<PhysicalStorageTable, "name" | "schema">;
|
|
46
|
+
readonly name: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const TextColumns = Schema.Array(Schema.Struct({ type: Schema.String, collation: Schema.String }));
|
|
50
|
+
|
|
51
|
+
/** One acquisition-only read: presence/unique-key validation does not establish
|
|
52
|
+
* equality compatibility. SQLite's pragma omits column collations, so only
|
|
53
|
+
* ordinary TEXT tables with no COLLATE declaration qualify (default BINARY). */
|
|
54
|
+
export const canJoinTextColumns = Effect.fnUntraced(function* (
|
|
55
|
+
left: PhysicalTextColumn | undefined,
|
|
56
|
+
right: PhysicalTextColumn | undefined,
|
|
57
|
+
) {
|
|
58
|
+
if (left === undefined || right === undefined) return false;
|
|
59
|
+
const { $client } = yield* NativeDatabase;
|
|
60
|
+
const client = $client.withoutTransforms();
|
|
61
|
+
const columns = [left, right];
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
client.onDialectOrElse({ sqlite: () => true, orElse: () => false }) &&
|
|
65
|
+
columns.some((c) => c.table.schema !== undefined && c.table.schema !== "main")
|
|
66
|
+
)
|
|
67
|
+
return false;
|
|
68
|
+
|
|
69
|
+
const query = client.onDialectOrElse({
|
|
70
|
+
pg: () => client`
|
|
71
|
+
select a.atttypid::text as type, a.attcollation::text as collation
|
|
72
|
+
from (values ${client.join(", ", false)(columns.map((c) => client`(${qualifiedTableName(c.table)}::text, ${c.name}::text)`))}) requested(relation, name)
|
|
73
|
+
join pg_attribute a on a.attrelid = to_regclass(requested.relation)
|
|
74
|
+
and a.attname = requested.name and a.attnum > 0 and not a.attisdropped
|
|
75
|
+
where a.atttypid in ('text'::regtype, 'varchar'::regtype)`,
|
|
76
|
+
sqlite: () => client`
|
|
77
|
+
with requested(relation, name) as (values ${client.join(", ", false)(columns.map((c) => client`(${c.table.name}, ${c.name})`))})
|
|
78
|
+
select 'text' as type, 'BINARY' as collation from requested
|
|
79
|
+
join sqlite_schema s on s.name = requested.relation and s.type = 'table'
|
|
80
|
+
join pragma_table_info(requested.relation) c on c.name = requested.name
|
|
81
|
+
where upper(c.type) = 'TEXT' and instr(lower(s.sql), 'collate') = 0`,
|
|
82
|
+
mysql: () => client`
|
|
83
|
+
select c.data_type as type, c.collation_name as collation
|
|
84
|
+
from (${client.join(" union all ", false)(columns.map((c) => client`select ${c.table.schema ?? null} as schema_name, ${c.table.name} as table_name, ${c.name} as column_name`))}) requested
|
|
85
|
+
join information_schema.columns c
|
|
86
|
+
on c.table_schema = coalesce(requested.schema_name, database())
|
|
87
|
+
and c.table_name = requested.table_name and c.column_name = requested.column_name
|
|
88
|
+
where c.data_type in ('varchar', 'text', 'tinytext', 'mediumtext', 'longtext')`,
|
|
89
|
+
orElse: () => Effect.succeed([]),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const rows = yield* query.pipe(Effect.flatMap(Schema.decodeUnknownEffect(TextColumns)));
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
rows.length === 2 &&
|
|
96
|
+
rows[0]?.type === rows[1]?.type &&
|
|
97
|
+
rows[0]?.collation === rows[1]?.collation
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
43
101
|
const validateKeys = Effect.fnUntraced(function* (
|
|
44
102
|
table: PhysicalStorageTable,
|
|
45
103
|
required: ReadonlyArray<ReadonlyArray<string>>,
|
|
@@ -366,9 +366,13 @@ export const makeTransactionKernel = <
|
|
|
366
366
|
? toStatement(query)
|
|
367
367
|
: (query as NativeQuery<Row[]>);
|
|
368
368
|
|
|
369
|
-
|
|
369
|
+
const row = rows[0];
|
|
370
370
|
|
|
371
|
-
|
|
371
|
+
invariant(rows.length === 1 && row !== undefined);
|
|
372
|
+
|
|
373
|
+
// SQLite executes the raw statement, before logical field decoding.
|
|
374
|
+
// This query projects one value; its alias belongs to the adapter compiler.
|
|
375
|
+
return Object.values(row)[0] === 1;
|
|
372
376
|
});
|
|
373
377
|
|
|
374
378
|
const guardedQuery = (table: Table, where: SQL, values: Row, guard: GuardedUpdate) => {
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { sqlMapping } from "./native-sql-table.mjs";
|
|
2
|
-
import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
3
|
-
import { PasswordPreparedPostconditions } from "./PasswordPreparedPostconditions.mjs";
|
|
4
|
-
import { DateTime, Effect, Option, Redacted, Schema } from "effect";
|
|
5
|
-
import { CurrentCommitJournal, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
|
|
6
|
-
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
7
|
-
import { PasswordUnavailable, snapshotPasswordCredential } from "@yielded/auth/Password";
|
|
8
|
-
//#region src/internal/password-attempts.ts
|
|
9
|
-
const unavailable = () => PasswordUnavailable.make({});
|
|
10
|
-
const nowMillis = DateTime.now.pipe(Effect.map(DateTime.toEpochMillis));
|
|
11
|
-
const failure = (effect) => reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(Effect.mapError(unavailable));
|
|
12
|
-
const samePasswordCredentialSnapshot = (a, b) => a.moduleId === b.moduleId && a.revision.subjectId === b.revision.subjectId && a.revision.securityRevision === b.revision.securityRevision && a.revision.credentials.length === b.revision.credentials.length && new Set(a.revision.credentials.map((entry) => entry.credentialId)).size === a.revision.credentials.length && new Set(b.revision.credentials.map((entry) => entry.credentialId)).size === b.revision.credentials.length && a.revision.credentials.every((entry) => b.revision.credentials.some((other) => entry.credentialId === other.credentialId && entry.revision === other.revision)) && a.credentialId === b.credentialId && a.credentialRevision === b.credentialRevision && a.verifierVersion === b.verifierVersion && Redacted.value(a.verifier) === Redacted.value(b.verifier) && a.normalization === b.normalization && a.identifier.namespace === b.identifier.namespace && a.identifier.value === b.identifier.value && a.identifierBindingRevision === b.identifierBindingRevision && a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;
|
|
13
|
-
/** The interactive attempt workflow uses the owning Effect SQL client directly.
|
|
14
|
-
* Table adapters supply representation only; admission and settlement policy live here.
|
|
15
|
-
*/
|
|
16
|
-
const makePasswordAttempts = Effect.fnUntraced(function* (table, mapping, options) {
|
|
17
|
-
const sql = (yield* NativeDatabase).$client.withoutTransforms();
|
|
18
|
-
const hooks = yield* LifecycleHooks;
|
|
19
|
-
const parent = yield* Effect.serviceOption(CurrentCommitJournal);
|
|
20
|
-
const [s, i, c, a] = yield* sqlMapping(() => [
|
|
21
|
-
table(mapping.subject.table),
|
|
22
|
-
table(mapping.identifier.table),
|
|
23
|
-
table(mapping.credential.table),
|
|
24
|
-
table(mapping.attempt.table)
|
|
25
|
-
]).pipe(failure);
|
|
26
|
-
const lock = options.locking ? sql.literal(" FOR UPDATE") : sql.literal("");
|
|
27
|
-
const standalone = Effect.gen(function* () {
|
|
28
|
-
if (options.coordinated) {
|
|
29
|
-
const current = yield* Effect.serviceOption(CurrentCommitJournal);
|
|
30
|
-
if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value) return yield* unavailable();
|
|
31
|
-
} else {
|
|
32
|
-
if (yield* hasCommitScope) return yield* unavailable();
|
|
33
|
-
yield* options.standaloneGuard;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
const owned = (work) => standalone.pipe(Effect.andThen(coordinateCommit(() => sql.withTransaction(work), { mode: options.mode })), Effect.map((result) => result.value), Effect.provideService(LifecycleHooks, hooks), failure);
|
|
37
|
-
const allocate = (asynchronous, sync) => {
|
|
38
|
-
if (options.mode !== "synchronous" && asynchronous !== void 0) return asynchronous;
|
|
39
|
-
return sync === void 0 ? Effect.fail(unavailable()) : Effect.try({
|
|
40
|
-
try: sync,
|
|
41
|
-
catch: unavailable
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
const snapshot = Effect.fnUntraced(function* (nativeId, moduleId, identifier, locking, attemptId) {
|
|
45
|
-
const subjectTable = s.as("password_subject");
|
|
46
|
-
const identifierTable = i.as("password_identifier");
|
|
47
|
-
const credentialTable = c.as("password_credential");
|
|
48
|
-
const attemptTable = a.as("password_attempt");
|
|
49
|
-
const rows = yield* sqlMapping(() => sql`select ${subjectTable.fields("subject_")}, ${identifierTable.fields("identifier_")}, ${credentialTable.fields("credential_")}
|
|
50
|
-
${attemptId === void 0 ? sql.literal("") : sql`, ${attemptTable.fields("attempt_")}`}
|
|
51
|
-
from ${subjectTable.name}
|
|
52
|
-
inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}
|
|
53
|
-
and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}
|
|
54
|
-
and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}
|
|
55
|
-
inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}
|
|
56
|
-
and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}
|
|
57
|
-
${attemptId === void 0 ? sql.literal("") : sql`inner join ${attemptTable.name} on
|
|
58
|
-
${attemptTable.column(mapping.attempt.moduleId)} = ${attemptTable.value(mapping.attempt.moduleId, moduleId)}
|
|
59
|
-
and ${attemptTable.column(mapping.attempt.attemptId)} = ${attemptTable.value(mapping.attempt.attemptId, attemptId)}`}
|
|
60
|
-
where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}
|
|
61
|
-
${locking ? lock : sql.literal("")}`).pipe(Effect.flatten);
|
|
62
|
-
if (rows.length !== 1) return void 0;
|
|
63
|
-
const [subject, identifierRow, credential] = yield* sqlMapping(() => [
|
|
64
|
-
s.decode(rows[0], "subject_"),
|
|
65
|
-
i.decode(rows[0], "identifier_"),
|
|
66
|
-
c.decode(rows[0], "credential_")
|
|
67
|
-
]);
|
|
68
|
-
if (yield* sqlMapping(() => !mapping.subject.isActiveStatus(subject[mapping.subject.status]) || !mapping.identifier.isCurrent(identifierRow) || !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) || !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) || !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]))) return void 0;
|
|
69
|
-
return {
|
|
70
|
-
nativeId,
|
|
71
|
-
captured: yield* sqlMapping(() => mapping.credential.decode({
|
|
72
|
-
moduleId,
|
|
73
|
-
subject,
|
|
74
|
-
identifier: identifierRow,
|
|
75
|
-
credential
|
|
76
|
-
})).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential)),
|
|
77
|
-
attempt: attemptId === void 0 ? void 0 : yield* sqlMapping(() => a.decode(rows[0], "attempt_"))
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
|
-
const attemptRow = Effect.fnUntraced(function* (moduleId, attemptId, locking) {
|
|
81
|
-
const rows = yield* sqlMapping(() => sql`select ${a.fields("a_")} from ${a.name}
|
|
82
|
-
where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, moduleId)}
|
|
83
|
-
and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, attemptId)}
|
|
84
|
-
${locking ? lock : sql.literal("")}`).pipe(Effect.flatten);
|
|
85
|
-
return rows.length === 1 ? yield* sqlMapping(() => a.decode(rows[0], "a_")) : void 0;
|
|
86
|
-
});
|
|
87
|
-
const attemptMatches = (row, candidate, nativeId) => row[mapping.attempt.moduleId] === candidate.moduleId && row[mapping.attempt.identifierNamespace] === candidate.identifier.namespace && row[mapping.attempt.identifierValue] === candidate.identifier.value && mapping.subjectId.equals(row[mapping.attempt.subjectId], nativeId) && row[mapping.attempt.credentialId] === candidate.credentialId && row[mapping.attempt.securityRevision] === candidate.revision.securityRevision && row[mapping.attempt.credentialRevision] === candidate.credentialRevision && row[mapping.attempt.verifierVersion] === candidate.verifierVersion && row[mapping.attempt.identifierBindingRevision] === candidate.identifierBindingRevision;
|
|
88
|
-
return {
|
|
89
|
-
prepareAttempt: (uncaptured) => Effect.gen(function* () {
|
|
90
|
-
const input = Object.freeze({
|
|
91
|
-
...uncaptured,
|
|
92
|
-
identifier: Object.freeze({ ...uncaptured.identifier })
|
|
93
|
-
});
|
|
94
|
-
yield* standalone;
|
|
95
|
-
const identifiers = yield* sqlMapping(() => sql`select ${i.fields("i_")} from ${i.name}
|
|
96
|
-
where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}
|
|
97
|
-
and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`).pipe(Effect.flatten);
|
|
98
|
-
const identifier = identifiers.length === 1 ? yield* sqlMapping(() => i.decode(identifiers[0], "i_")) : void 0;
|
|
99
|
-
const resolved = identifier === void 0 || !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier))) ? void 0 : yield* snapshot(identifier[mapping.identifier.subjectId], input.moduleId, input.identifier, false);
|
|
100
|
-
const candidate = resolved !== void 0 && (input.subjectId === void 0 || resolved.captured.revision.subjectId === input.subjectId) ? resolved : void 0;
|
|
101
|
-
const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
|
|
102
|
-
let used = false;
|
|
103
|
-
return Object.freeze({
|
|
104
|
-
...candidate === void 0 ? {} : { credential: candidate.captured },
|
|
105
|
-
admit: (prepare) => Effect.suspend(() => {
|
|
106
|
-
if (used) return Effect.fail(unavailable());
|
|
107
|
-
used = true;
|
|
108
|
-
return owned(Effect.gen(function* () {
|
|
109
|
-
const journal = yield* CurrentCommitJournal;
|
|
110
|
-
const now = yield* nowMillis;
|
|
111
|
-
const captured = candidate?.captured;
|
|
112
|
-
yield* sqlMapping(() => sql`${a.insert(mapping.attempt.encodeInsert({
|
|
113
|
-
moduleId: input.moduleId,
|
|
114
|
-
action: input.action,
|
|
115
|
-
attemptId,
|
|
116
|
-
identifier: input.identifier,
|
|
117
|
-
...captured === void 0 ? {} : {
|
|
118
|
-
subjectId: captured.revision.subjectId,
|
|
119
|
-
credentialId: captured.credentialId,
|
|
120
|
-
securityRevision: captured.revision.securityRevision,
|
|
121
|
-
credentialRevision: captured.credentialRevision,
|
|
122
|
-
verifierVersion: captured.verifierVersion,
|
|
123
|
-
identifierBindingRevision: captured.identifierBindingRevision
|
|
124
|
-
},
|
|
125
|
-
admittedAtMillis: now,
|
|
126
|
-
deadlineMillis: now + input.attemptLifetimeMillis,
|
|
127
|
-
retentionUntilMillis: now + input.attemptLifetimeMillis
|
|
128
|
-
}, {
|
|
129
|
-
...candidate === void 0 ? {} : { nativeSubjectId: candidate.nativeId },
|
|
130
|
-
state: "pending"
|
|
131
|
-
}))}`).pipe(Effect.flatten);
|
|
132
|
-
return prepare({
|
|
133
|
-
_tag: "Admitted",
|
|
134
|
-
attemptId,
|
|
135
|
-
...captured === void 0 ? {} : { credential: captured }
|
|
136
|
-
}, journal);
|
|
137
|
-
}));
|
|
138
|
-
})
|
|
139
|
-
});
|
|
140
|
-
}).pipe(failure),
|
|
141
|
-
settleAttempt: (uncaptured, prepare) => owned(Effect.gen(function* () {
|
|
142
|
-
const input = Object.freeze({
|
|
143
|
-
...uncaptured,
|
|
144
|
-
...uncaptured.rehash === void 0 ? {} : { rehash: Object.freeze({ ...uncaptured.rehash }) }
|
|
145
|
-
});
|
|
146
|
-
const journal = yield* CurrentCommitJournal;
|
|
147
|
-
const captured = input.captured === void 0 ? void 0 : yield* snapshotPasswordCredential(input.captured);
|
|
148
|
-
const discovered = yield* attemptRow(input.moduleId, input.attemptId, false);
|
|
149
|
-
if (discovered === void 0 || discovered[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
|
|
150
|
-
const current = input.outcome !== "verified" || captured === void 0 ? void 0 : yield* snapshot(discovered[mapping.attempt.subjectId], input.moduleId, captured.identifier, true);
|
|
151
|
-
const row = options.locking ? yield* attemptRow(input.moduleId, input.attemptId, true) : discovered;
|
|
152
|
-
if (row === void 0 || row[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
|
|
153
|
-
const now = yield* nowMillis;
|
|
154
|
-
const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
|
|
155
|
-
const verified = captured !== void 0 && current !== void 0 && captured.moduleId === input.moduleId && deadline > now && attemptMatches(row, captured, current.nativeId) && samePasswordCredentialSnapshot(current.captured, captured);
|
|
156
|
-
const decision = verified ? "verified" : "rejected";
|
|
157
|
-
const receipt = prepare(decision, journal);
|
|
158
|
-
yield* sqlMapping(() => sql`${a.update({ [mapping.attempt.state]: decision })}
|
|
159
|
-
where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, input.moduleId)}
|
|
160
|
-
and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, input.attemptId)}
|
|
161
|
-
and ${a.column(mapping.attempt.state)} = ${a.value(mapping.attempt.state, "pending")}`).pipe(Effect.flatten);
|
|
162
|
-
if (verified && current !== void 0 && captured !== void 0) {
|
|
163
|
-
let expected = current.captured;
|
|
164
|
-
if (input.rehash !== void 0) {
|
|
165
|
-
if (input.rehash.expectedVersion !== expected.verifierVersion || Redacted.value(input.rehash.expectedVerifier) !== Redacted.value(expected.verifier)) return yield* unavailable();
|
|
166
|
-
const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
|
|
167
|
-
const rehash = input.rehash;
|
|
168
|
-
yield* sqlMapping(() => sql`${c.update(mapping.credential.encodeVerifier(rehash.nextVerifier, version))}
|
|
169
|
-
where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, input.moduleId)}
|
|
170
|
-
and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, current.nativeId)}
|
|
171
|
-
and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, captured.credentialId)}
|
|
172
|
-
and ${c.column(mapping.credential.credentialRevision)} = ${c.value(mapping.credential.credentialRevision, captured.credentialRevision)}
|
|
173
|
-
and ${c.column(mapping.credential.verifierVersion)} = ${c.value(mapping.credential.verifierVersion, rehash.expectedVersion)}
|
|
174
|
-
and ${c.column(mapping.credential.verifier)} = ${c.value(mapping.credential.verifier, Redacted.value(rehash.expectedVerifier))}`).pipe(Effect.flatten);
|
|
175
|
-
expected = {
|
|
176
|
-
...expected,
|
|
177
|
-
verifierVersion: version,
|
|
178
|
-
verifier: input.rehash.nextVerifier
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const settled = expected;
|
|
182
|
-
const applied = Effect.gen(function* () {
|
|
183
|
-
const final = yield* snapshot(current.nativeId, input.moduleId, captured.identifier, false, input.attemptId);
|
|
184
|
-
return final !== void 0 && samePasswordCredentialSnapshot(final.captured, settled) && final.attempt !== void 0 && final.attempt[mapping.attempt.state] === "verified" && attemptMatches(final.attempt, captured, current.nativeId) && (yield* mapping.decodeInstant(final.attempt[mapping.attempt.deadline])) === deadline && deadline > (yield* nowMillis);
|
|
185
|
-
}).pipe(failure);
|
|
186
|
-
if (!(yield* applied)) return yield* unavailable();
|
|
187
|
-
const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);
|
|
188
|
-
if (Option.isSome(postconditions) && !postconditions.value.register(Effect.flatMap(applied, (holds) => holds ? Effect.void : Effect.fail(unavailable())))) return yield* unavailable();
|
|
189
|
-
}
|
|
190
|
-
return receipt;
|
|
191
|
-
}))
|
|
192
|
-
};
|
|
193
|
-
});
|
|
194
|
-
//#endregion
|
|
195
|
-
export { makePasswordAttempts, samePasswordCredentialSnapshot };
|
|
196
|
-
|
|
197
|
-
//# sourceMappingURL=password-attempts.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"password-attempts.mjs","names":[],"sources":["../../src/internal/password-attempts.ts"],"sourcesContent":["import {\n coordinateCommit,\n CurrentCommitJournal,\n hasCommitScope,\n LifecycleHooks,\n} from \"@yielded/auth/Hooks\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n type PasswordPersistence,\n PasswordUnavailable,\n snapshotPasswordCredential,\n type PasswordCredentialSnapshot,\n type PreparePasswordCommit,\n type PasswordAttemptAdmission,\n} from \"@yielded/auth/Password\";\nimport { reportPersistenceFailure } from \"@yielded/auth/Persistence\";\nimport { DateTime, Effect, Option, Redacted, Schema } from \"effect\";\n\nimport type { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport { sqlMapping, type SqlTable } from \"./native-sql-table\";\nimport type { PasswordSqlConfiguration } from \"./password-kernel\";\nimport { PasswordPreparedPostconditions } from \"./PasswordPreparedPostconditions\";\nimport { NativeDatabase } from \"./transaction-kernel\";\n\ntype Row = Readonly<Record<string, unknown>>;\ntype Mapping = AnyPasswordPersistenceMapping;\n\nconst unavailable = () => PasswordUnavailable.make({});\nconst nowMillis = DateTime.now.pipe(Effect.map(DateTime.toEpochMillis));\n\nconst failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(\n Effect.mapError(unavailable),\n );\n\nexport const samePasswordCredentialSnapshot = (\n a: PasswordCredentialSnapshot,\n b: PasswordCredentialSnapshot,\n) =>\n a.moduleId === b.moduleId &&\n a.revision.subjectId === b.revision.subjectId &&\n a.revision.securityRevision === b.revision.securityRevision &&\n a.revision.credentials.length === b.revision.credentials.length &&\n new Set(a.revision.credentials.map((entry) => entry.credentialId)).size ===\n a.revision.credentials.length &&\n new Set(b.revision.credentials.map((entry) => entry.credentialId)).size ===\n b.revision.credentials.length &&\n a.revision.credentials.every((entry) =>\n b.revision.credentials.some(\n (other) => entry.credentialId === other.credentialId && entry.revision === other.revision,\n ),\n ) &&\n a.credentialId === b.credentialId &&\n a.credentialRevision === b.credentialRevision &&\n a.verifierVersion === b.verifierVersion &&\n Redacted.value(a.verifier) === Redacted.value(b.verifier) &&\n a.normalization === b.normalization &&\n a.identifier.namespace === b.identifier.namespace &&\n a.identifier.value === b.identifier.value &&\n a.identifierBindingRevision === b.identifierBindingRevision &&\n a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;\n\n/** The interactive attempt workflow uses the owning Effect SQL client directly.\n * Table adapters supply representation only; admission and settlement policy live here.\n */\nexport const makePasswordAttempts = Effect.fnUntraced(function* (\n table: (table: object) => SqlTable,\n mapping: Mapping,\n options: PasswordSqlConfiguration,\n): Effect.fn.Return<\n Pick<PasswordPersistence[\"Service\"], \"prepareAttempt\" | \"settleAttempt\">,\n PasswordUnavailable,\n LifecycleHooks | NativeDatabase\n> {\n const sql = (yield* NativeDatabase).$client.withoutTransforms();\n const hooks = yield* LifecycleHooks;\n const parent = yield* Effect.serviceOption(CurrentCommitJournal);\n\n const [s, i, c, a] = yield* sqlMapping(\n () =>\n [\n table(mapping.subject.table),\n table(mapping.identifier.table),\n table(mapping.credential.table),\n table(mapping.attempt.table),\n ] as const,\n ).pipe(failure);\n\n const lock = options.locking ? sql.literal(\" FOR UPDATE\") : sql.literal(\"\");\n\n const standalone = Effect.gen(function* () {\n if (options.coordinated) {\n const current = yield* Effect.serviceOption(CurrentCommitJournal);\n\n if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value)\n return yield* unavailable();\n } else {\n if (yield* hasCommitScope) return yield* unavailable();\n yield* options.standaloneGuard;\n }\n });\n\n const owned = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n standalone.pipe(\n Effect.andThen(coordinateCommit(() => sql.withTransaction(work), { mode: options.mode })),\n Effect.map((result) => result.value),\n Effect.provideService(LifecycleHooks, hooks),\n failure,\n );\n\n const allocate = <A>(\n asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,\n sync: (() => A) | undefined,\n ) => {\n if (options.mode !== \"synchronous\" && asynchronous !== undefined) return asynchronous;\n\n return sync === undefined\n ? Effect.fail(unavailable())\n : Effect.try({ try: sync, catch: unavailable });\n };\n\n // Bind each native ID through its own column codec. Physical encodings of the\n // same application ID need not match across subject, identifier and password.\n const snapshot = Effect.fnUntraced(function* (\n nativeId: unknown,\n moduleId: string,\n identifier: LoginIdentifier,\n locking: boolean,\n attemptId?: string,\n ) {\n const subjectTable = s.as(\"password_subject\");\n const identifierTable = i.as(\"password_identifier\");\n const credentialTable = c.as(\"password_credential\");\n const attemptTable = a.as(\"password_attempt\");\n\n const rows = yield* sqlMapping(\n () =>\n sql<Row>`select ${subjectTable.fields(\"subject_\")}, ${identifierTable.fields(\"identifier_\")}, ${credentialTable.fields(\"credential_\")}\n ${attemptId === undefined ? sql.literal(\"\") : sql`, ${attemptTable.fields(\"attempt_\")}`}\n from ${subjectTable.name}\n inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}\n and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}\n and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}\n inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}\n and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}\n ${\n attemptId === undefined\n ? sql.literal(\"\")\n : sql`inner join ${attemptTable.name} on\n ${attemptTable.column(mapping.attempt.moduleId)} = ${attemptTable.value(mapping.attempt.moduleId, moduleId)}\n and ${attemptTable.column(mapping.attempt.attemptId)} = ${attemptTable.value(mapping.attempt.attemptId, attemptId)}`\n }\n where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}\n ${locking ? lock : sql.literal(\"\")}`,\n ).pipe(Effect.flatten);\n\n if (rows.length !== 1) return undefined;\n\n const [subject, identifierRow, credential] = yield* sqlMapping(\n () =>\n [\n s.decode(rows[0]!, \"subject_\"),\n i.decode(rows[0]!, \"identifier_\"),\n c.decode(rows[0]!, \"credential_\"),\n ] as const,\n );\n\n if (\n yield* sqlMapping(\n () =>\n !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||\n !mapping.identifier.isCurrent(identifierRow) ||\n !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) ||\n !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) ||\n !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]),\n )\n )\n return undefined;\n\n const captured = yield* sqlMapping(() =>\n mapping.credential.decode({ moduleId, subject, identifier: identifierRow, credential }),\n ).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));\n\n return {\n nativeId,\n captured,\n attempt:\n attemptId === undefined\n ? undefined\n : yield* sqlMapping(() => a.decode(rows[0]!, \"attempt_\")),\n };\n });\n\n const attemptRow = Effect.fnUntraced(function* (\n moduleId: string,\n attemptId: string,\n locking: boolean,\n ) {\n const rows = yield* sqlMapping(\n () => sql<Row>`select ${a.fields(\"a_\")} from ${a.name}\n where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, moduleId)}\n and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, attemptId)}\n ${locking ? lock : sql.literal(\"\")}`,\n ).pipe(Effect.flatten);\n\n return rows.length === 1 ? yield* sqlMapping(() => a.decode(rows[0]!, \"a_\")) : undefined;\n });\n\n const attemptMatches = (row: Row, candidate: PasswordCredentialSnapshot, nativeId: unknown) =>\n row[mapping.attempt.moduleId] === candidate.moduleId &&\n row[mapping.attempt.identifierNamespace] === candidate.identifier.namespace &&\n row[mapping.attempt.identifierValue] === candidate.identifier.value &&\n mapping.subjectId.equals(row[mapping.attempt.subjectId], nativeId) &&\n row[mapping.attempt.credentialId] === candidate.credentialId &&\n row[mapping.attempt.securityRevision] === candidate.revision.securityRevision &&\n row[mapping.attempt.credentialRevision] === candidate.credentialRevision &&\n row[mapping.attempt.verifierVersion] === candidate.verifierVersion &&\n row[mapping.attempt.identifierBindingRevision] === candidate.identifierBindingRevision;\n\n const service: Pick<PasswordPersistence[\"Service\"], \"prepareAttempt\" | \"settleAttempt\"> = {\n // The remaining operations are composed by the persistence owner.\n prepareAttempt: (uncaptured) =>\n Effect.gen(function* () {\n const input = Object.freeze({\n ...uncaptured,\n identifier: Object.freeze({ ...uncaptured.identifier }),\n });\n\n yield* standalone;\n\n const identifiers = yield* sqlMapping(\n () => sql<Row>`select ${i.fields(\"i_\")} from ${i.name}\n where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}\n and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`,\n ).pipe(Effect.flatten);\n\n const identifier =\n identifiers.length === 1\n ? yield* sqlMapping(() => i.decode(identifiers[0]!, \"i_\"))\n : undefined;\n\n const resolved =\n identifier === undefined ||\n !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))\n ? undefined\n : yield* snapshot(\n identifier[mapping.identifier.subjectId],\n input.moduleId,\n input.identifier,\n false,\n );\n\n const candidate =\n resolved !== undefined &&\n (input.subjectId === undefined ||\n resolved.captured.revision.subjectId === input.subjectId)\n ? resolved\n : undefined;\n\n const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);\n let used = false;\n\n return Object.freeze({\n ...(candidate === undefined ? {} : { credential: candidate.captured }),\n admit: <A>(prepare: PreparePasswordCommit<PasswordAttemptAdmission, A>) =>\n Effect.suspend(() => {\n if (used) return Effect.fail(unavailable());\n used = true;\n\n return owned(\n Effect.gen(function* () {\n const journal = yield* CurrentCommitJournal;\n const now = yield* nowMillis;\n const captured = candidate?.captured;\n\n // This receipt records what was inspected; it grants no authority.\n // Settlement checks the current database against this exact snapshot.\n yield* sqlMapping(\n () =>\n sql`${a.insert(\n mapping.attempt.encodeInsert(\n {\n moduleId: input.moduleId,\n action: input.action,\n attemptId,\n identifier: input.identifier,\n ...(captured === undefined\n ? {}\n : {\n subjectId: captured.revision.subjectId,\n credentialId: captured.credentialId,\n securityRevision: captured.revision.securityRevision,\n credentialRevision: captured.credentialRevision,\n verifierVersion: captured.verifierVersion,\n identifierBindingRevision: captured.identifierBindingRevision,\n }),\n admittedAtMillis: now,\n deadlineMillis: now + input.attemptLifetimeMillis,\n retentionUntilMillis: now + input.attemptLifetimeMillis,\n },\n {\n ...(candidate === undefined\n ? {}\n : { nativeSubjectId: candidate.nativeId }),\n state: \"pending\",\n },\n ),\n )}`,\n ).pipe(Effect.flatten);\n\n return prepare(\n {\n _tag: \"Admitted\",\n attemptId,\n ...(captured === undefined ? {} : { credential: captured }),\n },\n journal,\n );\n }),\n );\n }),\n });\n }).pipe(failure),\n settleAttempt: (uncaptured, prepare) =>\n owned(\n Effect.gen(function* () {\n const input = Object.freeze({\n ...uncaptured,\n ...(uncaptured.rehash === undefined\n ? {}\n : { rehash: Object.freeze({ ...uncaptured.rehash }) }),\n });\n\n const journal = yield* CurrentCommitJournal;\n\n const captured =\n input.captured === undefined\n ? undefined\n : yield* snapshotPasswordCredential(input.captured);\n\n const discovered = yield* attemptRow(input.moduleId, input.attemptId, false);\n\n if (discovered === undefined || discovered[mapping.attempt.state] !== \"pending\")\n return prepare(\"rejected\", journal);\n\n // Lock the subject before the attempt: a coordinated application\n // transaction may already hold the subject lock. Without row locks\n // the discovery read is already serialized, so it is not repeated.\n const current =\n input.outcome !== \"verified\" || captured === undefined\n ? undefined\n : yield* snapshot(\n discovered[mapping.attempt.subjectId],\n input.moduleId,\n captured.identifier,\n true,\n );\n\n const row = options.locking\n ? yield* attemptRow(input.moduleId, input.attemptId, true)\n : discovered;\n\n if (row === undefined || row[mapping.attempt.state] !== \"pending\")\n return prepare(\"rejected\", journal);\n const now = yield* nowMillis;\n const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);\n\n const verified =\n captured !== undefined &&\n current !== undefined &&\n captured.moduleId === input.moduleId &&\n deadline > now &&\n attemptMatches(row, captured, current.nativeId) &&\n samePasswordCredentialSnapshot(current.captured, captured);\n\n const decision = verified ? \"verified\" : \"rejected\";\n const receipt = prepare(decision, journal);\n\n yield* sqlMapping(\n () => sql`${a.update({ [mapping.attempt.state]: decision })}\n where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, input.moduleId)}\n and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, input.attemptId)}\n and ${a.column(mapping.attempt.state)} = ${a.value(mapping.attempt.state, \"pending\")}`,\n ).pipe(Effect.flatten);\n\n if (verified && current !== undefined && captured !== undefined) {\n let expected = current.captured;\n\n if (input.rehash !== undefined) {\n if (\n input.rehash.expectedVersion !== expected.verifierVersion ||\n Redacted.value(input.rehash.expectedVerifier) !== Redacted.value(expected.verifier)\n )\n return yield* unavailable();\n\n const version = yield* allocate(\n mapping.allocateRevision,\n mapping.allocateRevisionSync,\n );\n\n const rehash = input.rehash;\n\n yield* sqlMapping(\n () => sql`${c.update(mapping.credential.encodeVerifier(rehash.nextVerifier, version))}\n where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, input.moduleId)}\n and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, current.nativeId)}\n and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, captured.credentialId)}\n and ${c.column(mapping.credential.credentialRevision)} = ${c.value(mapping.credential.credentialRevision, captured.credentialRevision)}\n and ${c.column(mapping.credential.verifierVersion)} = ${c.value(mapping.credential.verifierVersion, rehash.expectedVersion)}\n and ${c.column(mapping.credential.verifier)} = ${c.value(mapping.credential.verifier, Redacted.value(rehash.expectedVerifier))}`,\n ).pipe(Effect.flatten);\n expected = {\n ...expected,\n verifierVersion: version,\n verifier: input.rehash.nextVerifier,\n };\n }\n\n const settled = expected;\n\n const applied = Effect.gen(function* () {\n const final = yield* snapshot(\n current.nativeId,\n input.moduleId,\n captured.identifier,\n false,\n input.attemptId,\n );\n\n return (\n final !== undefined &&\n samePasswordCredentialSnapshot(final.captured, settled) &&\n final.attempt !== undefined &&\n final.attempt[mapping.attempt.state] === \"verified\" &&\n attemptMatches(final.attempt, captured, current.nativeId) &&\n (yield* mapping.decodeInstant(final.attempt[mapping.attempt.deadline])) ===\n deadline &&\n deadline > (yield* nowMillis)\n );\n }).pipe(failure);\n\n if (!(yield* applied)) return yield* unavailable();\n\n // A coordinator that accepts postconditions repeats this check after\n // application work, inside the same transaction.\n const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);\n\n if (\n Option.isSome(postconditions) &&\n !postconditions.value.register(\n Effect.flatMap(applied, (holds) =>\n holds ? Effect.void : Effect.fail(unavailable()),\n ),\n )\n )\n return yield* unavailable();\n }\n\n return receipt;\n }),\n ),\n };\n\n return service;\n});\n"],"mappings":";;;;;;;;AA4BA,MAAM,oBAAoB,oBAAoB,KAAK,CAAC,CAAC;AACrD,MAAM,YAAY,SAAS,IAAI,KAAK,OAAO,IAAI,SAAS,aAAa,CAAC;AAEtE,MAAM,WAAoB,WACxB,yBAAyB,QAAQ,OAAO,GAAG,mBAAmB,CAAC,CAAC,CAAC,KAC/D,OAAO,SAAS,WAAW,CAC7B;AAEF,MAAa,kCACX,GACA,MAEA,EAAE,aAAa,EAAE,YACjB,EAAE,SAAS,cAAc,EAAE,SAAS,aACpC,EAAE,SAAS,qBAAqB,EAAE,SAAS,oBAC3C,EAAE,SAAS,YAAY,WAAW,EAAE,SAAS,YAAY,UACzD,IAAI,IAAI,EAAE,SAAS,YAAY,KAAK,UAAU,MAAM,YAAY,CAAC,CAAC,CAAC,SACjE,EAAE,SAAS,YAAY,UACzB,IAAI,IAAI,EAAE,SAAS,YAAY,KAAK,UAAU,MAAM,YAAY,CAAC,CAAC,CAAC,SACjE,EAAE,SAAS,YAAY,UACzB,EAAE,SAAS,YAAY,OAAO,UAC5B,EAAE,SAAS,YAAY,MACpB,UAAU,MAAM,iBAAiB,MAAM,gBAAgB,MAAM,aAAa,MAAM,QACnF,CACF,KACA,EAAE,iBAAiB,EAAE,gBACrB,EAAE,uBAAuB,EAAE,sBAC3B,EAAE,oBAAoB,EAAE,mBACxB,SAAS,MAAM,EAAE,QAAQ,MAAM,SAAS,MAAM,EAAE,QAAQ,KACxD,EAAE,kBAAkB,EAAE,iBACtB,EAAE,WAAW,cAAc,EAAE,WAAW,aACxC,EAAE,WAAW,UAAU,EAAE,WAAW,SACpC,EAAE,8BAA8B,EAAE,6BAClC,EAAE,+BAA+B,EAAE;;;;AAKrC,MAAa,uBAAuB,OAAO,WAAW,WACpD,OACA,SACA,SAKA;CACA,MAAM,OAAO,OAAO,eAAA,CAAgB,QAAQ,kBAAkB;CAC9D,MAAM,QAAQ,OAAO;CACrB,MAAM,SAAS,OAAO,OAAO,cAAc,oBAAoB;CAE/D,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,OAAO,iBAExB;EACE,MAAM,QAAQ,QAAQ,KAAK;EAC3B,MAAM,QAAQ,WAAW,KAAK;EAC9B,MAAM,QAAQ,WAAW,KAAK;EAC9B,MAAM,QAAQ,QAAQ,KAAK;CAC7B,CACJ,CAAC,CAAC,KAAK,OAAO;CAEd,MAAM,OAAO,QAAQ,UAAU,IAAI,QAAQ,aAAa,IAAI,IAAI,QAAQ,EAAE;CAE1E,MAAM,aAAa,OAAO,IAAI,aAAa;EACzC,IAAI,QAAQ,aAAa;GACvB,MAAM,UAAU,OAAO,OAAO,cAAc,oBAAoB;GAEhE,IAAI,OAAO,OAAO,MAAM,KAAK,OAAO,OAAO,OAAO,KAAK,OAAO,UAAU,QAAQ,OAC9E,OAAO,OAAO,YAAY;EAC9B,OAAO;GACL,IAAI,OAAO,gBAAgB,OAAO,OAAO,YAAY;GACrD,OAAO,QAAQ;EACjB;CACF,CAAC;CAED,MAAM,SAAkB,SACtB,WAAW,KACT,OAAO,QAAQ,uBAAuB,IAAI,gBAAgB,IAAI,GAAG,EAAE,MAAM,QAAQ,KAAK,CAAC,CAAC,GACxF,OAAO,KAAK,WAAW,OAAO,KAAK,GACnC,OAAO,eAAe,gBAAgB,KAAK,GAC3C,OACF;CAEF,MAAM,YACJ,cACA,SACG;EACH,IAAI,QAAQ,SAAS,iBAAiB,iBAAiB,KAAA,GAAW,OAAO;EAEzE,OAAO,SAAS,KAAA,IACZ,OAAO,KAAK,YAAY,CAAC,IACzB,OAAO,IAAI;GAAE,KAAK;GAAM,OAAO;EAAY,CAAC;CAClD;CAIA,MAAM,WAAW,OAAO,WAAW,WACjC,UACA,UACA,YACA,SACA,WACA;EACA,MAAM,eAAe,EAAE,GAAG,kBAAkB;EAC5C,MAAM,kBAAkB,EAAE,GAAG,qBAAqB;EAClD,MAAM,kBAAkB,EAAE,GAAG,qBAAqB;EAClD,MAAM,eAAe,EAAE,GAAG,kBAAkB;EAE5C,MAAM,OAAO,OAAO,iBAEhB,GAAQ,UAAU,aAAa,OAAO,UAAU,EAAE,IAAI,gBAAgB,OAAO,aAAa,EAAE,IAAI,gBAAgB,OAAO,aAAa,EAAE;QACtI,cAAc,KAAA,IAAY,IAAI,QAAQ,EAAE,IAAI,GAAG,KAAK,aAAa,OAAO,UAAU,IAAI;aACjF,aAAa,KAAK;mBACZ,gBAAgB,KAAK,MAAM,gBAAgB,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,WAAW,WAAW,SAAS,EAAE;cACpK,gBAAgB,OAAO,QAAQ,WAAW,KAAK,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,OAAO,WAAW,KAAK,EAAE;cACxH,gBAAgB,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,WAAW,QAAQ,EAAE;mBACnH,gBAAgB,KAAK,MAAM,gBAAgB,OAAO,QAAQ,WAAW,QAAQ,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,UAAU,QAAQ,EAAE;cACtJ,gBAAgB,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,WAAW,QAAQ,EAAE;QAE9H,cAAc,KAAA,IACV,IAAI,QAAQ,EAAE,IACd,GAAG,cAAc,aAAa,KAAK;UACrC,aAAa,OAAO,QAAQ,QAAQ,QAAQ,EAAE,KAAK,aAAa,MAAM,QAAQ,QAAQ,UAAU,QAAQ,EAAE;cACtG,aAAa,OAAO,QAAQ,QAAQ,SAAS,EAAE,KAAK,aAAa,MAAM,QAAQ,QAAQ,WAAW,SAAS,IAClH;cACO,aAAa,OAAO,QAAQ,QAAQ,EAAE,EAAE,KAAK,aAAa,MAAM,QAAQ,QAAQ,IAAI,QAAQ,EAAE;QACpG,UAAU,OAAO,IAAI,QAAQ,EAAE,GACnC,CAAC,CAAC,KAAK,OAAO,OAAO;EAErB,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;EAE9B,MAAM,CAAC,SAAS,eAAe,cAAc,OAAO,iBAEhD;GACE,EAAE,OAAO,KAAK,IAAK,UAAU;GAC7B,EAAE,OAAO,KAAK,IAAK,aAAa;GAChC,EAAE,OAAO,KAAK,IAAK,aAAa;EAClC,CACJ;EAEA,IACE,OAAO,iBAEH,CAAC,QAAQ,QAAQ,eAAe,QAAQ,QAAQ,QAAQ,OAAO,KAC/D,CAAC,QAAQ,WAAW,UAAU,aAAa,KAC3C,CAAC,QAAQ,UAAU,OAAO,UAAU,cAAc,QAAQ,WAAW,UAAU,KAC/E,CAAC,QAAQ,UAAU,OAAO,UAAU,QAAQ,QAAQ,QAAQ,GAAG,KAC/D,CAAC,QAAQ,UAAU,OAAO,UAAU,WAAW,QAAQ,WAAW,UAAU,CAChF,GAEA,OAAO,KAAA;EAMT,OAAO;GACL;GACA,UAAA,OANsB,iBACtB,QAAQ,WAAW,OAAO;IAAE;IAAU;IAAS,YAAY;IAAe;GAAW,CAAC,CACxF,CAAC,CAAC,KAAK,OAAO,SAAS,OAAO,QAAQ,0BAA0B,CAAC;GAK/D,SACE,cAAc,KAAA,IACV,KAAA,IACA,OAAO,iBAAiB,EAAE,OAAO,KAAK,IAAK,UAAU,CAAC;EAC9D;CACF,CAAC;CAED,MAAM,aAAa,OAAO,WAAW,WACnC,UACA,WACA,SACA;EACA,MAAM,OAAO,OAAO,iBACZ,GAAQ,UAAU,EAAE,OAAO,IAAI,EAAE,QAAQ,EAAE,KAAK;cAC9C,EAAE,OAAO,QAAQ,QAAQ,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,QAAQ,UAAU,QAAQ,EAAE;cACpF,EAAE,OAAO,QAAQ,QAAQ,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,QAAQ,WAAW,SAAS,EAAE;QAC7F,UAAU,OAAO,IAAI,QAAQ,EAAE,GACnC,CAAC,CAAC,KAAK,OAAO,OAAO;EAErB,OAAO,KAAK,WAAW,IAAI,OAAO,iBAAiB,EAAE,OAAO,KAAK,IAAK,IAAI,CAAC,IAAI,KAAA;CACjF,CAAC;CAED,MAAM,kBAAkB,KAAU,WAAuC,aACvE,IAAI,QAAQ,QAAQ,cAAc,UAAU,YAC5C,IAAI,QAAQ,QAAQ,yBAAyB,UAAU,WAAW,aAClE,IAAI,QAAQ,QAAQ,qBAAqB,UAAU,WAAW,SAC9D,QAAQ,UAAU,OAAO,IAAI,QAAQ,QAAQ,YAAY,QAAQ,KACjE,IAAI,QAAQ,QAAQ,kBAAkB,UAAU,gBAChD,IAAI,QAAQ,QAAQ,sBAAsB,UAAU,SAAS,oBAC7D,IAAI,QAAQ,QAAQ,wBAAwB,UAAU,sBACtD,IAAI,QAAQ,QAAQ,qBAAqB,UAAU,mBACnD,IAAI,QAAQ,QAAQ,+BAA+B,UAAU;CAsP/D,OAAO;EAlPL,iBAAiB,eACf,OAAO,IAAI,aAAa;GACtB,MAAM,QAAQ,OAAO,OAAO;IAC1B,GAAG;IACH,YAAY,OAAO,OAAO,EAAE,GAAG,WAAW,WAAW,CAAC;GACxD,CAAC;GAED,OAAO;GAEP,MAAM,cAAc,OAAO,iBACnB,GAAQ,UAAU,EAAE,OAAO,IAAI,EAAE,QAAQ,EAAE,KAAK;gBAChD,EAAE,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,WAAW,MAAM,WAAW,SAAS,EAAE;gBAC9G,EAAE,OAAO,QAAQ,WAAW,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,OAAO,MAAM,WAAW,KAAK,GACxG,CAAC,CAAC,KAAK,OAAO,OAAO;GAErB,MAAM,aACJ,YAAY,WAAW,IACnB,OAAO,iBAAiB,EAAE,OAAO,YAAY,IAAK,IAAI,CAAC,IACvD,KAAA;GAEN,MAAM,WACJ,eAAe,KAAA,KACf,EAAE,OAAO,iBAAiB,QAAQ,WAAW,UAAU,UAAU,CAAC,KAC9D,KAAA,IACA,OAAO,SACL,WAAW,QAAQ,WAAW,YAC9B,MAAM,UACN,MAAM,YACN,KACF;GAEN,MAAM,YACJ,aAAa,KAAA,MACZ,MAAM,cAAc,KAAA,KACnB,SAAS,SAAS,SAAS,cAAc,MAAM,aAC7C,WACA,KAAA;GAEN,MAAM,YAAY,OAAO,SAAS,QAAQ,mBAAmB,QAAQ,qBAAqB;GAC1F,IAAI,OAAO;GAEX,OAAO,OAAO,OAAO;IACnB,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,UAAU,SAAS;IACpE,QAAW,YACT,OAAO,cAAc;KACnB,IAAI,MAAM,OAAO,OAAO,KAAK,YAAY,CAAC;KAC1C,OAAO;KAEP,OAAO,MACL,OAAO,IAAI,aAAa;MACtB,MAAM,UAAU,OAAO;MACvB,MAAM,MAAM,OAAO;MACnB,MAAM,WAAW,WAAW;MAI5B,OAAO,iBAEH,GAAG,GAAG,EAAE,OACN,QAAQ,QAAQ,aACd;OACE,UAAU,MAAM;OAChB,QAAQ,MAAM;OACd;OACA,YAAY,MAAM;OAClB,GAAI,aAAa,KAAA,IACb,CAAC,IACD;QACE,WAAW,SAAS,SAAS;QAC7B,cAAc,SAAS;QACvB,kBAAkB,SAAS,SAAS;QACpC,oBAAoB,SAAS;QAC7B,iBAAiB,SAAS;QAC1B,2BAA2B,SAAS;OACtC;OACJ,kBAAkB;OAClB,gBAAgB,MAAM,MAAM;OAC5B,sBAAsB,MAAM,MAAM;MACpC,GACA;OACE,GAAI,cAAc,KAAA,IACd,CAAC,IACD,EAAE,iBAAiB,UAAU,SAAS;OAC1C,OAAO;MACT,CACF,CACF,GACJ,CAAC,CAAC,KAAK,OAAO,OAAO;MAErB,OAAO,QACL;OACE,MAAM;OACN;OACA,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,SAAS;MAC3D,GACA,OACF;KACF,CAAC,CACH;IACF,CAAC;GACL,CAAC;EACH,CAAC,CAAC,CAAC,KAAK,OAAO;EACjB,gBAAgB,YAAY,YAC1B,MACE,OAAO,IAAI,aAAa;GACtB,MAAM,QAAQ,OAAO,OAAO;IAC1B,GAAG;IACH,GAAI,WAAW,WAAW,KAAA,IACtB,CAAC,IACD,EAAE,QAAQ,OAAO,OAAO,EAAE,GAAG,WAAW,OAAO,CAAC,EAAE;GACxD,CAAC;GAED,MAAM,UAAU,OAAO;GAEvB,MAAM,WACJ,MAAM,aAAa,KAAA,IACf,KAAA,IACA,OAAO,2BAA2B,MAAM,QAAQ;GAEtD,MAAM,aAAa,OAAO,WAAW,MAAM,UAAU,MAAM,WAAW,KAAK;GAE3E,IAAI,eAAe,KAAA,KAAa,WAAW,QAAQ,QAAQ,WAAW,WACpE,OAAO,QAAQ,YAAY,OAAO;GAKpC,MAAM,UACJ,MAAM,YAAY,cAAc,aAAa,KAAA,IACzC,KAAA,IACA,OAAO,SACL,WAAW,QAAQ,QAAQ,YAC3B,MAAM,UACN,SAAS,YACT,IACF;GAEN,MAAM,MAAM,QAAQ,UAChB,OAAO,WAAW,MAAM,UAAU,MAAM,WAAW,IAAI,IACvD;GAEJ,IAAI,QAAQ,KAAA,KAAa,IAAI,QAAQ,QAAQ,WAAW,WACtD,OAAO,QAAQ,YAAY,OAAO;GACpC,MAAM,MAAM,OAAO;GACnB,MAAM,WAAW,OAAO,QAAQ,cAAc,IAAI,QAAQ,QAAQ,SAAS;GAE3E,MAAM,WACJ,aAAa,KAAA,KACb,YAAY,KAAA,KACZ,SAAS,aAAa,MAAM,YAC5B,WAAW,OACX,eAAe,KAAK,UAAU,QAAQ,QAAQ,KAC9C,+BAA+B,QAAQ,UAAU,QAAQ;GAE3D,MAAM,WAAW,WAAW,aAAa;GACzC,MAAM,UAAU,QAAQ,UAAU,OAAO;GAEzC,OAAO,iBACC,GAAG,GAAG,EAAE,OAAO,GAAG,QAAQ,QAAQ,QAAQ,SAAS,CAAC,EAAE;gBACxD,EAAE,OAAO,QAAQ,QAAQ,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,QAAQ,UAAU,MAAM,QAAQ,EAAE;gBAC1F,EAAE,OAAO,QAAQ,QAAQ,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,QAAQ,WAAW,MAAM,SAAS,EAAE;gBAC7F,EAAE,OAAO,QAAQ,QAAQ,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,SAAS,GACnF,CAAC,CAAC,KAAK,OAAO,OAAO;GAErB,IAAI,YAAY,YAAY,KAAA,KAAa,aAAa,KAAA,GAAW;IAC/D,IAAI,WAAW,QAAQ;IAEvB,IAAI,MAAM,WAAW,KAAA,GAAW;KAC9B,IACE,MAAM,OAAO,oBAAoB,SAAS,mBAC1C,SAAS,MAAM,MAAM,OAAO,gBAAgB,MAAM,SAAS,MAAM,SAAS,QAAQ,GAElF,OAAO,OAAO,YAAY;KAE5B,MAAM,UAAU,OAAO,SACrB,QAAQ,kBACR,QAAQ,oBACV;KAEA,MAAM,SAAS,MAAM;KAErB,OAAO,iBACC,GAAG,GAAG,EAAE,OAAO,QAAQ,WAAW,eAAe,OAAO,cAAc,OAAO,CAAC,EAAE;oBAClF,EAAE,OAAO,QAAQ,WAAW,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,UAAU,MAAM,QAAQ,EAAE;oBAChG,EAAE,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,WAAW,QAAQ,QAAQ,EAAE;oBACpG,EAAE,OAAO,QAAQ,WAAW,YAAY,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,cAAc,SAAS,YAAY,EAAE;oBAC/G,EAAE,OAAO,QAAQ,WAAW,kBAAkB,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,oBAAoB,SAAS,kBAAkB,EAAE;oBACjI,EAAE,OAAO,QAAQ,WAAW,eAAe,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,iBAAiB,OAAO,eAAe,EAAE;oBACtH,EAAE,OAAO,QAAQ,WAAW,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,UAAU,SAAS,MAAM,OAAO,gBAAgB,CAAC,GAC7H,CAAC,CAAC,KAAK,OAAO,OAAO;KACrB,WAAW;MACT,GAAG;MACH,iBAAiB;MACjB,UAAU,MAAM,OAAO;KACzB;IACF;IAEA,MAAM,UAAU;IAEhB,MAAM,UAAU,OAAO,IAAI,aAAa;KACtC,MAAM,QAAQ,OAAO,SACnB,QAAQ,UACR,MAAM,UACN,SAAS,YACT,OACA,MAAM,SACR;KAEA,OACE,UAAU,KAAA,KACV,+BAA+B,MAAM,UAAU,OAAO,KACtD,MAAM,YAAY,KAAA,KAClB,MAAM,QAAQ,QAAQ,QAAQ,WAAW,cACzC,eAAe,MAAM,SAAS,UAAU,QAAQ,QAAQ,MACvD,OAAO,QAAQ,cAAc,MAAM,QAAQ,QAAQ,QAAQ,SAAS,OACnE,YACF,YAAY,OAAO;IAEvB,CAAC,CAAC,CAAC,KAAK,OAAO;IAEf,IAAI,EAAE,OAAO,UAAU,OAAO,OAAO,YAAY;IAIjD,MAAM,iBAAiB,OAAO,OAAO,cAAc,8BAA8B;IAEjF,IACE,OAAO,OAAO,cAAc,KAC5B,CAAC,eAAe,MAAM,SACpB,OAAO,QAAQ,UAAU,UACvB,QAAQ,OAAO,OAAO,OAAO,KAAK,YAAY,CAAC,CACjD,CACF,GAEA,OAAO,OAAO,YAAY;GAC9B;GAEA,OAAO;EACT,CAAC,CACH;CAGS;AACf,CAAC"}
|