@yielded/auth-persistence 0.1.0-beta.10

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 +27 -0
  2. package/dist/Adapter.d.mts +30 -0
  3. package/dist/Adapter.mjs +25 -0
  4. package/dist/index.d.mts +7 -0
  5. package/dist/index.mjs +7 -0
  6. package/dist/internal/PasswordPreparedPostconditions.d.mts +22 -0
  7. package/dist/internal/PasswordPreparedPostconditions.mjs +12 -0
  8. package/dist/internal/PasswordPreparedPostconditions.mjs.map +1 -0
  9. package/dist/internal/configuration.d.mts +174 -0
  10. package/dist/internal/configuration.mjs +7 -0
  11. package/dist/internal/configuration.mjs.map +1 -0
  12. package/dist/internal/email-kernel.d.mts +275 -0
  13. package/dist/internal/email-kernel.mjs +487 -0
  14. package/dist/internal/email-kernel.mjs.map +1 -0
  15. package/dist/internal/mapping-error.d.mts +10 -0
  16. package/dist/internal/mapping-error.mjs +30 -0
  17. package/dist/internal/mapping-error.mjs.map +1 -0
  18. package/dist/internal/models/common.d.mts +12 -0
  19. package/dist/internal/models/email-model.d.mts +353 -0
  20. package/dist/internal/models/email-model.mjs +21 -0
  21. package/dist/internal/models/email-model.mjs.map +1 -0
  22. package/dist/internal/models/passkey-model.d.mts +266 -0
  23. package/dist/internal/models/passkey-model.mjs +34 -0
  24. package/dist/internal/models/passkey-model.mjs.map +1 -0
  25. package/dist/internal/models/passkey-registration-ceremony-model.d.mts +49 -0
  26. package/dist/internal/models/passkey-registration-ceremony-model.mjs +18 -0
  27. package/dist/internal/models/passkey-registration-ceremony-model.mjs.map +1 -0
  28. package/dist/internal/models/passkey-write-model.d.mts +189 -0
  29. package/dist/internal/models/passkey-write-model.mjs +13 -0
  30. package/dist/internal/models/passkey-write-model.mjs.map +1 -0
  31. package/dist/internal/models/password-model.d.mts +328 -0
  32. package/dist/internal/models/password-model.mjs +23 -0
  33. package/dist/internal/models/password-model.mjs.map +1 -0
  34. package/dist/internal/models/proof-model.d.mts +285 -0
  35. package/dist/internal/models/proof-model.mjs +17 -0
  36. package/dist/internal/models/proof-model.mjs.map +1 -0
  37. package/dist/internal/models/session-model.d.mts +199 -0
  38. package/dist/internal/models/session-model.mjs +20 -0
  39. package/dist/internal/models/session-model.mjs.map +1 -0
  40. package/dist/internal/models/step-up-model.d.mts +66 -0
  41. package/dist/internal/models/step-up-model.mjs +9 -0
  42. package/dist/internal/models/step-up-model.mjs.map +1 -0
  43. package/dist/internal/oauth-app.d.mts +11 -0
  44. package/dist/internal/oauth-app.mjs +57 -0
  45. package/dist/internal/oauth-app.mjs.map +1 -0
  46. package/dist/internal/oauth-server.d.mts +10 -0
  47. package/dist/internal/oauth-server.mjs +66 -0
  48. package/dist/internal/oauth-server.mjs.map +1 -0
  49. package/dist/internal/passkey/admission.d.mts +6 -0
  50. package/dist/internal/passkey/admission.mjs +216 -0
  51. package/dist/internal/passkey/admission.mjs.map +1 -0
  52. package/dist/internal/passkey/credentials.d.mts +14 -0
  53. package/dist/internal/passkey/credentials.mjs +249 -0
  54. package/dist/internal/passkey/credentials.mjs.map +1 -0
  55. package/dist/internal/passkey/enrollment.mjs +138 -0
  56. package/dist/internal/passkey/enrollment.mjs.map +1 -0
  57. package/dist/internal/passkey/flow.d.mts +21 -0
  58. package/dist/internal/passkey/flow.mjs +452 -0
  59. package/dist/internal/passkey/flow.mjs.map +1 -0
  60. package/dist/internal/passkey/management.mjs +231 -0
  61. package/dist/internal/passkey/management.mjs.map +1 -0
  62. package/dist/internal/passkey/registration-ceremony.d.mts +9 -0
  63. package/dist/internal/passkey/registration-ceremony.mjs +129 -0
  64. package/dist/internal/passkey/registration-ceremony.mjs.map +1 -0
  65. package/dist/internal/passkey/registration-custody.d.mts +2 -0
  66. package/dist/internal/passkey/registration-custody.mjs +52 -0
  67. package/dist/internal/passkey/registration-custody.mjs.map +1 -0
  68. package/dist/internal/passkey/registration-write.mjs +211 -0
  69. package/dist/internal/passkey/registration-write.mjs.map +1 -0
  70. package/dist/internal/passkey/state.d.mts +10 -0
  71. package/dist/internal/passkey/state.mjs +234 -0
  72. package/dist/internal/passkey/state.mjs.map +1 -0
  73. package/dist/internal/passkey/target.d.mts +19 -0
  74. package/dist/internal/passkey/target.mjs +190 -0
  75. package/dist/internal/passkey/target.mjs.map +1 -0
  76. package/dist/internal/passkey/write-state.d.mts +17 -0
  77. package/dist/internal/passkey/write-state.mjs +345 -0
  78. package/dist/internal/passkey/write-state.mjs.map +1 -0
  79. package/dist/internal/passkey/write-target.mjs +192 -0
  80. package/dist/internal/passkey/write-target.mjs.map +1 -0
  81. package/dist/internal/passkey-kernel.d.mts +5592 -0
  82. package/dist/internal/passkey-kernel.mjs +49 -0
  83. package/dist/internal/passkey-kernel.mjs.map +1 -0
  84. package/dist/internal/passkeys.d.mts +7 -0
  85. package/dist/internal/passkeys.mjs +368 -0
  86. package/dist/internal/passkeys.mjs.map +1 -0
  87. package/dist/internal/password-kernel.d.mts +290 -0
  88. package/dist/internal/password-kernel.mjs +704 -0
  89. package/dist/internal/password-kernel.mjs.map +1 -0
  90. package/dist/internal/persistence.d.mts +25 -0
  91. package/dist/internal/persistence.mjs +291 -0
  92. package/dist/internal/persistence.mjs.map +1 -0
  93. package/dist/internal/phone-kernel.d.mts +162 -0
  94. package/dist/internal/phone-kernel.mjs +434 -0
  95. package/dist/internal/phone-kernel.mjs.map +1 -0
  96. package/dist/internal/proof-kernel.d.mts +93 -0
  97. package/dist/internal/proof-kernel.mjs +619 -0
  98. package/dist/internal/proof-kernel.mjs.map +1 -0
  99. package/dist/internal/query-operations.d.mts +61 -0
  100. package/dist/internal/registration-contract.d.mts +16 -0
  101. package/dist/internal/registration.mjs +66 -0
  102. package/dist/internal/registration.mjs.map +1 -0
  103. package/dist/internal/session-kernel.d.mts +48 -0
  104. package/dist/internal/session-kernel.mjs +737 -0
  105. package/dist/internal/session-kernel.mjs.map +1 -0
  106. package/dist/internal/sql-persistence.d.mts +10 -0
  107. package/dist/internal/sql-persistence.mjs +37 -0
  108. package/dist/internal/sql-persistence.mjs.map +1 -0
  109. package/dist/internal/sql-query.mjs +207 -0
  110. package/dist/internal/sql-query.mjs.map +1 -0
  111. package/dist/internal/sql-table.d.mts +36 -0
  112. package/dist/internal/sql-table.mjs +81 -0
  113. package/dist/internal/sql-table.mjs.map +1 -0
  114. package/dist/internal/standalone.d.mts +9 -0
  115. package/dist/internal/standalone.mjs +13 -0
  116. package/dist/internal/standalone.mjs.map +1 -0
  117. package/dist/internal/step-up-state.d.mts +49 -0
  118. package/dist/internal/step-up-state.mjs +32 -0
  119. package/dist/internal/step-up-state.mjs.map +1 -0
  120. package/dist/internal/storage-mapping.mjs +686 -0
  121. package/dist/internal/storage-mapping.mjs.map +1 -0
  122. package/dist/internal/storage-tables.d.mts +913 -0
  123. package/dist/internal/storage-tables.mjs +387 -0
  124. package/dist/internal/storage-tables.mjs.map +1 -0
  125. package/dist/internal/storage-validation.mjs +58 -0
  126. package/dist/internal/storage-validation.mjs.map +1 -0
  127. package/dist/internal/transaction-execution-kernel.d.mts +38 -0
  128. package/dist/internal/transaction-execution-kernel.mjs +116 -0
  129. package/dist/internal/transaction-execution-kernel.mjs.map +1 -0
  130. package/dist/internal/transaction-kernel.d.mts +77 -0
  131. package/dist/internal/transaction-kernel.mjs +260 -0
  132. package/dist/internal/transaction-kernel.mjs.map +1 -0
  133. package/package.json +1 -0
  134. package/src/Adapter.ts +291 -0
  135. package/src/index.ts +6 -0
  136. package/src/internal/PasswordPreparedPostconditions.ts +30 -0
  137. package/src/internal/configuration.ts +282 -0
  138. package/src/internal/email-kernel.ts +1535 -0
  139. package/src/internal/mapping-error.ts +36 -0
  140. package/src/internal/models/common.ts +11 -0
  141. package/src/internal/models/email-model.ts +559 -0
  142. package/src/internal/models/passkey-model.ts +390 -0
  143. package/src/internal/models/passkey-registration-ceremony-model.ts +72 -0
  144. package/src/internal/models/passkey-write-model.ts +297 -0
  145. package/src/internal/models/password-model.ts +513 -0
  146. package/src/internal/models/proof-model.ts +424 -0
  147. package/src/internal/models/session-model.ts +375 -0
  148. package/src/internal/models/step-up-model.ts +135 -0
  149. package/src/internal/oauth-app.ts +82 -0
  150. package/src/internal/oauth-server.ts +89 -0
  151. package/src/internal/passkey/admission.ts +361 -0
  152. package/src/internal/passkey/credentials.ts +511 -0
  153. package/src/internal/passkey/enrollment.ts +326 -0
  154. package/src/internal/passkey/flow.ts +1062 -0
  155. package/src/internal/passkey/management.ts +421 -0
  156. package/src/internal/passkey/registration-ceremony.ts +283 -0
  157. package/src/internal/passkey/registration-custody.ts +91 -0
  158. package/src/internal/passkey/registration-write.ts +426 -0
  159. package/src/internal/passkey/state.ts +353 -0
  160. package/src/internal/passkey/target.ts +586 -0
  161. package/src/internal/passkey/write-state.ts +681 -0
  162. package/src/internal/passkey/write-target.ts +452 -0
  163. package/src/internal/passkey-kernel.ts +104 -0
  164. package/src/internal/passkeys.ts +606 -0
  165. package/src/internal/password-kernel.ts +2124 -0
  166. package/src/internal/persistence.ts +588 -0
  167. package/src/internal/phone-kernel.ts +846 -0
  168. package/src/internal/proof-kernel.ts +1954 -0
  169. package/src/internal/query-operations.ts +81 -0
  170. package/src/internal/registration-contract.ts +22 -0
  171. package/src/internal/registration.ts +161 -0
  172. package/src/internal/session-kernel.ts +2217 -0
  173. package/src/internal/sql-persistence.ts +45 -0
  174. package/src/internal/sql-query.ts +298 -0
  175. package/src/internal/sql-table.ts +104 -0
  176. package/src/internal/standalone.ts +30 -0
  177. package/src/internal/step-up-state.ts +139 -0
  178. package/src/internal/storage-mapping.ts +708 -0
  179. package/src/internal/storage-tables.ts +408 -0
  180. package/src/internal/storage-validation.ts +117 -0
  181. package/src/internal/transaction-execution-kernel.ts +313 -0
  182. package/src/internal/transaction-kernel.ts +593 -0
