@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.
Files changed (182) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +12 -6
  3. package/dist/Adapter.mjs +8 -3
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/configuration.d.mts +1 -1
  11. package/dist/internal/configuration.mjs.map +1 -1
  12. package/dist/internal/email-kernel.d.mts +1 -1
  13. package/dist/internal/email-kernel.mjs +1 -1
  14. package/dist/internal/email-kernel.mjs.map +1 -1
  15. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  16. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  17. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  18. package/dist/internal/models/oauth-model.d.mts +467 -0
  19. package/dist/internal/models/oauth-model.mjs +49 -0
  20. package/dist/internal/models/oauth-model.mjs.map +1 -0
  21. package/dist/internal/models/password-model.d.mts +6 -48
  22. package/dist/internal/models/password-model.mjs +0 -1
  23. package/dist/internal/models/password-model.mjs.map +1 -1
  24. package/dist/internal/native-sql-table.d.mts +4 -0
  25. package/dist/internal/native-sql-table.mjs +7 -0
  26. package/dist/internal/native-sql-table.mjs.map +1 -1
  27. package/dist/internal/oauth/accounts.d.mts +8 -0
  28. package/dist/internal/oauth/accounts.mjs +379 -0
  29. package/dist/internal/oauth/accounts.mjs.map +1 -0
  30. package/dist/internal/oauth/connected-access.d.mts +9 -0
  31. package/dist/internal/oauth/connected-access.mjs +229 -0
  32. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  33. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  34. package/dist/internal/oauth/connected-collection.mjs +214 -0
  35. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  36. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  37. package/dist/internal/oauth/connected-custody.mjs +165 -0
  38. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  39. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  40. package/dist/internal/oauth/connected-flow.mjs +324 -0
  41. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  42. package/dist/internal/oauth/connected-input.mjs +122 -0
  43. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  44. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  45. package/dist/internal/oauth/connected-keys.mjs +22 -0
  46. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  47. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  48. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  49. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  50. package/dist/internal/oauth/connected-management.d.mts +9 -0
  51. package/dist/internal/oauth/connected-management.mjs +243 -0
  52. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  53. package/dist/internal/oauth/connected-reference.mjs +63 -0
  54. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  55. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  56. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  57. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  58. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  59. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  60. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  61. package/dist/internal/oauth/connected-state.d.mts +14 -0
  62. package/dist/internal/oauth/connected-state.mjs +469 -0
  63. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  64. package/dist/internal/oauth/connected-target.d.mts +21 -0
  65. package/dist/internal/oauth/connected-target.mjs +117 -0
  66. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  67. package/dist/internal/oauth/flow.d.mts +5 -0
  68. package/dist/internal/oauth/flow.mjs +279 -0
  69. package/dist/internal/oauth/flow.mjs.map +1 -0
  70. package/dist/internal/oauth/input.mjs +127 -0
  71. package/dist/internal/oauth/input.mjs.map +1 -0
  72. package/dist/internal/oauth/owner.d.mts +10 -0
  73. package/dist/internal/oauth/owner.mjs +10 -0
  74. package/dist/internal/oauth/owner.mjs.map +1 -0
  75. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  76. package/dist/internal/oauth/query-compiler.mjs +9 -0
  77. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  78. package/dist/internal/oauth/registration.d.mts +7 -0
  79. package/dist/internal/oauth/registration.mjs +393 -0
  80. package/dist/internal/oauth/registration.mjs.map +1 -0
  81. package/dist/internal/oauth/state.mjs +107 -0
  82. package/dist/internal/oauth/state.mjs.map +1 -0
  83. package/dist/internal/oauth/target.d.mts +31 -0
  84. package/dist/internal/oauth/target.mjs +273 -0
  85. package/dist/internal/oauth/target.mjs.map +1 -0
  86. package/dist/internal/oauth-kernel.d.mts +1423 -0
  87. package/dist/internal/oauth-kernel.mjs +187 -0
  88. package/dist/internal/oauth-kernel.mjs.map +1 -0
  89. package/dist/internal/oauth-proxy.d.mts +31 -0
  90. package/dist/internal/oauth-proxy.mjs +129 -0
  91. package/dist/internal/oauth-proxy.mjs.map +1 -0
  92. package/dist/internal/passkey/write-state.mjs +9 -1
  93. package/dist/internal/passkey/write-state.mjs.map +1 -1
  94. package/dist/internal/passkey-kernel.d.mts +9 -9
  95. package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
  96. package/dist/internal/password-credentials.mjs +121 -0
  97. package/dist/internal/password-credentials.mjs.map +1 -0
  98. package/dist/internal/password-kernel.d.mts +8 -24
  99. package/dist/internal/password-kernel.mjs +5 -43
  100. package/dist/internal/password-kernel.mjs.map +1 -1
  101. package/dist/internal/persistence.d.mts +1 -1
  102. package/dist/internal/persistence.mjs +2 -2
  103. package/dist/internal/persistence.mjs.map +1 -1
  104. package/dist/internal/query-operations.d.mts +2 -0
  105. package/dist/internal/session-kernel.d.mts +2 -2
  106. package/dist/internal/session-kernel.mjs +74 -18
  107. package/dist/internal/session-kernel.mjs.map +1 -1
  108. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  109. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  110. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  111. package/dist/internal/sql-oauth-model.d.mts +60 -0
  112. package/dist/internal/sql-oauth-model.mjs +10 -0
  113. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  114. package/dist/internal/sql-query.d.mts +12 -0
  115. package/dist/internal/sql-query.mjs +15 -18
  116. package/dist/internal/sql-query.mjs.map +1 -1
  117. package/dist/internal/sql-storage-validation.mjs +54 -0
  118. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  119. package/dist/internal/storage-mapping.mjs +1 -37
  120. package/dist/internal/storage-mapping.mjs.map +1 -1
  121. package/dist/internal/storage-plans.d.mts +7 -0
  122. package/dist/internal/storage-plans.mjs +189 -0
  123. package/dist/internal/storage-plans.mjs.map +1 -0
  124. package/dist/internal/storage-tables.d.mts +0 -56
  125. package/dist/internal/storage-tables.mjs +0 -17
  126. package/dist/internal/storage-tables.mjs.map +1 -1
  127. package/dist/internal/storage-validation.d.mts +6 -1
  128. package/dist/internal/storage-validation.mjs +42 -1
  129. package/dist/internal/storage-validation.mjs.map +1 -1
  130. package/dist/internal/transaction-kernel.mjs +3 -2
  131. package/dist/internal/transaction-kernel.mjs.map +1 -1
  132. package/package.json +1 -1
  133. package/src/Adapter.ts +89 -5
  134. package/src/OAuthPersistence.ts +427 -0
  135. package/src/index.ts +3 -0
  136. package/src/internal/configuration.ts +1 -3
  137. package/src/internal/email-kernel.ts +2 -1
  138. package/src/internal/models/oauth-connected-model.ts +396 -0
  139. package/src/internal/models/oauth-model.ts +708 -0
  140. package/src/internal/models/password-model.ts +2 -57
  141. package/src/internal/native-sql-table.ts +14 -0
  142. package/src/internal/oauth/accounts.ts +839 -0
  143. package/src/internal/oauth/connected-access.ts +388 -0
  144. package/src/internal/oauth/connected-collection.ts +345 -0
  145. package/src/internal/oauth/connected-custody.ts +308 -0
  146. package/src/internal/oauth/connected-flow.ts +562 -0
  147. package/src/internal/oauth/connected-input.ts +147 -0
  148. package/src/internal/oauth/connected-keys.ts +31 -0
  149. package/src/internal/oauth/connected-maintenance.ts +379 -0
  150. package/src/internal/oauth/connected-management.ts +391 -0
  151. package/src/internal/oauth/connected-reference.ts +135 -0
  152. package/src/internal/oauth/connected-settlement.ts +278 -0
  153. package/src/internal/oauth/connected-sign-in.ts +472 -0
  154. package/src/internal/oauth/connected-state.ts +924 -0
  155. package/src/internal/oauth/connected-target.ts +307 -0
  156. package/src/internal/oauth/flow.ts +505 -0
  157. package/src/internal/oauth/input.ts +201 -0
  158. package/src/internal/oauth/owner.ts +22 -0
  159. package/src/internal/oauth/query-compiler.ts +15 -0
  160. package/src/internal/oauth/registration.ts +676 -0
  161. package/src/internal/oauth/state.ts +218 -0
  162. package/src/internal/oauth/target.ts +803 -0
  163. package/src/internal/oauth-kernel.ts +294 -0
  164. package/src/internal/oauth-proxy.ts +176 -0
  165. package/src/internal/passkey/write-state.ts +18 -4
  166. package/src/internal/password-credentials.ts +245 -0
  167. package/src/internal/password-kernel.ts +7 -99
  168. package/src/internal/persistence.ts +1 -1
  169. package/src/internal/query-operations.ts +2 -0
  170. package/src/internal/session-kernel.ts +143 -55
  171. package/src/internal/sql-oauth-connected-model.ts +145 -0
  172. package/src/internal/sql-oauth-model.ts +263 -0
  173. package/src/internal/sql-query.ts +16 -13
  174. package/src/internal/sql-storage-validation.ts +64 -0
  175. package/src/internal/storage-mapping.ts +0 -37
  176. package/src/internal/storage-plans.ts +218 -0
  177. package/src/internal/storage-tables.ts +0 -20
  178. package/src/internal/storage-validation.ts +58 -0
  179. package/src/internal/transaction-kernel.ts +6 -2
  180. package/dist/internal/password-attempts.mjs +0 -197
  181. package/dist/internal/password-attempts.mjs.map +0 -1
  182. 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
- invariant(rows.length === 1);
369
+ const row = rows[0];
370
370
 
371
- return rows[0]!.value === 1;
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":"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"}