@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,708 @@
1
+ import { randomId } from "@yielded/auth-crypto";
2
+ import { EmailCredentialSnapshot } from "@yielded/auth/Email";
3
+ import { LoginIdentifier } from "@yielded/auth/Identity";
4
+ import {
5
+ PasswordAttemptId,
6
+ PasswordCredentialSnapshot,
7
+ EncodedPasswordHash,
8
+ type PasswordReplacement,
9
+ } from "@yielded/auth/Password";
10
+ import {
11
+ ProofBinding,
12
+ ProofContinuationId,
13
+ ProofDeliveryId,
14
+ ProofId,
15
+ ProofPurpose,
16
+ ProofRequestId,
17
+ ProofRequestReceipt,
18
+ ProofVersion,
19
+ } from "@yielded/auth/Proofs";
20
+ import { TokenDigest } from "@yielded/auth/Schema";
21
+ import {
22
+ SecurityRevision,
23
+ SessionId,
24
+ SessionMetadata,
25
+ SessionAuthenticationProvenance,
26
+ SessionCredentialVersion,
27
+ type StatefulSessionRecord,
28
+ } from "@yielded/auth/Sessions";
29
+ import { DateTime, Effect, Redacted, Schema } from "effect";
30
+
31
+ import type { MappingInput } from "./configuration";
32
+ import { PersistenceMappingError } from "./mapping-error";
33
+ import { requiredEmailAddressConstraints, type AnyEmailAddressMapping } from "./models/email-model";
34
+ import {
35
+ requiredPasswordConstraints,
36
+ type AnyPasswordPersistenceMapping,
37
+ } from "./models/password-model";
38
+ import { requiredProofConstraints, type AnyProofPersistenceMapping } from "./models/proof-model";
39
+ import type { StatefulSessionMapping } from "./models/session-model";
40
+ import type { TableModel, SqlFragment } from "./query-operations";
41
+ import { storageTables, type StorageRole } from "./storage-tables";
42
+
43
+ const failure = (cause: unknown) => PersistenceMappingError.make({ operation: "decode", cause });
44
+
45
+ const decode = <S extends Schema.Codec<unknown, unknown>>(schema: S, value: unknown) =>
46
+ Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(failure));
47
+
48
+ const string = (value: unknown) => Schema.decodeUnknownSync(Schema.String)(value);
49
+ const tuple = Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.String)));
50
+ const bindingJson = Schema.fromJsonString(ProofBinding);
51
+ const receiptJson = Schema.fromJsonString(ProofRequestReceipt);
52
+
53
+ const ProofRecord = Schema.Struct({
54
+ moduleId: Schema.String,
55
+ purpose: ProofPurpose,
56
+ proofId: ProofId,
57
+ requestId: ProofRequestId,
58
+ fingerprint: TokenDigest,
59
+ deliveryId: ProofDeliveryId,
60
+ binding: ProofBinding,
61
+ verifier: Schema.Struct({ keyId: Schema.String, digest: TokenDigest }),
62
+ issuedAtMillis: Schema.Int,
63
+ expiresAtMillis: Schema.Int,
64
+ version: ProofVersion,
65
+ });
66
+
67
+ const ProofContinuation = Schema.Struct({
68
+ moduleId: Schema.String,
69
+ purpose: ProofPurpose,
70
+ continuationId: ProofContinuationId,
71
+ digest: TokenDigest,
72
+ proofId: ProofId,
73
+ seriesKey: Schema.String,
74
+ binding: ProofBinding,
75
+ expiresAtMillis: Schema.Int,
76
+ version: ProofVersion,
77
+ });
78
+
79
+ export const makeMappings = (input: MappingInput) => {
80
+ const table = (role: StorageRole) => {
81
+ const found = input.tables[role];
82
+
83
+ if (found === undefined) throw failure(`Missing ${role} mapping`);
84
+
85
+ return found;
86
+ };
87
+
88
+ const mapped = (role: StorageRole) => ({
89
+ table: table(role),
90
+ ...Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key])),
91
+ });
92
+
93
+ const { subjects: s, encodeInstant: instant, decodeInstant: readInstant } = input;
94
+ const subjectId = { toNative: s.toNative, toSubject: s.toSubject, equals: Object.is };
95
+
96
+ const subject = {
97
+ table: s.table,
98
+ id: s.id,
99
+ status: s.status,
100
+ securityRevision: s.securityRevision,
101
+ isActiveStatus: (value: unknown) => Object.is(value, s.activeValue),
102
+ d1ActiveStatusValue: s.activeValue,
103
+ decodeRequirement: s.requirements,
104
+ decodeActionRequirement: s.actionRequirements,
105
+ nextSecurityRevisionSync: () => SecurityRevision.make(randomId()),
106
+ };
107
+
108
+ const authorityCredential = () => ({
109
+ ...mapped("credentials"),
110
+ subjectId: "subjectId",
111
+ credentialId: "credentialId",
112
+ revision: "revision",
113
+ status: "active",
114
+ isActiveStatus: (value: unknown) => value === true,
115
+ encodeInsert: (row: object) => ({ ...row, active: true }),
116
+ encodeRevision: (revision: SecurityRevision) => ({ revision }),
117
+ });
118
+
119
+ const authority = () => ({ subjectId, subject, credential: authorityCredential() });
120
+
121
+ const proofs = (): AnyProofPersistenceMapping<SqlFragment> => ({
122
+ constraints: requiredProofConstraints,
123
+ encodeInstant: instant,
124
+ decodeInstant: readInstant,
125
+ allocateVersionSync: () => ProofVersion.make(randomId()),
126
+ isRequestConflict: () => false,
127
+ isSeriesConflict: () => false,
128
+ isCommandConflict: () => false,
129
+ scopeKeys: ({ binding }) => ({
130
+ series: tuple([
131
+ binding.identifier.namespace,
132
+ binding.identifier.value,
133
+ binding._tag === "Identifier" ? "anonymous" : binding.revision.subjectId,
134
+ ]),
135
+ identifier: tuple([binding.identifier.namespace, binding.identifier.value]),
136
+ subject: binding._tag === "Identifier" ? "anonymous" : binding.revision.subjectId,
137
+ }),
138
+ authority: {
139
+ ...authority(),
140
+ identifier: {
141
+ table: table("identifiers"),
142
+ namespace: "namespace",
143
+ value: "value",
144
+ isCurrent: (request, rows) =>
145
+ request.binding._tag === "Identifier"
146
+ ? rows.length === 0
147
+ : request.binding._tag === "IdentifierChange"
148
+ ? rows.length === 0 ||
149
+ (rows.length === 1 &&
150
+ rows.every(
151
+ (row) =>
152
+ Object.is(row.subjectId, request.nativeSubjectId) &&
153
+ row.active === true &&
154
+ row.verifiedAt === null,
155
+ ))
156
+ : rows.length === 1 &&
157
+ rows.every(
158
+ (row) => Object.is(row.subjectId, request.nativeSubjectId) && row.active === true,
159
+ ),
160
+ },
161
+ },
162
+ request: {
163
+ ...mapped("proofRequests"),
164
+ moduleId: "moduleId",
165
+ requestId: "requestId",
166
+ fingerprint: "fingerprint",
167
+ proofId: "proofId",
168
+ purpose: "purpose",
169
+ keyId: "keyId",
170
+ createdAt: "createdAt",
171
+ retentionUntil: "retentionUntil",
172
+ encodeInsert: ({ record, retentionUntilMillis }) => ({
173
+ moduleId: record.moduleId,
174
+ requestId: record.requestId,
175
+ fingerprint: record.fingerprint,
176
+ proofId: record.proofId,
177
+ purpose: record.purpose,
178
+ keyId: record.verifier.keyId,
179
+ createdAt: instant(record.issuedAtMillis),
180
+ retentionUntil: instant(retentionUntilMillis),
181
+ receipt: Schema.encodeSync(receiptJson)({
182
+ requestId: record.requestId,
183
+ reference: {
184
+ proofId: record.proofId,
185
+ purpose: record.purpose,
186
+ keyId: record.verifier.keyId,
187
+ },
188
+ }),
189
+ }),
190
+ decodeReceipt: (row) => decode(receiptJson, row.receipt),
191
+ },
192
+ series: {
193
+ ...mapped("proofSeries"),
194
+ moduleId: "moduleId",
195
+ purpose: "purpose",
196
+ scopeKey: "scopeKey",
197
+ activeProofId: "activeProofId",
198
+ lastIssueAt: "lastIssueAt",
199
+ version: "version",
200
+ encodeInsert: (row) => ({ ...row, activeProofId: null, lastIssueAt: null }),
201
+ },
202
+ generation: {
203
+ ...mapped("proofGenerations"),
204
+ moduleId: "moduleId",
205
+ purpose: "purpose",
206
+ proofId: "proofId",
207
+ requestId: "requestId",
208
+ seriesKey: "seriesKey",
209
+ deliveryId: "deliveryId",
210
+ binding: "binding",
211
+ verifierKeyId: "verifierKeyId",
212
+ verifierDigest: "verifierDigest",
213
+ issuedAt: "issuedAt",
214
+ expiresAt: "expiresAt",
215
+ version: "version",
216
+ state: "state",
217
+ sendCount: "sendCount",
218
+ deliveryState: "deliveryState",
219
+ claimVersion: "claimVersion",
220
+ claimDeadline: "claimDeadline",
221
+ retryAt: "retryAt",
222
+ deliveryRetryMillis: "deliveryRetryMillis",
223
+ retentionUntil: "retentionUntil",
224
+ encodeInsert: ({
225
+ record,
226
+ seriesKey,
227
+ retentionUntilMillis,
228
+ state,
229
+ deliveryState,
230
+ policy,
231
+ }) => ({
232
+ moduleId: record.moduleId,
233
+ purpose: record.purpose,
234
+ proofId: record.proofId,
235
+ requestId: record.requestId,
236
+ seriesKey,
237
+ deliveryId: record.deliveryId,
238
+ binding: Schema.encodeSync(bindingJson)(record.binding),
239
+ verifierKeyId: record.verifier.keyId,
240
+ verifierDigest: record.verifier.digest,
241
+ issuedAt: instant(record.issuedAtMillis),
242
+ expiresAt: instant(record.expiresAtMillis),
243
+ version: record.version,
244
+ state,
245
+ sendCount: 0,
246
+ deliveryState,
247
+ claimVersion: null,
248
+ claimDeadline: null,
249
+ retryAt: null,
250
+ deliveryRetryMillis: policy.deliveryRetryMillis,
251
+ retentionUntil: instant(retentionUntilMillis),
252
+ fingerprint: record.fingerprint,
253
+ }),
254
+ decodeBinding: (row) => decode(bindingJson, row.binding),
255
+ decodeRecord: (row) =>
256
+ Effect.gen(function* () {
257
+ return yield* decode(ProofRecord, {
258
+ moduleId: row.moduleId,
259
+ purpose: row.purpose,
260
+ proofId: row.proofId,
261
+ requestId: row.requestId,
262
+ fingerprint: row.fingerprint,
263
+ deliveryId: row.deliveryId,
264
+ binding: yield* decode(bindingJson, row.binding),
265
+ verifier: { keyId: row.verifierKeyId, digest: row.verifierDigest },
266
+ issuedAtMillis: yield* readInstant(row.issuedAt),
267
+ expiresAtMillis: yield* readInstant(row.expiresAt),
268
+ version: row.version,
269
+ });
270
+ }),
271
+ },
272
+ continuation: {
273
+ ...mapped("proofContinuations"),
274
+ moduleId: "moduleId",
275
+ purpose: "purpose",
276
+ continuationId: "continuationId",
277
+ digest: "digest",
278
+ proofId: "proofId",
279
+ seriesKey: "seriesKey",
280
+ binding: "binding",
281
+ expiresAt: "expiresAt",
282
+ consumed: "consumed",
283
+ version: "version",
284
+ retentionUntil: "retentionUntil",
285
+ encodeInsert: (record) => ({
286
+ ...record,
287
+ binding: Schema.encodeSync(bindingJson)(record.binding),
288
+ expiresAt: instant(record.expiresAtMillis),
289
+ consumed: false,
290
+ retentionUntil: instant(record.retentionUntilMillis),
291
+ }),
292
+ decode: (row) =>
293
+ Effect.gen(function* () {
294
+ return yield* decode(ProofContinuation, {
295
+ ...row,
296
+ binding: yield* decode(bindingJson, row.binding),
297
+ expiresAtMillis: yield* readInstant(row.expiresAt),
298
+ });
299
+ }),
300
+ },
301
+ rateScope: {
302
+ ...mapped("proofScopes"),
303
+ moduleId: "moduleId",
304
+ purpose: "purpose",
305
+ action: "action",
306
+ scopeKind: "scopeKind",
307
+ scopeKey: "scopeKey",
308
+ encodeInsert: (row) => ({ ...row }),
309
+ },
310
+ abuseEvent: {
311
+ ...mapped("proofAbuse"),
312
+ moduleId: "moduleId",
313
+ purpose: "purpose",
314
+ action: "action",
315
+ scopeKind: "scopeKind",
316
+ scopeKey: "scopeKey",
317
+ commandId: "commandId",
318
+ occurredAt: "occurredAt",
319
+ retentionUntil: "retentionUntil",
320
+ encodeInsert: (row) => ({
321
+ ...row,
322
+ occurredAt: instant(row.occurredAtMillis),
323
+ retentionUntil: instant(row.retentionUntilMillis),
324
+ }),
325
+ },
326
+ failureEvent: {
327
+ ...mapped("proofFailures"),
328
+ moduleId: "moduleId",
329
+ purpose: "purpose",
330
+ seriesKey: "seriesKey",
331
+ commandId: "commandId",
332
+ occurredAt: "occurredAt",
333
+ retentionUntil: "retentionUntil",
334
+ encodeInsert: (row) => ({
335
+ ...row,
336
+ occurredAt: instant(row.occurredAtMillis),
337
+ retentionUntil: instant(row.retentionUntilMillis),
338
+ }),
339
+ },
340
+ command: {
341
+ ...mapped("proofCommands"),
342
+ moduleId: "moduleId",
343
+ commandId: "commandId",
344
+ kind: "kind",
345
+ decision: "decision",
346
+ retentionUntil: "retentionUntil",
347
+ encodeInsert: (row) => ({ ...row, retentionUntil: instant(row.retentionUntilMillis) }),
348
+ },
349
+ });
350
+
351
+ const passwords = (): AnyPasswordPersistenceMapping<SqlFragment> => {
352
+ const verifier = (replacement: PasswordReplacement) => ({
353
+ verifier: Redacted.value(replacement.verifier),
354
+ normalization: replacement.normalization,
355
+ });
356
+
357
+ return {
358
+ subjectId,
359
+ subject,
360
+ authorityCredential: authorityCredential(),
361
+ constraints: requiredPasswordConstraints,
362
+ isCommandConflict: () => false,
363
+ isRateScopeConflict: () => false,
364
+ scopeKeys: ({ moduleId, action, identifier, subjectId }) => ({
365
+ action: tuple([moduleId, action]),
366
+ identifier: tuple([identifier.namespace, identifier.value]),
367
+ ...(subjectId === undefined ? {} : { subject: subjectId }),
368
+ }),
369
+ encodeInstant: instant,
370
+ decodeInstant: readInstant,
371
+ allocateAttemptIdSync: () => PasswordAttemptId.make(randomId()),
372
+ allocateCredentialIdSync: randomId,
373
+ allocateRevisionSync: () => SecurityRevision.make(randomId()),
374
+ commandRetentionMillis: 86_400_000,
375
+ sessionInvalidation: "same-authority-immediate",
376
+ identifier: {
377
+ table: table("identifiers"),
378
+ namespace: "namespace",
379
+ value: "value",
380
+ subjectId: "subjectId",
381
+ verifiedAt: "verifiedAt",
382
+ bindingRevision: "revision",
383
+ isCurrent: (row) => row.active === true,
384
+ encodeInitialInsert: (identifier, subjectId, revision) => ({
385
+ ...identifier,
386
+ subjectId,
387
+ revision,
388
+ verifiedAt: null,
389
+ active: true,
390
+ }),
391
+ },
392
+ credential: {
393
+ ...mapped("passwords"),
394
+ moduleId: "moduleId",
395
+ subjectId: "subjectId",
396
+ credentialId: "credentialId",
397
+ credentialRevision: "credentialRevision",
398
+ verifierVersion: "verifierVersion",
399
+ verifier: "verifier",
400
+ normalization: "normalization",
401
+ encodeInsert: ({ replacement, ...row }) => ({ ...row, ...verifier(replacement) }),
402
+ encodeVerifier: (hash, verifierVersion) => ({
403
+ verifier: Redacted.value(hash),
404
+ verifierVersion,
405
+ }),
406
+ encodeReplacement: ({ replacement, ...row }) => ({ ...row, ...verifier(replacement) }),
407
+ decode: ({ moduleId, subject: row, identifier, credential }) =>
408
+ Effect.gen(function* () {
409
+ const id = yield* s.toSubject(row[s.id]);
410
+ const hash = yield* decode(EncodedPasswordHash, credential.verifier);
411
+
412
+ return yield* decode(PasswordCredentialSnapshot, {
413
+ moduleId,
414
+ revision: {
415
+ subjectId: id,
416
+ securityRevision: row[s.securityRevision],
417
+ credentials: [
418
+ {
419
+ credentialId: credential.credentialId,
420
+ revision: credential.credentialRevision,
421
+ },
422
+ ],
423
+ },
424
+ credentialId: credential.credentialId,
425
+ credentialRevision: credential.credentialRevision,
426
+ verifierVersion: credential.verifierVersion,
427
+ verifier: hash,
428
+ normalization: credential.normalization,
429
+ identifier: yield* decode(LoginIdentifier, {
430
+ namespace: identifier.namespace,
431
+ value: identifier.value,
432
+ }),
433
+ identifierBindingRevision: identifier.revision,
434
+ ...(identifier.verifiedAt === null
435
+ ? {}
436
+ : { identifierVerifiedAtMillis: yield* readInstant(identifier.verifiedAt) }),
437
+ });
438
+ }),
439
+ },
440
+ attempt: {
441
+ ...mapped("passwordAttempts"),
442
+ moduleId: "moduleId",
443
+ action: "action",
444
+ attemptId: "attemptId",
445
+ identifierNamespace: "identifierNamespace",
446
+ identifierValue: "identifierValue",
447
+ subjectId: "subjectId",
448
+ credentialId: "credentialId",
449
+ securityRevision: "securityRevision",
450
+ credentialRevision: "credentialRevision",
451
+ verifierVersion: "verifierVersion",
452
+ identifierBindingRevision: "identifierBindingRevision",
453
+ admittedAt: "admittedAt",
454
+ deadline: "deadline",
455
+ retentionUntil: "retentionUntil",
456
+ state: "state",
457
+ encodeInsert: (row, { nativeSubjectId, state }) => ({
458
+ moduleId: row.moduleId,
459
+ action: row.action,
460
+ attemptId: row.attemptId,
461
+ identifierNamespace: row.identifier.namespace,
462
+ identifierValue: row.identifier.value,
463
+ subjectId: nativeSubjectId ?? null,
464
+ credentialId: row.credentialId ?? null,
465
+ securityRevision: row.securityRevision ?? null,
466
+ credentialRevision: row.credentialRevision ?? null,
467
+ verifierVersion: row.verifierVersion ?? null,
468
+ identifierBindingRevision: row.identifierBindingRevision ?? null,
469
+ admittedAt: instant(row.admittedAtMillis),
470
+ deadline: instant(row.deadlineMillis),
471
+ retentionUntil: instant(row.retentionUntilMillis),
472
+ state,
473
+ }),
474
+ },
475
+ rateScope: {
476
+ ...mapped("passwordScopes"),
477
+ moduleId: "moduleId",
478
+ action: "action",
479
+ scopeKind: "scopeKind",
480
+ scopeKey: "scopeKey",
481
+ encodeInsert: (row) => ({ ...row }),
482
+ },
483
+ charge: {
484
+ ...mapped("passwordCharges"),
485
+ moduleId: "moduleId",
486
+ action: "action",
487
+ scopeKind: "scopeKind",
488
+ scopeKey: "scopeKey",
489
+ attemptId: "attemptId",
490
+ occurredAt: "occurredAt",
491
+ retentionUntil: "retentionUntil",
492
+ encodeInsert: (row) => ({
493
+ ...row,
494
+ occurredAt: instant(row.occurredAtMillis),
495
+ retentionUntil: instant(row.retentionUntilMillis),
496
+ }),
497
+ },
498
+ command: {
499
+ ...mapped("passwordCommands"),
500
+ moduleId: "moduleId",
501
+ commandId: "commandId",
502
+ action: "action",
503
+ bindingDigest: "bindingDigest",
504
+ decision: "decision",
505
+ retentionUntil: "retentionUntil",
506
+ encodeInsert: (row) => ({ ...row, retentionUntil: instant(row.retentionUntilMillis) }),
507
+ },
508
+ };
509
+ };
510
+
511
+ const emails = (): AnyEmailAddressMapping<SqlFragment> => ({
512
+ subjectId,
513
+ subject,
514
+ constraints: requiredEmailAddressConstraints,
515
+ addressCardinality: "single",
516
+ changeDisposition: "retire-source",
517
+ encodeInstant: instant,
518
+ decodeInstant: readInstant,
519
+ allocateCredentialIdSync: randomId,
520
+ allocateRevisionSync: () => SecurityRevision.make(randomId()),
521
+ commandRetentionMillis: 86_400_000,
522
+ sessionInvalidation: "same-authority-immediate",
523
+ isCommandConflict: () => false,
524
+ isIdentifierConflict: () => false,
525
+ isCredentialConflict: () => false,
526
+ authorityCredential: {
527
+ ...authorityCredential(),
528
+ encodeActivation: (revision) => ({ revision, active: true }),
529
+ encodeRetirement: (revision) => ({ revision, active: false }),
530
+ },
531
+ identifier: {
532
+ table: table("identifiers"),
533
+ namespace: "namespace",
534
+ value: "value",
535
+ subjectId: "subjectId",
536
+ verifiedAt: "verifiedAt",
537
+ bindingRevision: "revision",
538
+ isCurrent: (row) => row.active === true && row.verifiedAt !== null,
539
+ isMutableTarget: (row) => row.active === true && row.verifiedAt === null,
540
+ encodeVerifiedInsert: ({ identifier, subjectId, verifiedAtMillis, bindingRevision }) => ({
541
+ ...identifier,
542
+ subjectId,
543
+ verifiedAt: instant(verifiedAtMillis),
544
+ revision: bindingRevision,
545
+ active: true,
546
+ }),
547
+ encodeVerification: ({ verifiedAtMillis, bindingRevision }) => ({
548
+ verifiedAt: instant(verifiedAtMillis),
549
+ revision: bindingRevision,
550
+ active: true,
551
+ }),
552
+ encodeRetirement: ({ bindingRevision }) => ({ revision: bindingRevision, active: false }),
553
+ },
554
+ credential: {
555
+ table: table("emailCredentials"),
556
+ moduleId: "moduleId",
557
+ subjectId: "subjectId",
558
+ credentialId: "credentialId",
559
+ identifierNamespace: "identifierNamespace",
560
+ identifierValue: "identifierValue",
561
+ credentialRevision: "credentialRevision",
562
+ status: "active",
563
+ isActiveStatus: (value) => value === true,
564
+ encodeVerifiedInsert: ({ identifier, ...row }) => ({
565
+ ...row,
566
+ identifierNamespace: identifier.namespace,
567
+ identifierValue: identifier.value,
568
+ active: true,
569
+ }),
570
+ encodeActivation: ({ identifier, credentialRevision }) => ({
571
+ identifierNamespace: identifier.namespace,
572
+ identifierValue: identifier.value,
573
+ credentialRevision,
574
+ active: true,
575
+ }),
576
+ encodeRetirement: ({ credentialRevision }) => ({ credentialRevision, active: false }),
577
+ decode: ({ moduleId, subject: row, identifier, credential }) =>
578
+ Effect.gen(function* () {
579
+ return yield* decode(EmailCredentialSnapshot, {
580
+ moduleId,
581
+ identifier: { namespace: identifier.namespace, value: identifier.value },
582
+ identifierRevision: identifier.revision,
583
+ verifiedAtMillis: yield* readInstant(identifier.verifiedAt),
584
+ credentialId: credential.credentialId,
585
+ credentialRevision: credential.credentialRevision,
586
+ revision: {
587
+ subjectId: yield* s.toSubject(row[s.id]),
588
+ securityRevision: row[s.securityRevision],
589
+ credentials: [
590
+ { credentialId: credential.credentialId, revision: credential.credentialRevision },
591
+ ],
592
+ },
593
+ });
594
+ }),
595
+ },
596
+ command: {
597
+ table: table("emailCommands"),
598
+ moduleId: "moduleId",
599
+ commandId: "commandId",
600
+ action: "action",
601
+ bindingDigest: "bindingDigest",
602
+ retentionUntil: "retentionUntil",
603
+ encodeInsert: ({ retentionUntilMillis, ...row }) => ({
604
+ ...row,
605
+ retentionUntil: instant(retentionUntilMillis),
606
+ }),
607
+ },
608
+ });
609
+
610
+ const sessions = <C extends Schema.Codec<unknown, unknown, never, never>>(
611
+ claims: C,
612
+ ): StatefulSessionMapping<
613
+ C["Type"],
614
+ TableModel,
615
+ TableModel,
616
+ TableModel,
617
+ TableModel,
618
+ TableModel,
619
+ unknown,
620
+ string
621
+ > => {
622
+ const record = Schema.fromJsonString(
623
+ Schema.Struct({
624
+ ...SessionMetadata.fields,
625
+ claims: Schema.Unknown,
626
+ digest: TokenDigest,
627
+ version: SecurityRevision,
628
+ provenance: SessionAuthenticationProvenance,
629
+ credentialVersion: SessionCredentialVersion,
630
+ }),
631
+ );
632
+
633
+ const encodeRow = (value: StatefulSessionRecord<C["Type"]>) => ({
634
+ digest: value.digest,
635
+ version: value.version,
636
+ securityRevision: value.securityRevision,
637
+ issuedAt: instant(DateTime.toEpochMillis(value.issuedAt)),
638
+ expiresAt: instant(DateTime.toEpochMillis(value.expiresAt)),
639
+ absoluteExpiresAt: instant(DateTime.toEpochMillis(value.absoluteExpiresAt)),
640
+ record: Schema.encodeSync(record)({
641
+ ...value,
642
+ claims: Schema.encodeSync(claims)(value.claims),
643
+ }),
644
+ });
645
+
646
+ return {
647
+ ...authority(),
648
+ isConstraintConflict: () => false,
649
+ constraints: { sessionDigest: "unique(session.digest)", flowId: "unique(flow.flowId)" },
650
+ sessionId: {
651
+ toNative: (id) => Effect.succeed(id),
652
+ toSession: (id) => decode(SessionId, id),
653
+ equals: Object.is,
654
+ },
655
+ flow: {
656
+ table: table("sessionFlows"),
657
+ flowId: "flowId",
658
+ subjectId: "subjectId",
659
+ state: "state",
660
+ pendingDigest: "pendingDigest",
661
+ dedupUntil: "dedupUntil",
662
+ pendingStateValue: "pending",
663
+ establishedStateValue: "established",
664
+ encodeInstant: (date) => instant(DateTime.toEpochMillis(date)),
665
+ decodeInstant: (value) => readInstant(value).pipe(Effect.map(DateTime.makeUnsafe)),
666
+ encodePendingInsert: (row) => ({
667
+ flowId: row.evidence.flowId,
668
+ subjectId: row.subjectId,
669
+ state: "pending",
670
+ pendingDigest: row.pendingDigest,
671
+ dedupUntil: instant(DateTime.toEpochMillis(row.dedupUntil)),
672
+ }),
673
+ encodeEstablishedInsert: (row) => ({
674
+ flowId: row.evidence.flowId,
675
+ subjectId: row.subjectId,
676
+ state: "established",
677
+ pendingDigest: null,
678
+ dedupUntil: instant(DateTime.toEpochMillis(row.dedupUntil)),
679
+ }),
680
+ },
681
+ session: {
682
+ table: table("sessions"),
683
+ sessionId: "sessionId",
684
+ subjectId: "subjectId",
685
+ digest: "digest",
686
+ version: "version",
687
+ securityRevision: "securityRevision",
688
+ issuedAt: "issuedAt",
689
+ expiresAt: "expiresAt",
690
+ absoluteExpiresAt: "absoluteExpiresAt",
691
+ encodeInstant: (date) => instant(DateTime.toEpochMillis(date)),
692
+ allocateIdSync: randomId,
693
+ allocateVersionSync: () => SecurityRevision.make(randomId()),
694
+ encodeInsert: (value, ids) => ({ ...encodeRow(value), ...ids }),
695
+ encodeRotation: encodeRow,
696
+ decode: (row) =>
697
+ Effect.gen(function* () {
698
+ const saved = yield* decode(record, row.record);
699
+ const decodedClaims = yield* decode(claims, saved.claims);
700
+
701
+ return { ...saved, claims: decodedClaims };
702
+ }),
703
+ },
704
+ };
705
+ };
706
+
707
+ return { table, authority, proofs, passwords, emails, sessions, string };
708
+ };