@@ -0,0 +1,704 @@
1
+ import { PersistenceMappingError } from "./mapping-error.mjs";
2
+ import { CurrentProofSql } from "./proof-kernel.mjs";
3
+ import { requiredPasswordConstraints } from "./models/password-model.mjs";
4
+ import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
5
+ import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
6
+ import { snapshotAuthenticationEvidence } from "@yielded/auth/Sessions";
7
+ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
8
+ import { PasswordPersistence, PasswordUnavailable, snapshotPasswordCredential, snapshotPasswordRequirement, snapshotPasswordRevision } from "@yielded/auth/Password";
9
+ import { ProofUnavailable } from "@yielded/auth/Proofs";
10
+ //#region src/internal/password-kernel.ts
11
+ var CurrentPasswordSql = class extends Context.Service()("effect-auth/CurrentPasswordSql") {};
12
+ const makePasswordKernel = (operations, proofs) => {
13
+ const { and, eq, gte, inArray, lte, notExists, sql, column, updateValues } = operations;
14
+ const { completeProofPlanIn } = proofs;
15
+ const unavailable = () => PasswordUnavailable.make({});
16
+ const translateFailure = (effect) => reportPersistenceFailure(effect, (error) => Schema.is(PasswordUnavailable)(error) || Schema.is(ProofUnavailable)(error) || Schema.is(HookConfigurationError)(error)).pipe(Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, () => unavailable()))));
17
+ const nowMillis = DateTime.now.pipe(Effect.map(DateTime.toEpochMillis));
18
+ const validConstraints = (mapping) => Object.entries(requiredPasswordConstraints).every(([key, value]) => mapping.constraints[key] === value);
19
+ const selectRows = (query, locking) => locking && typeof query.for === "function" ? query.for("update") : query;
20
+ const subjectColumns = (mapping) => ({
21
+ id: column(mapping.subject.table, mapping.subject.id),
22
+ status: column(mapping.subject.table, mapping.subject.status),
23
+ securityRevision: column(mapping.subject.table, mapping.subject.securityRevision)
24
+ });
25
+ const identifierColumns = (mapping) => ({
26
+ namespace: column(mapping.identifier.table, mapping.identifier.namespace),
27
+ value: column(mapping.identifier.table, mapping.identifier.value),
28
+ subjectId: column(mapping.identifier.table, mapping.identifier.subjectId),
29
+ verifiedAt: column(mapping.identifier.table, mapping.identifier.verifiedAt),
30
+ bindingRevision: column(mapping.identifier.table, mapping.identifier.bindingRevision)
31
+ });
32
+ const credentialColumns = (mapping) => ({
33
+ moduleId: column(mapping.credential.table, mapping.credential.moduleId),
34
+ subjectId: column(mapping.credential.table, mapping.credential.subjectId),
35
+ credentialId: column(mapping.credential.table, mapping.credential.credentialId),
36
+ credentialRevision: column(mapping.credential.table, mapping.credential.credentialRevision),
37
+ verifierVersion: column(mapping.credential.table, mapping.credential.verifierVersion),
38
+ verifier: column(mapping.credential.table, mapping.credential.verifier),
39
+ normalization: column(mapping.credential.table, mapping.credential.normalization)
40
+ });
41
+ const authorityCredentialColumns = (mapping) => ({
42
+ subjectId: column(mapping.authorityCredential.table, mapping.authorityCredential.subjectId),
43
+ credentialId: column(mapping.authorityCredential.table, mapping.authorityCredential.credentialId),
44
+ revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision)
45
+ });
46
+ const attemptColumns = (mapping) => ({
47
+ moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
48
+ action: column(mapping.attempt.table, mapping.attempt.action),
49
+ attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
50
+ identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
51
+ identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
52
+ subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
53
+ credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
54
+ securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
55
+ credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
56
+ verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
57
+ identifierBindingRevision: column(mapping.attempt.table, mapping.attempt.identifierBindingRevision),
58
+ admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
59
+ deadline: column(mapping.attempt.table, mapping.attempt.deadline),
60
+ retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
61
+ state: column(mapping.attempt.table, mapping.attempt.state)
62
+ });
63
+ const scopeColumns = (mapping) => ({
64
+ moduleId: column(mapping.rateScope.table, mapping.rateScope.moduleId),
65
+ action: column(mapping.rateScope.table, mapping.rateScope.action),
66
+ scopeKind: column(mapping.rateScope.table, mapping.rateScope.scopeKind),
67
+ scopeKey: column(mapping.rateScope.table, mapping.rateScope.scopeKey)
68
+ });
69
+ const chargeColumns = (mapping) => ({
70
+ moduleId: column(mapping.charge.table, mapping.charge.moduleId),
71
+ action: column(mapping.charge.table, mapping.charge.action),
72
+ scopeKind: column(mapping.charge.table, mapping.charge.scopeKind),
73
+ scopeKey: column(mapping.charge.table, mapping.charge.scopeKey),
74
+ attemptId: column(mapping.charge.table, mapping.charge.attemptId),
75
+ occurredAt: column(mapping.charge.table, mapping.charge.occurredAt),
76
+ retentionUntil: column(mapping.charge.table, mapping.charge.retentionUntil)
77
+ });
78
+ const commandColumns = (mapping) => ({
79
+ moduleId: column(mapping.command.table, mapping.command.moduleId),
80
+ commandId: column(mapping.command.table, mapping.command.commandId),
81
+ action: column(mapping.command.table, mapping.command.action),
82
+ bindingDigest: column(mapping.command.table, mapping.command.bindingDigest),
83
+ decision: column(mapping.command.table, mapping.command.decision),
84
+ retentionUntil: column(mapping.command.table, mapping.command.retentionUntil)
85
+ });
86
+ const allocate = (mode, asyncValue, syncValue) => {
87
+ if (mode === "synchronous") return syncValue === void 0 ? Effect.fail(unavailable()) : Effect.try({
88
+ try: syncValue,
89
+ catch: (cause) => new PersistenceMappingError({
90
+ operation: "mapping",
91
+ cause
92
+ })
93
+ });
94
+ if (asyncValue !== void 0) return asyncValue;
95
+ return syncValue === void 0 ? Effect.fail(unavailable()) : Effect.try({
96
+ try: syncValue,
97
+ catch: (cause) => new PersistenceMappingError({
98
+ operation: "mapping",
99
+ cause
100
+ })
101
+ });
102
+ };
103
+ const allocateNextSecurityRevision = (mapping, mode, current) => {
104
+ const asyncValue = mapping.subject.nextSecurityRevision?.(current);
105
+ const syncValue = mapping.subject.nextSecurityRevisionSync === void 0 ? void 0 : () => mapping.subject.nextSecurityRevisionSync(current);
106
+ return allocate(mode, asyncValue ?? mapping.allocateRevision, syncValue ?? mapping.allocateRevisionSync);
107
+ };
108
+ const sameIdentifier = (left, right) => left.namespace === right.namespace && left.value === right.value;
109
+ const sameCredentialSnapshot = (left, right) => left.moduleId === right.moduleId && left.revision.subjectId === right.revision.subjectId && left.revision.securityRevision === right.revision.securityRevision && left.credentialId === right.credentialId && left.credentialRevision === right.credentialRevision && left.verifierVersion === right.verifierVersion && left.normalization === right.normalization && left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis && left.revision.credentials.length === right.revision.credentials.length && left.revision.credentials.every((entry) => right.revision.credentials.some((other) => other.credentialId === entry.credentialId && other.revision === entry.revision)) && left.identifierBindingRevision === right.identifierBindingRevision && sameIdentifier(left.identifier, right.identifier) && Redacted.value(left.verifier) === Redacted.value(right.verifier);
110
+ const evidenceSatisfiedAt = (evidence, requirement, now) => {
111
+ if (evidence.proofs.length > 64 || evidence.revision.credentials.length > 64) return false;
112
+ const revisions = /* @__PURE__ */ new Map();
113
+ for (const item of evidence.revision.credentials) {
114
+ if (revisions.has(item.credentialId)) return false;
115
+ revisions.set(item.credentialId, item.revision);
116
+ }
117
+ if (evidence.proofs.some((proof) => !revisions.has(proof.credentialId))) return false;
118
+ if (evidence.proofs.some((proof) => now < DateTime.toEpochMillis(proof.verifiedAt))) return false;
119
+ const eligible = evidence.proofs.filter((proof) => {
120
+ const age = now - DateTime.toEpochMillis(proof.verifiedAt);
121
+ return age >= 0 && age < requirement.maximumAgeMillis;
122
+ });
123
+ const factors = new Set(eligible.flatMap((proof) => proof.factors));
124
+ const credentials = new Set(eligible.map((proof) => proof.credentialId));
125
+ return requirement.alternatives.some((alternative) => alternative.factors.every((factor) => factors.has(factor)) && credentials.size >= alternative.minimumCredentials && eligible.some((proof) => (!alternative.userVerified || proof.userVerified) && (!alternative.phishingResistant || proof.phishingResistant)));
126
+ };
127
+ const snapshotMutationInput = Effect.fn("DrizzlePassword.snapshotMutationInput")(function* (input) {
128
+ const expectedRevision = snapshotPasswordRevision(input.expectedRevision);
129
+ const evidence = yield* snapshotAuthenticationEvidence(input.authorization.evidence).pipe(Effect.mapError(unavailable));
130
+ const requirement = yield* snapshotPasswordRequirement(input.authorization.requirement);
131
+ const credential = input.credential === void 0 ? void 0 : yield* snapshotPasswordCredential(input.credential);
132
+ return Object.freeze({
133
+ ...input,
134
+ expectedRevision,
135
+ replacement: Object.freeze({
136
+ verifier: Redacted.make(Redacted.value(input.replacement.verifier)),
137
+ normalization: input.replacement.normalization
138
+ }),
139
+ invalidation: Object.freeze({ ...input.invalidation }),
140
+ ...credential === void 0 ? {} : { credential },
141
+ authorization: Object.freeze({
142
+ challenge: Object.freeze({
143
+ ...input.authorization.challenge,
144
+ revision: snapshotPasswordRevision(input.authorization.challenge.revision)
145
+ }),
146
+ evidence,
147
+ requirement
148
+ })
149
+ });
150
+ });
151
+ const lockSubject = Effect.fn("DrizzlePassword.lockSubject")(function* (mapping, subjectId, locking) {
152
+ const database = yield* CurrentPasswordSql;
153
+ const nativeSubjectId = yield* mapping.subjectId.toNative(subjectId);
154
+ const c = subjectColumns(mapping);
155
+ return {
156
+ nativeSubjectId,
157
+ row: (yield* selectRows(database.select().from(mapping.subject.table).where(eq(c.id, nativeSubjectId)).limit(1), locking))[0]
158
+ };
159
+ });
160
+ const lockIdentifier = Effect.fn("Drizzle.lockIdentifier")(function* (mapping, identifier, locking) {
161
+ const database = yield* CurrentPasswordSql;
162
+ const c = identifierColumns(mapping);
163
+ return yield* selectRows(database.select().from(mapping.identifier.table).where(and(eq(c.namespace, identifier.namespace), eq(c.value, identifier.value))).limit(1), locking);
164
+ });
165
+ const readPasswordCredential = Effect.fn("Drizzle.readPasswordCredential")(function* (mapping, moduleId, nativeSubjectId, locking) {
166
+ const database = yield* CurrentPasswordSql;
167
+ const c = credentialColumns(mapping);
168
+ return yield* selectRows(database.select().from(mapping.credential.table).where(and(eq(c.moduleId, moduleId), eq(c.subjectId, nativeSubjectId))).limit(1), locking);
169
+ });
170
+ const resolveCredential = Effect.fn("DrizzlePassword.resolveCredential")(function* (mapping, moduleId, identifier, requestedSubjectId, locking) {
171
+ const database = yield* CurrentPasswordSql;
172
+ const discovered = (yield* lockIdentifier(mapping, identifier, false))[0];
173
+ if (discovered === void 0 || !mapping.identifier.isCurrent(discovered)) return void 0;
174
+ const discoveredNative = discovered[mapping.identifier.subjectId];
175
+ const subject = (yield* selectRows(database.select().from(mapping.subject.table).where(eq(subjectColumns(mapping).id, discoveredNative)).limit(1), locking))[0];
176
+ if (subject === void 0 || !mapping.subject.isActiveStatus(subject[mapping.subject.status])) return void 0;
177
+ const identifierRow = (yield* lockIdentifier(mapping, identifier, locking))[0];
178
+ if (identifierRow === void 0 || !mapping.identifier.isCurrent(identifierRow) || !mapping.subjectId.equals(discoveredNative, identifierRow[mapping.identifier.subjectId])) return void 0;
179
+ const subjectId = yield* mapping.subjectId.toSubject(discoveredNative);
180
+ if (requestedSubjectId !== void 0 && requestedSubjectId !== subjectId) return void 0;
181
+ const credential = (yield* readPasswordCredential(mapping, moduleId, discoveredNative, locking))[0];
182
+ if (credential === void 0) return {
183
+ nativeSubjectId: discoveredNative,
184
+ subject,
185
+ identifier: identifierRow
186
+ };
187
+ return {
188
+ nativeSubjectId: discoveredNative,
189
+ subject,
190
+ identifier: identifierRow,
191
+ credential,
192
+ snapshot: yield* mapping.credential.decode({
193
+ moduleId,
194
+ subject,
195
+ identifier: identifierRow,
196
+ credential
197
+ }).pipe(Effect.flatMap(snapshotPasswordCredential))
198
+ };
199
+ });
200
+ const scopeEntries = (keys, policy) => {
201
+ const values = [{
202
+ kind: "action",
203
+ key: keys.action,
204
+ ...policy.action
205
+ }, {
206
+ kind: "identifier",
207
+ key: keys.identifier,
208
+ ...policy.identifier
209
+ }];
210
+ if (keys.subject !== void 0) values.push({
211
+ kind: "subject",
212
+ key: keys.subject,
213
+ ...policy.subject
214
+ });
215
+ return values;
216
+ };
217
+ const lockScope = Effect.fn("DrizzlePassword.lockScope")(function* (mapping, configuration, moduleId, action, entry) {
218
+ const database = yield* CurrentPasswordSql;
219
+ const query = database.insert(mapping.rateScope.table).values(mapping.rateScope.encodeInsert({
220
+ moduleId,
221
+ action,
222
+ scopeKind: entry.kind,
223
+ scopeKey: entry.key
224
+ }));
225
+ yield* configuration.insertIfAbsent(query, mapping.rateScope.scopeKey, entry.key);
226
+ const c = scopeColumns(mapping);
227
+ yield* selectRows(database.select().from(mapping.rateScope.table).where(and(eq(c.moduleId, moduleId), eq(c.action, action), eq(c.scopeKind, entry.kind), eq(c.scopeKey, entry.key))).limit(1), configuration.locking);
228
+ });
229
+ const scopeAdmits = Effect.fn("DrizzlePassword.scopeAdmits")(function* (mapping, moduleId, action, entry, policy, identifier, nativeSubjectId, now) {
230
+ const database = yield* CurrentPasswordSql;
231
+ const c = chargeColumns(mapping);
232
+ if ((yield* selectRows(database.select({ attemptId: c.attemptId }).from(mapping.charge.table).where(and(eq(c.moduleId, moduleId), eq(c.action, action), eq(c.scopeKind, entry.kind), eq(c.scopeKey, entry.key), gte(c.occurredAt, mapping.encodeInstant(now - entry.windowMillis)))).limit(entry.limit), true)).length >= entry.limit) return false;
233
+ const a = attemptColumns(mapping);
234
+ const pendingWhere = entry.kind === "action" ? and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.state, "pending")) : entry.kind === "identifier" ? and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.identifierNamespace, identifier.namespace), eq(a.identifierValue, identifier.value), eq(a.state, "pending")) : and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.subjectId, nativeSubjectId), eq(a.state, "pending"));
235
+ return (yield* selectRows(database.select({ attemptId: a.attemptId }).from(mapping.attempt.table).where(pendingWhere).limit(policy.maximumPending), true)).length < policy.maximumPending;
236
+ });
237
+ const insertCharge = Effect.fn("Drizzle.insertCharge")(function* (mapping, moduleId, action, entry, attemptId, now) {
238
+ return yield* (yield* CurrentPasswordSql).insert(mapping.charge.table).values(mapping.charge.encodeInsert({
239
+ moduleId,
240
+ action,
241
+ scopeKind: entry.kind,
242
+ scopeKey: entry.key,
243
+ attemptId,
244
+ occurredAtMillis: now,
245
+ retentionUntilMillis: now + entry.windowMillis
246
+ }));
247
+ });
248
+ const owned = (database, mapping, configuration, body) => {
249
+ if (!validConstraints(mapping)) return Effect.fail(unavailable());
250
+ const run = coordinateCommit(() => database.transaction((transaction) => body.pipe(Effect.provideService(CurrentPasswordSql, transaction), Effect.provideService(CurrentProofSql, transaction))), { mode: configuration.mode }).pipe(Effect.map((result) => result.value));
251
+ return configuration.coordinated !== true ? Effect.gen(function* () {
252
+ if (yield* hasCommitScope) return yield* unavailable();
253
+ yield* configuration.standaloneGuard;
254
+ return yield* run;
255
+ }) : run;
256
+ };
257
+ const safeRead = (database, configuration, body) => Effect.gen(function* () {
258
+ if (configuration.coordinated !== true) {
259
+ if (yield* hasCommitScope) return yield* unavailable();
260
+ yield* configuration.standaloneGuard;
261
+ }
262
+ return yield* database.transaction((transaction) => body.pipe(Effect.provideService(CurrentPasswordSql, transaction), Effect.provideService(CurrentProofSql, transaction)));
263
+ });
264
+ const proofCompletionMatchesPassword = (input) => {
265
+ const completion = input.completion.input;
266
+ const binding = completion.binding;
267
+ if (completion.moduleId !== `${input.moduleId}/reset` || completion.purpose !== "password-reset" || binding._tag !== "Subject" || input.credential === void 0 || binding.revision.subjectId !== input.expectedRevision.subjectId || binding.revision.securityRevision !== input.expectedRevision.securityRevision || !sameIdentifier(binding.identifier, input.credential.identifier)) return false;
268
+ const expected = [...input.expectedRevision.credentials].sort((left, right) => left.credentialId.localeCompare(right.credentialId));
269
+ const actual = [...binding.revision.credentials].sort((left, right) => left.credentialId.localeCompare(right.credentialId));
270
+ return expected.length === actual.length && expected.every((item, index) => item.credentialId === actual[index].credentialId && item.revision === actual[index].revision);
271
+ };
272
+ const probeProofCompletion = Effect.fn("Drizzle.probeProofCompletion")(function* (configuration, input) {
273
+ const database = yield* CurrentPasswordSql;
274
+ if (configuration.proof === void 0) return yield* Effect.fail(unavailable());
275
+ const validProbe = { _tag: "PasswordProofCompletionValid" };
276
+ const plan = {
277
+ input,
278
+ prepare: (decision, journal, project) => journal.prepare(project(decision))
279
+ };
280
+ const probe = coordinateCommit(() => database.transaction((transaction) => completeProofPlanIn(configuration.proof.mapping, configuration.proof.configuration, plan, Effect.fail(validProbe), () => false).pipe(Effect.provideService(CurrentProofSql, transaction))), { mode: configuration.mode });
281
+ if (configuration.coordinated === true || (yield* hasCommitScope)) return yield* unavailable();
282
+ yield* configuration.standaloneGuard;
283
+ return yield* probe.pipe(Effect.as(false), Effect.catchCause((cause) => {
284
+ const reason = cause.reasons[0];
285
+ return cause.reasons.length === 1 && reason !== void 0 && Cause.isFailReason(reason) && reason.error === validProbe ? Effect.succeed(true) : Effect.failCause(cause);
286
+ }));
287
+ });
288
+ const validateMutationAuthority = Effect.fn("DrizzlePassword.validateMutationAuthority")(function* (mapping, configuration, input, action) {
289
+ const database = yield* CurrentPasswordSql;
290
+ const locked = yield* lockSubject(mapping, input.expectedRevision.subjectId, configuration.locking);
291
+ const row = locked.row;
292
+ if (row === void 0 || !mapping.subject.isActiveStatus(row[mapping.subject.status]) || row[mapping.subject.securityRevision] !== input.expectedRevision.securityRevision) return void 0;
293
+ if (input.authorization.challenge.moduleId !== input.moduleId || input.authorization.challenge.action !== action || input.authorization.challenge.commandId !== input.commandId || input.authorization.challenge.revision.subjectId !== input.expectedRevision.subjectId || input.authorization.challenge.revision.securityRevision !== input.expectedRevision.securityRevision || input.authorization.evidence.flowId !== input.commandId || input.authorization.evidence.bindingDigest !== input.authorization.challenge.bindingDigest || input.authorization.evidence.revision.subjectId !== input.expectedRevision.subjectId || input.authorization.evidence.revision.securityRevision !== input.expectedRevision.securityRevision || action === "add-password" && (input.credential !== void 0 || input.authorization.challenge.targetCredentialId !== void 0)) return void 0;
294
+ if (input.expectedRevision.credentials.some((expected) => !input.authorization.evidence.revision.credentials.some((actual) => actual.credentialId === expected.credentialId && actual.revision === expected.revision))) return void 0;
295
+ if (input.credential !== void 0) {
296
+ const identifier = (yield* lockIdentifier(mapping, input.credential.identifier, configuration.locking))[0];
297
+ if (identifier === void 0 || !mapping.identifier.isCurrent(identifier) || !mapping.subjectId.equals(identifier[mapping.identifier.subjectId], locked.nativeSubjectId) || identifier[mapping.identifier.bindingRevision] !== input.credential.identifierBindingRevision) return void 0;
298
+ }
299
+ const allExpected = [...input.expectedRevision.credentials, ...input.authorization.evidence.revision.credentials];
300
+ const expected = new Map(allExpected.map((item) => [item.credentialId, item]));
301
+ const ac = authorityCredentialColumns(mapping);
302
+ const credentialRows = expected.size === 0 ? [] : yield* selectRows(database.select().from(mapping.authorityCredential.table).where(and(eq(ac.subjectId, locked.nativeSubjectId), inArray(ac.credentialId, [...expected.keys()]))).orderBy(ac.credentialId), configuration.locking);
303
+ if (credentialRows.length !== expected.size) return void 0;
304
+ for (const actual of credentialRows) {
305
+ const wanted = expected.get(actual[mapping.authorityCredential.credentialId]);
306
+ if (wanted === void 0 || actual[mapping.authorityCredential.revision] !== wanted.revision || mapping.authorityCredential.status !== void 0 && mapping.authorityCredential.isActiveStatus?.(actual[mapping.authorityCredential.status]) !== true) return void 0;
307
+ expected.delete(wanted.credentialId);
308
+ }
309
+ if (expected.size !== 0) return void 0;
310
+ if (input.credential !== void 0) {
311
+ const identifier = (yield* lockIdentifier(mapping, input.credential.identifier, false))[0];
312
+ const credential = (yield* readPasswordCredential(mapping, input.moduleId, locked.nativeSubjectId, configuration.locking))[0];
313
+ if (identifier === void 0 || credential === void 0) return void 0;
314
+ const snapshot = yield* mapping.credential.decode({
315
+ moduleId: input.moduleId,
316
+ subject: row,
317
+ identifier,
318
+ credential
319
+ });
320
+ if (!sameCredentialSnapshot(snapshot, input.credential)) return void 0;
321
+ }
322
+ const currentRequirement = yield* mapping.subject.decodeActionRequirement(row, action);
323
+ const now = yield* nowMillis;
324
+ if (!evidenceSatisfiedAt(input.authorization.evidence, input.authorization.requirement, now) || !evidenceSatisfiedAt(input.authorization.evidence, currentRequirement, now)) return void 0;
325
+ if (input.invalidation.existingSessions === "immediate" && mapping.sessionInvalidation !== "same-authority-immediate") return void 0;
326
+ return {
327
+ ...locked,
328
+ now
329
+ };
330
+ });
331
+ const commandExists = Effect.fn("Drizzle.commandExists")(function* (mapping, moduleId, commandId, locking) {
332
+ const database = yield* CurrentPasswordSql;
333
+ const c = commandColumns(mapping);
334
+ return yield* selectRows(database.select().from(mapping.command.table).where(and(eq(c.moduleId, moduleId), eq(c.commandId, commandId))).limit(1), locking);
335
+ });
336
+ const insertCommand = Effect.fn("Drizzle.insertCommand")(function* (mapping, input, action, now) {
337
+ return yield* (yield* CurrentPasswordSql).insert(mapping.command.table).values(mapping.command.encodeInsert({
338
+ moduleId: input.moduleId,
339
+ commandId: input.commandId,
340
+ action,
341
+ bindingDigest: input.authorization.challenge.bindingDigest,
342
+ decision: "changed",
343
+ retentionUntilMillis: now + mapping.commandRetentionMillis
344
+ }));
345
+ });
346
+ const mutationPostconditions = Effect.fn("DrizzlePassword.mutationPostconditions")(function* (mapping, input, nativeSubjectId, subject, authorityCredential, now) {
347
+ if (subject === void 0 || !mapping.subject.isActiveStatus(subject[mapping.subject.status]) || authorityCredential === void 0 || mapping.authorityCredential.status !== void 0 && mapping.authorityCredential.isActiveStatus?.(authorityCredential[mapping.authorityCredential.status]) !== true) return false;
348
+ if (input.credential !== void 0) {
349
+ const identifier = (yield* lockIdentifier(mapping, input.credential.identifier, false))[0];
350
+ if (identifier === void 0 || !mapping.identifier.isCurrent(identifier) || !mapping.subjectId.equals(identifier[mapping.identifier.subjectId], nativeSubjectId) || identifier[mapping.identifier.bindingRevision] !== input.credential.identifierBindingRevision) return false;
351
+ const credential = (yield* readPasswordCredential(mapping, input.moduleId, nativeSubjectId, false))[0];
352
+ if (credential === void 0) return false;
353
+ if ((yield* mapping.credential.decode({
354
+ moduleId: input.moduleId,
355
+ subject,
356
+ identifier,
357
+ credential
358
+ })).identifierVerifiedAtMillis !== input.credential.identifierVerifiedAtMillis) return false;
359
+ }
360
+ const rows = yield* commandExists(mapping, input.moduleId, input.commandId, false);
361
+ const row = rows[0];
362
+ return rows.length === 1 && row[mapping.command.action] === input.authorization.challenge.action && row[mapping.command.bindingDigest] === input.authorization.challenge.bindingDigest && row[mapping.command.decision] === "changed" && (yield* mapping.decodeInstant(row[mapping.command.retentionUntil])) === now + mapping.commandRetentionMillis;
363
+ });
364
+ const addPasswordIn = Effect.fn("DrizzlePassword.addPasswordIn")(function* (mapping, configuration, input, revisions, commandNow) {
365
+ const database = yield* CurrentPasswordSql;
366
+ const { credentialId, credentialRevision, verifierVersion, nextSecurityRevision } = revisions;
367
+ const authority = yield* validateMutationAuthority(mapping, configuration, input, "add-password");
368
+ if (authority === void 0) return false;
369
+ if ((yield* commandExists(mapping, input.moduleId, input.commandId, true)).length) return false;
370
+ if ((yield* readPasswordCredential(mapping, input.moduleId, authority.nativeSubjectId, true)).length) return false;
371
+ yield* database.insert(mapping.credential.table).values(mapping.credential.encodeInsert({
372
+ moduleId: input.moduleId,
373
+ subjectId: authority.nativeSubjectId,
374
+ credentialId,
375
+ credentialRevision,
376
+ verifierVersion,
377
+ replacement: input.replacement
378
+ }));
379
+ yield* database.insert(mapping.authorityCredential.table).values(mapping.authorityCredential.encodeInsert({
380
+ subjectId: authority.nativeSubjectId,
381
+ credentialId,
382
+ revision: credentialRevision
383
+ }));
384
+ yield* database.update(mapping.subject.table).set(updateValues([[mapping.subject.securityRevision, nextSecurityRevision]])).where(and(eq(subjectColumns(mapping).id, authority.nativeSubjectId), eq(subjectColumns(mapping).securityRevision, input.expectedRevision.securityRevision)));
385
+ yield* insertCommand(mapping, input, "add-password", commandNow ?? authority.now);
386
+ const inserted = (yield* readPasswordCredential(mapping, input.moduleId, authority.nativeSubjectId, false))[0];
387
+ const authorityInserted = (yield* database.select().from(mapping.authorityCredential.table).where(and(eq(authorityCredentialColumns(mapping).subjectId, authority.nativeSubjectId), eq(authorityCredentialColumns(mapping).credentialId, credentialId), eq(authorityCredentialColumns(mapping).revision, credentialRevision))).limit(1))[0];
388
+ const subjectUpdated = (yield* database.select().from(mapping.subject.table).where(and(eq(subjectColumns(mapping).id, authority.nativeSubjectId), eq(subjectColumns(mapping).securityRevision, nextSecurityRevision))).limit(1))[0];
389
+ if (inserted === void 0 || inserted[mapping.credential.credentialId] !== credentialId || inserted[mapping.credential.credentialRevision] !== credentialRevision || inserted[mapping.credential.verifierVersion] !== verifierVersion || inserted[mapping.credential.verifier] !== Redacted.value(input.replacement.verifier) || inserted[mapping.credential.normalization] !== input.replacement.normalization || authorityInserted === void 0 || subjectUpdated === void 0 || !(yield* mutationPostconditions(mapping, input, authority.nativeSubjectId, subjectUpdated, authorityInserted, commandNow ?? authority.now))) return yield* unavailable();
390
+ return true;
391
+ });
392
+ const makeSqlPasswordPersistence = Effect.fn("makeSqlPasswordPersistence")(function* (database, mapping, configuration) {
393
+ const hooks = yield* LifecycleHooks;
394
+ return PasswordPersistence.of({
395
+ admitAttempt: (input, prepare) => Effect.gen(function* () {
396
+ const attemptId = yield* allocate(configuration.mode, mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
397
+ return yield* owned(database, mapping, configuration, Effect.gen(function* () {
398
+ const transaction = yield* CurrentPasswordSql;
399
+ const journal = yield* CurrentCommitJournal;
400
+ const resolved = yield* resolveCredential(mapping, input.moduleId, input.identifier, input.subjectId, configuration.locking);
401
+ const keys = mapping.scopeKeys({
402
+ moduleId: input.moduleId,
403
+ action: input.action,
404
+ identifier: input.identifier,
405
+ ...resolved === void 0 ? {} : { subjectId: resolved.snapshot?.revision.subjectId }
406
+ });
407
+ const scopes = scopeEntries(keys, input.policy);
408
+ const admitted = [];
409
+ yield* lockScope(mapping, configuration, input.moduleId, input.action, scopes[0]);
410
+ let currentNow = yield* nowMillis;
411
+ if (yield* scopeAdmits(mapping, input.moduleId, input.action, scopes[0], input.policy, input.identifier, resolved?.nativeSubjectId, currentNow)) admitted.push(scopes[0]);
412
+ if (admitted.length === 1) for (const entry of scopes.slice(1)) {
413
+ yield* lockScope(mapping, configuration, input.moduleId, input.action, entry);
414
+ currentNow = yield* nowMillis;
415
+ if (yield* scopeAdmits(mapping, input.moduleId, input.action, entry, input.policy, input.identifier, resolved?.nativeSubjectId, currentNow)) admitted.push(entry);
416
+ }
417
+ const now = yield* nowMillis;
418
+ for (const entry of admitted) yield* insertCharge(mapping, input.moduleId, input.action, entry, attemptId, now);
419
+ if (admitted.length !== scopes.length) return prepare({ _tag: "Denied" }, journal);
420
+ const snapshot = resolved?.snapshot;
421
+ const deadline = now + input.policy.attemptLifetimeMillis;
422
+ const retentionUntil = Math.max(deadline, ...scopes.map((entry) => now + entry.windowMillis));
423
+ yield* transaction.insert(mapping.attempt.table).values(mapping.attempt.encodeInsert({
424
+ moduleId: input.moduleId,
425
+ action: input.action,
426
+ attemptId,
427
+ identifier: input.identifier,
428
+ ...snapshot === void 0 ? {} : {
429
+ subjectId: snapshot.revision.subjectId,
430
+ credentialId: snapshot.credentialId,
431
+ securityRevision: snapshot.revision.securityRevision,
432
+ credentialRevision: snapshot.credentialRevision,
433
+ verifierVersion: snapshot.verifierVersion,
434
+ identifierBindingRevision: snapshot.identifierBindingRevision
435
+ },
436
+ admittedAtMillis: now,
437
+ deadlineMillis: deadline,
438
+ retentionUntilMillis: retentionUntil
439
+ }, {
440
+ ...resolved?.nativeSubjectId === void 0 ? {} : { nativeSubjectId: resolved.nativeSubjectId },
441
+ state: "pending"
442
+ }));
443
+ return prepare({
444
+ _tag: "Admitted",
445
+ attemptId,
446
+ ...snapshot === void 0 ? {} : { credential: snapshot }
447
+ }, journal);
448
+ }));
449
+ }).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
450
+ settleAttempt: (uncaptured, prepare) => Effect.gen(function* () {
451
+ const captured = uncaptured.captured === void 0 ? void 0 : yield* snapshotPasswordCredential(uncaptured.captured);
452
+ const input = {
453
+ ...uncaptured,
454
+ ...captured === void 0 ? {} : { captured }
455
+ };
456
+ return yield* owned(database, mapping, configuration, Effect.gen(function* () {
457
+ const transaction = yield* CurrentPasswordSql;
458
+ const journal = yield* CurrentCommitJournal;
459
+ const c = attemptColumns(mapping);
460
+ let row = (yield* selectRows(transaction.select().from(mapping.attempt.table).where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId))).limit(1), false))[0];
461
+ if (row === void 0 || row[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
462
+ let decision = "rejected";
463
+ let current;
464
+ let attemptMatches = false;
465
+ if (input.outcome === "verified" && input.captured !== void 0) {
466
+ current = yield* resolveCredential(mapping, input.moduleId, input.captured.identifier, input.captured.revision.subjectId, configuration.locking);
467
+ attemptMatches = input.captured.moduleId === input.moduleId && row[mapping.attempt.identifierNamespace] === input.captured.identifier.namespace && row[mapping.attempt.identifierValue] === input.captured.identifier.value && mapping.subjectId.equals(row[mapping.attempt.subjectId], current?.nativeSubjectId) && row[mapping.attempt.credentialId] === input.captured.credentialId && row[mapping.attempt.securityRevision] === input.captured.revision.securityRevision && row[mapping.attempt.credentialRevision] === input.captured.credentialRevision && row[mapping.attempt.verifierVersion] === input.captured.verifierVersion && row[mapping.attempt.identifierBindingRevision] === input.captured.identifierBindingRevision;
468
+ }
469
+ row = (yield* selectRows(transaction.select().from(mapping.attempt.table).where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId))).limit(1), configuration.locking))[0];
470
+ if (row === void 0 || row[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
471
+ const now = yield* nowMillis;
472
+ const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
473
+ if (attemptMatches && current?.snapshot !== void 0 && input.captured !== void 0 && deadline > now && sameCredentialSnapshot(current.snapshot, input.captured)) decision = "verified";
474
+ const prepared = prepare(decision, journal);
475
+ yield* transaction.update(mapping.attempt.table).set(updateValues([[mapping.attempt.state, decision]])).where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId), eq(c.state, "pending")));
476
+ if (decision === "verified" && input.rehash !== void 0 && current?.snapshot !== void 0) {
477
+ const cc = credentialColumns(mapping);
478
+ const nextVersion = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
479
+ yield* transaction.update(mapping.credential.table).set(mapping.credential.encodeVerifier(input.rehash.nextVerifier, nextVersion)).where(and(eq(cc.moduleId, input.moduleId), eq(cc.subjectId, current.nativeSubjectId), eq(cc.credentialId, current.snapshot.credentialId), eq(cc.credentialRevision, current.snapshot.credentialRevision), eq(cc.verifierVersion, input.rehash.expectedVersion), eq(cc.verifier, Redacted.value(input.rehash.expectedVerifier))));
480
+ }
481
+ return prepared;
482
+ }));
483
+ }).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
484
+ readForSubject: (input) => safeRead(database, configuration, Effect.gen(function* () {
485
+ const transaction = yield* CurrentPasswordSql;
486
+ const locked = yield* lockSubject(mapping, input.subjectId, false);
487
+ if (locked.row === void 0 || !mapping.subject.isActiveStatus(locked.row[mapping.subject.status])) return Option.none();
488
+ const credential = (yield* readPasswordCredential(mapping, input.moduleId, locked.nativeSubjectId, false))[0];
489
+ if (credential === void 0) return Option.none();
490
+ const ic = identifierColumns(mapping);
491
+ const identifier = (yield* transaction.select().from(mapping.identifier.table).where(eq(ic.subjectId, locked.nativeSubjectId))).find((row) => mapping.identifier.isCurrent(row));
492
+ if (identifier === void 0) return Option.none();
493
+ const snapshot = yield* mapping.credential.decode({
494
+ moduleId: input.moduleId,
495
+ subject: locked.row,
496
+ identifier,
497
+ credential
498
+ }).pipe(Effect.flatMap(snapshotPasswordCredential));
499
+ return Option.some(snapshot);
500
+ })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
501
+ recoveryTarget: (input) => safeRead(database, configuration, Effect.gen(function* () {
502
+ const resolved = yield* resolveCredential(mapping, input.moduleId, input.identifier, void 0, false);
503
+ return resolved?.snapshot === void 0 || resolved.snapshot.identifierVerifiedAtMillis === void 0 ? Option.none() : Option.some(resolved.snapshot);
504
+ })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
505
+ addIfAbsent: (uncaptured, prepare) => Effect.gen(function* () {
506
+ const input = yield* snapshotMutationInput(uncaptured);
507
+ const credentialId = yield* allocate(configuration.mode, mapping.allocateCredentialId, mapping.allocateCredentialIdSync);
508
+ const credentialRevision = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
509
+ const verifierVersion = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
510
+ const nextSecurityRevision = yield* allocateNextSecurityRevision(mapping, configuration.mode, input.expectedRevision.securityRevision);
511
+ if (nextSecurityRevision === input.expectedRevision.securityRevision) return yield* unavailable();
512
+ return yield* owned(database, mapping, configuration, Effect.gen(function* () {
513
+ const journal = yield* CurrentCommitJournal;
514
+ return prepare((yield* addPasswordIn(mapping, configuration, input, {
515
+ credentialId,
516
+ credentialRevision,
517
+ verifierVersion,
518
+ nextSecurityRevision
519
+ })) ? "changed" : "rejected", journal);
520
+ }));
521
+ }).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
522
+ replaceIfCurrent: (uncaptured, prepare) => Effect.gen(function* () {
523
+ const input = yield* snapshotMutationInput(uncaptured);
524
+ const credentialRevision = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
525
+ const verifierVersion = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
526
+ const nextSecurityRevision = yield* allocateNextSecurityRevision(mapping, configuration.mode, input.expectedRevision.securityRevision);
527
+ if (nextSecurityRevision === input.expectedRevision.securityRevision || input.credential !== void 0 && credentialRevision === input.credential.credentialRevision) return yield* unavailable();
528
+ return yield* owned(database, mapping, configuration, Effect.gen(function* () {
529
+ return yield* replaceIn(mapping, configuration, input, "change-password", {
530
+ credentialRevision,
531
+ verifierVersion,
532
+ nextSecurityRevision
533
+ }, prepare);
534
+ }));
535
+ }).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
536
+ checkReset: (input) => (configuration.proof === void 0 ? Effect.fail(unavailable()) : Effect.gen(function* () {
537
+ if (input.moduleId.length <= 6 || !input.moduleId.endsWith("/reset") || input.purpose !== "password-reset" || input.binding._tag !== "Subject") return false;
538
+ const binding = input.binding;
539
+ const passwordModuleId = input.moduleId.slice(0, -6);
540
+ return (yield* safeRead(database, configuration, Effect.gen(function* () {
541
+ const resolved = yield* resolveCredential(mapping, passwordModuleId, binding.identifier, binding.revision.subjectId, false);
542
+ return resolved?.snapshot !== void 0 && resolved.snapshot.revision.securityRevision === binding.revision.securityRevision && binding.revision.credentials.every((item) => resolved.snapshot.revision.credentials.some((actual) => actual.credentialId === item.credentialId && actual.revision === item.revision));
543
+ }))) ? yield* probeProofCompletion(configuration, input) : false;
544
+ })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
545
+ resetWithProof: (uncaptured, prepare) => (configuration.proof === void 0 ? Effect.fail(unavailable()) : Effect.gen(function* () {
546
+ const input = Object.freeze({
547
+ ...yield* snapshotMutationInput(uncaptured),
548
+ completion: uncaptured.completion
549
+ });
550
+ const credentialRevision = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
551
+ const verifierVersion = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
552
+ const nextSecurityRevision = yield* allocateNextSecurityRevision(mapping, configuration.mode, input.expectedRevision.securityRevision);
553
+ if (nextSecurityRevision === input.expectedRevision.securityRevision || input.credential !== void 0 && credentialRevision === input.credential.credentialRevision) return yield* unavailable();
554
+ return yield* owned(database, mapping, configuration, Effect.gen(function* () {
555
+ const journal = yield* CurrentCommitJournal;
556
+ if (!proofCompletionMatchesPassword(input)) return prepare("rejected", journal);
557
+ const authority = yield* validateMutationAuthority(mapping, configuration, input, "reset-password");
558
+ if (authority === void 0) return prepare("rejected", journal);
559
+ if ((yield* commandExists(mapping, input.moduleId, input.commandId, true)).length) return prepare("rejected", journal);
560
+ if (!(yield* currentExpectedCredential(mapping, input, authority.nativeSubjectId))) return prepare("rejected", journal);
561
+ let passwordReceipt;
562
+ yield* completeProofPlanIn(configuration.proof.mapping, configuration.proof.configuration, input.completion, Effect.gen(function* () {
563
+ return yield* writeReplacement(mapping, input, authority.nativeSubjectId, {
564
+ credentialRevision,
565
+ verifierVersion,
566
+ nextSecurityRevision
567
+ }, authority.now);
568
+ }), (decision) => {
569
+ passwordReceipt = prepare(decision === "completed" ? "changed" : "rejected", journal);
570
+ return decision;
571
+ });
572
+ return passwordReceipt ?? prepare("rejected", journal);
573
+ }));
574
+ })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
575
+ cleanupAttempts: (input, prepare) => owned(database, mapping, configuration, Effect.gen(function* () {
576
+ const transaction = yield* CurrentPasswordSql;
577
+ const journal = yield* CurrentCommitJournal;
578
+ const now = yield* nowMillis;
579
+ const nativeNow = mapping.encodeInstant(now);
580
+ let remaining = input.limit;
581
+ let hasMore = false;
582
+ const a = attemptColumns(mapping);
583
+ const c = chargeColumns(mapping);
584
+ const attempts = yield* selectRows(transaction.select({ attemptId: a.attemptId }).from(mapping.attempt.table).where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow))).limit(remaining + 1), false);
585
+ if (attempts.length > remaining) hasMore = true;
586
+ const attemptCandidates = attempts.slice(0, remaining);
587
+ remaining -= attemptCandidates.length;
588
+ const charges = yield* selectRows(transaction.select({
589
+ action: c.action,
590
+ scopeKind: c.scopeKind,
591
+ scopeKey: c.scopeKey,
592
+ attemptId: c.attemptId
593
+ }).from(mapping.charge.table).where(and(eq(c.moduleId, input.moduleId), lte(c.retentionUntil, nativeNow))).limit(remaining + 1), false);
594
+ if (charges.length > remaining) hasMore = true;
595
+ const chargeCandidates = charges.slice(0, remaining);
596
+ remaining -= chargeCandidates.length;
597
+ const sc = scopeColumns(mapping);
598
+ const scopes = yield* selectRows(transaction.select({
599
+ action: sc.action,
600
+ scopeKind: sc.scopeKind,
601
+ scopeKey: sc.scopeKey
602
+ }).from(mapping.rateScope.table).where(and(eq(sc.moduleId, input.moduleId), notExists(transaction.select({ one: sql`1` }).from(mapping.charge.table).where(and(eq(c.moduleId, sc.moduleId), eq(c.action, sc.action), eq(c.scopeKind, sc.scopeKind), eq(c.scopeKey, sc.scopeKey)))))).limit(remaining + 1), remaining === 0 ? false : configuration.locking);
603
+ if (scopes.length > remaining) hasMore = true;
604
+ const selectedScopes = scopes.slice(0, remaining);
605
+ remaining -= selectedScopes.length;
606
+ const selectedCharges = [];
607
+ for (const candidate of chargeCandidates) {
608
+ const locked = yield* selectRows(transaction.select({
609
+ action: c.action,
610
+ scopeKind: c.scopeKind,
611
+ scopeKey: c.scopeKey,
612
+ attemptId: c.attemptId
613
+ }).from(mapping.charge.table).where(and(eq(c.moduleId, input.moduleId), eq(c.action, candidate.action), eq(c.scopeKind, candidate.scopeKind), eq(c.scopeKey, candidate.scopeKey), eq(c.attemptId, candidate.attemptId), lte(c.retentionUntil, nativeNow))).limit(1), configuration.locking);
614
+ if (locked[0] !== void 0) selectedCharges.push(locked[0]);
615
+ }
616
+ const selectedAttempts = [];
617
+ for (const candidate of attemptCandidates) {
618
+ const locked = yield* selectRows(transaction.select({ attemptId: a.attemptId }).from(mapping.attempt.table).where(and(eq(a.moduleId, input.moduleId), eq(a.attemptId, candidate.attemptId), lte(a.retentionUntil, nativeNow))).limit(1), configuration.locking);
619
+ if (locked[0] !== void 0) selectedAttempts.push(locked[0]);
620
+ }
621
+ const prepared = prepare({
622
+ removed: selectedAttempts.length + selectedCharges.length + selectedScopes.length,
623
+ hasMore
624
+ }, journal);
625
+ for (const row of selectedCharges) yield* transaction.delete(mapping.charge.table).where(and(eq(c.moduleId, input.moduleId), eq(c.action, row.action), eq(c.scopeKind, row.scopeKind), eq(c.scopeKey, row.scopeKey), eq(c.attemptId, row.attemptId), lte(c.retentionUntil, nativeNow)));
626
+ if (selectedAttempts.length > 0) yield* transaction.delete(mapping.attempt.table).where(and(eq(a.moduleId, input.moduleId), inArray(a.attemptId, selectedAttempts.map((row) => row.attemptId)), lte(a.retentionUntil, nativeNow)));
627
+ for (const row of selectedScopes) yield* transaction.delete(mapping.rateScope.table).where(and(eq(sc.moduleId, input.moduleId), eq(sc.action, row.action), eq(sc.scopeKind, row.scopeKind), eq(sc.scopeKey, row.scopeKey), notExists(transaction.select({ one: sql`1` }).from(mapping.charge.table).where(and(eq(c.moduleId, sc.moduleId), eq(c.action, sc.action), eq(c.scopeKind, sc.scopeKind), eq(c.scopeKey, sc.scopeKey))))));
628
+ return prepared;
629
+ })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure)
630
+ });
631
+ });
632
+ const currentExpectedCredential = Effect.fn("DrizzlePassword.currentExpectedCredential")(function* (mapping, input, nativeSubjectId) {
633
+ if (input.credential === void 0) return false;
634
+ const row = (yield* readPasswordCredential(mapping, input.moduleId, nativeSubjectId, true))[0];
635
+ if (row === void 0) return false;
636
+ return row[mapping.credential.credentialId] === input.credential.credentialId && row[mapping.credential.credentialRevision] === input.credential.credentialRevision && row[mapping.credential.verifierVersion] === input.credential.verifierVersion && row[mapping.credential.verifier] === Redacted.value(input.credential.verifier) && row[mapping.credential.normalization] === input.credential.normalization && input.authorization.challenge.targetCredentialId === input.credential.credentialId;
637
+ });
638
+ const checkMutationApplied = Effect.fn("DrizzlePassword.checkMutationApplied")(function* (mapping, input, nativeSubjectId, revisions, now) {
639
+ const database = yield* CurrentPasswordSql;
640
+ const c = credentialColumns(mapping), s = subjectColumns(mapping);
641
+ const credential = (yield* database.select().from(mapping.credential.table).where(and(eq(c.moduleId, input.moduleId), eq(c.subjectId, nativeSubjectId), eq(c.credentialId, revisions.credentialId), eq(c.credentialRevision, revisions.credentialRevision), eq(c.verifierVersion, revisions.verifierVersion), eq(c.verifier, Redacted.value(input.replacement.verifier)), eq(c.normalization, input.replacement.normalization))).limit(1))[0];
642
+ const subject = (yield* database.select().from(mapping.subject.table).where(and(eq(s.id, nativeSubjectId), eq(s.securityRevision, revisions.nextSecurityRevision))).limit(1))[0];
643
+ const authorityCredential = (yield* database.select().from(mapping.authorityCredential.table).where(and(eq(authorityCredentialColumns(mapping).subjectId, nativeSubjectId), eq(authorityCredentialColumns(mapping).credentialId, revisions.credentialId), eq(authorityCredentialColumns(mapping).revision, revisions.credentialRevision))).limit(1))[0];
644
+ return credential !== void 0 && (yield* mutationPostconditions(mapping, input, nativeSubjectId, subject, authorityCredential, now));
645
+ });
646
+ const writeReplacement = Effect.fn("DrizzlePassword.writeReplacement")(function* (mapping, input, nativeSubjectId, revisions, now) {
647
+ const database = yield* CurrentPasswordSql;
648
+ const c = credentialColumns(mapping);
649
+ const s = subjectColumns(mapping);
650
+ yield* database.update(mapping.credential.table).set(mapping.credential.encodeReplacement({
651
+ replacement: input.replacement,
652
+ credentialRevision: revisions.credentialRevision,
653
+ verifierVersion: revisions.verifierVersion
654
+ })).where(and(eq(c.moduleId, input.moduleId), eq(c.subjectId, nativeSubjectId), eq(c.credentialId, input.credential.credentialId), eq(c.credentialRevision, input.credential.credentialRevision), eq(c.verifierVersion, input.credential.verifierVersion), eq(c.verifier, Redacted.value(input.credential.verifier)), eq(c.normalization, input.credential.normalization)));
655
+ yield* database.update(mapping.subject.table).set(updateValues([[mapping.subject.securityRevision, revisions.nextSecurityRevision]])).where(and(eq(s.id, nativeSubjectId), eq(s.securityRevision, input.expectedRevision.securityRevision)));
656
+ yield* database.update(mapping.authorityCredential.table).set(mapping.authorityCredential.encodeRevision(revisions.credentialRevision)).where(and(eq(authorityCredentialColumns(mapping).subjectId, nativeSubjectId), eq(authorityCredentialColumns(mapping).credentialId, input.credential.credentialId), eq(authorityCredentialColumns(mapping).revision, input.credential.credentialRevision)));
657
+ yield* insertCommand(mapping, input, input.authorization.challenge.action, now);
658
+ return yield* checkMutationApplied(mapping, input, nativeSubjectId, {
659
+ ...revisions,
660
+ credentialId: input.credential.credentialId
661
+ }, now);
662
+ });
663
+ const replaceIn = Effect.fn("Drizzle.replaceIn")(function* (mapping, configuration, input, action, revisions, prepare) {
664
+ const journal = yield* CurrentCommitJournal;
665
+ const authority = yield* validateMutationAuthority(mapping, configuration, input, action);
666
+ if (authority === void 0) return prepare("rejected", journal);
667
+ if ((yield* commandExists(mapping, input.moduleId, input.commandId, true)).length) return prepare("rejected", journal);
668
+ if (!(yield* currentExpectedCredential(mapping, input, authority.nativeSubjectId))) return prepare("rejected", journal);
669
+ const prepared = prepare("changed", journal);
670
+ if (!(yield* writeReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now))) return yield* unavailable();
671
+ return prepared;
672
+ });
673
+ return {
674
+ makeSqlPasswordPersistence,
675
+ passwordSqlKernel: {
676
+ allocate,
677
+ allocateNextSecurityRevision,
678
+ owned,
679
+ safeRead,
680
+ snapshotMutationInput,
681
+ lockSubject,
682
+ lockIdentifier,
683
+ readPasswordCredential,
684
+ resolveCredential,
685
+ validateMutationAuthority,
686
+ currentExpectedCredential,
687
+ commandExists,
688
+ addPasswordIn,
689
+ checkMutationApplied,
690
+ writeReplacement,
691
+ evidenceSatisfiedAt,
692
+ sameCredentialSnapshot,
693
+ subjectColumns,
694
+ identifierColumns,
695
+ credentialColumns,
696
+ authorityCredentialColumns,
697
+ proofCompletionMatchesPassword
698
+ }
699
+ };
700
+ };
701
+ //#endregion
702
+ export { CurrentPasswordSql, makePasswordKernel };
703
+
704
+ //# sourceMappingURL=password-kernel.mjs.map