@yielded/auth-persistence 0.1.0-beta.23 → 0.1.0-beta.24

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 (175) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +12 -6
  3. package/dist/Adapter.mjs +8 -3
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/configuration.d.mts +1 -1
  11. package/dist/internal/configuration.mjs.map +1 -1
  12. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  13. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  14. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  15. package/dist/internal/models/oauth-model.d.mts +467 -0
  16. package/dist/internal/models/oauth-model.mjs +49 -0
  17. package/dist/internal/models/oauth-model.mjs.map +1 -0
  18. package/dist/internal/models/password-model.d.mts +4 -47
  19. package/dist/internal/models/password-model.mjs +0 -1
  20. package/dist/internal/models/password-model.mjs.map +1 -1
  21. package/dist/internal/native-sql-table.d.mts +4 -0
  22. package/dist/internal/native-sql-table.mjs +7 -0
  23. package/dist/internal/native-sql-table.mjs.map +1 -1
  24. package/dist/internal/oauth/accounts.d.mts +8 -0
  25. package/dist/internal/oauth/accounts.mjs +379 -0
  26. package/dist/internal/oauth/accounts.mjs.map +1 -0
  27. package/dist/internal/oauth/connected-access.d.mts +9 -0
  28. package/dist/internal/oauth/connected-access.mjs +229 -0
  29. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  30. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  31. package/dist/internal/oauth/connected-collection.mjs +214 -0
  32. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  33. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  34. package/dist/internal/oauth/connected-custody.mjs +165 -0
  35. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  36. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  37. package/dist/internal/oauth/connected-flow.mjs +324 -0
  38. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  39. package/dist/internal/oauth/connected-input.mjs +122 -0
  40. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  41. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  42. package/dist/internal/oauth/connected-keys.mjs +22 -0
  43. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  44. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  45. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  46. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  47. package/dist/internal/oauth/connected-management.d.mts +9 -0
  48. package/dist/internal/oauth/connected-management.mjs +243 -0
  49. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  50. package/dist/internal/oauth/connected-reference.mjs +63 -0
  51. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  52. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  53. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  54. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  55. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  56. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  57. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  58. package/dist/internal/oauth/connected-state.d.mts +14 -0
  59. package/dist/internal/oauth/connected-state.mjs +469 -0
  60. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  61. package/dist/internal/oauth/connected-target.d.mts +21 -0
  62. package/dist/internal/oauth/connected-target.mjs +117 -0
  63. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  64. package/dist/internal/oauth/flow.d.mts +5 -0
  65. package/dist/internal/oauth/flow.mjs +279 -0
  66. package/dist/internal/oauth/flow.mjs.map +1 -0
  67. package/dist/internal/oauth/input.mjs +127 -0
  68. package/dist/internal/oauth/input.mjs.map +1 -0
  69. package/dist/internal/oauth/owner.d.mts +10 -0
  70. package/dist/internal/oauth/owner.mjs +10 -0
  71. package/dist/internal/oauth/owner.mjs.map +1 -0
  72. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  73. package/dist/internal/oauth/query-compiler.mjs +9 -0
  74. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  75. package/dist/internal/oauth/registration.d.mts +7 -0
  76. package/dist/internal/oauth/registration.mjs +393 -0
  77. package/dist/internal/oauth/registration.mjs.map +1 -0
  78. package/dist/internal/oauth/state.mjs +107 -0
  79. package/dist/internal/oauth/state.mjs.map +1 -0
  80. package/dist/internal/oauth/target.d.mts +31 -0
  81. package/dist/internal/oauth/target.mjs +273 -0
  82. package/dist/internal/oauth/target.mjs.map +1 -0
  83. package/dist/internal/oauth-kernel.d.mts +1423 -0
  84. package/dist/internal/oauth-kernel.mjs +187 -0
  85. package/dist/internal/oauth-kernel.mjs.map +1 -0
  86. package/dist/internal/oauth-proxy.d.mts +31 -0
  87. package/dist/internal/oauth-proxy.mjs +129 -0
  88. package/dist/internal/oauth-proxy.mjs.map +1 -0
  89. package/dist/internal/passkey-kernel.d.mts +5 -5
  90. package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
  91. package/dist/internal/password-credentials.mjs +121 -0
  92. package/dist/internal/password-credentials.mjs.map +1 -0
  93. package/dist/internal/password-kernel.d.mts +8 -24
  94. package/dist/internal/password-kernel.mjs +4 -42
  95. package/dist/internal/password-kernel.mjs.map +1 -1
  96. package/dist/internal/persistence.d.mts +1 -1
  97. package/dist/internal/persistence.mjs +2 -2
  98. package/dist/internal/persistence.mjs.map +1 -1
  99. package/dist/internal/query-operations.d.mts +2 -0
  100. package/dist/internal/session-kernel.d.mts +2 -2
  101. package/dist/internal/session-kernel.mjs +74 -18
  102. package/dist/internal/session-kernel.mjs.map +1 -1
  103. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  104. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  105. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  106. package/dist/internal/sql-oauth-model.d.mts +60 -0
  107. package/dist/internal/sql-oauth-model.mjs +10 -0
  108. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  109. package/dist/internal/sql-query.d.mts +12 -0
  110. package/dist/internal/sql-query.mjs +15 -18
  111. package/dist/internal/sql-query.mjs.map +1 -1
  112. package/dist/internal/sql-storage-validation.mjs +54 -0
  113. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  114. package/dist/internal/storage-mapping.mjs +1 -37
  115. package/dist/internal/storage-mapping.mjs.map +1 -1
  116. package/dist/internal/storage-plans.d.mts +7 -0
  117. package/dist/internal/storage-plans.mjs +189 -0
  118. package/dist/internal/storage-plans.mjs.map +1 -0
  119. package/dist/internal/storage-tables.d.mts +0 -56
  120. package/dist/internal/storage-tables.mjs +0 -17
  121. package/dist/internal/storage-tables.mjs.map +1 -1
  122. package/dist/internal/storage-validation.d.mts +6 -1
  123. package/dist/internal/storage-validation.mjs +42 -1
  124. package/dist/internal/storage-validation.mjs.map +1 -1
  125. package/dist/internal/transaction-kernel.mjs +3 -2
  126. package/dist/internal/transaction-kernel.mjs.map +1 -1
  127. package/package.json +1 -1
  128. package/src/Adapter.ts +89 -5
  129. package/src/OAuthPersistence.ts +427 -0
  130. package/src/index.ts +3 -0
  131. package/src/internal/configuration.ts +1 -3
  132. package/src/internal/models/oauth-connected-model.ts +396 -0
  133. package/src/internal/models/oauth-model.ts +708 -0
  134. package/src/internal/models/password-model.ts +0 -56
  135. package/src/internal/native-sql-table.ts +14 -0
  136. package/src/internal/oauth/accounts.ts +838 -0
  137. package/src/internal/oauth/connected-access.ts +388 -0
  138. package/src/internal/oauth/connected-collection.ts +345 -0
  139. package/src/internal/oauth/connected-custody.ts +308 -0
  140. package/src/internal/oauth/connected-flow.ts +562 -0
  141. package/src/internal/oauth/connected-input.ts +147 -0
  142. package/src/internal/oauth/connected-keys.ts +31 -0
  143. package/src/internal/oauth/connected-maintenance.ts +379 -0
  144. package/src/internal/oauth/connected-management.ts +391 -0
  145. package/src/internal/oauth/connected-reference.ts +135 -0
  146. package/src/internal/oauth/connected-settlement.ts +278 -0
  147. package/src/internal/oauth/connected-sign-in.ts +472 -0
  148. package/src/internal/oauth/connected-state.ts +924 -0
  149. package/src/internal/oauth/connected-target.ts +307 -0
  150. package/src/internal/oauth/flow.ts +505 -0
  151. package/src/internal/oauth/input.ts +201 -0
  152. package/src/internal/oauth/owner.ts +22 -0
  153. package/src/internal/oauth/query-compiler.ts +15 -0
  154. package/src/internal/oauth/registration.ts +676 -0
  155. package/src/internal/oauth/state.ts +218 -0
  156. package/src/internal/oauth/target.ts +803 -0
  157. package/src/internal/oauth-kernel.ts +294 -0
  158. package/src/internal/oauth-proxy.ts +176 -0
  159. package/src/internal/password-credentials.ts +245 -0
  160. package/src/internal/password-kernel.ts +5 -98
  161. package/src/internal/persistence.ts +1 -1
  162. package/src/internal/query-operations.ts +2 -0
  163. package/src/internal/session-kernel.ts +143 -55
  164. package/src/internal/sql-oauth-connected-model.ts +145 -0
  165. package/src/internal/sql-oauth-model.ts +263 -0
  166. package/src/internal/sql-query.ts +16 -13
  167. package/src/internal/sql-storage-validation.ts +64 -0
  168. package/src/internal/storage-mapping.ts +0 -37
  169. package/src/internal/storage-plans.ts +218 -0
  170. package/src/internal/storage-tables.ts +0 -20
  171. package/src/internal/storage-validation.ts +58 -0
  172. package/src/internal/transaction-kernel.ts +6 -2
  173. package/dist/internal/password-attempts.mjs +0 -197
  174. package/dist/internal/password-attempts.mjs.map +0 -1
  175. package/src/internal/password-attempts.ts +0 -466
@@ -0,0 +1,245 @@
1
+ import { CurrentCommitJournal, hasCommitScope } from "@yielded/auth/Hooks";
2
+ import type { LoginIdentifier } from "@yielded/auth/Identity";
3
+ import {
4
+ EncodedPasswordHash,
5
+ type PasswordPersistence,
6
+ PasswordUnavailable,
7
+ snapshotPasswordCredential,
8
+ type PasswordCredentialSnapshot,
9
+ } from "@yielded/auth/Password";
10
+ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
11
+ import { Effect, Option, Redacted, Schema } from "effect";
12
+
13
+ import type { PersistenceMappingError } from "./mapping-error";
14
+ import type { AnyPasswordPersistenceMapping } from "./models/password-model";
15
+ import { sqlMapping, type SqlTable } from "./native-sql-table";
16
+ import type { PasswordSqlConfiguration } from "./password-kernel";
17
+ import { NativeDatabase } from "./transaction-kernel";
18
+
19
+ type Row = Readonly<Record<string, unknown>>;
20
+ const unavailable = () => PasswordUnavailable.make({});
21
+
22
+ const failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
23
+ reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(
24
+ Effect.mapError(unavailable),
25
+ );
26
+
27
+ export const samePasswordCredentialSnapshot = (
28
+ a: PasswordCredentialSnapshot,
29
+ b: PasswordCredentialSnapshot,
30
+ ) =>
31
+ a.moduleId === b.moduleId &&
32
+ a.revision.subjectId === b.revision.subjectId &&
33
+ a.revision.securityRevision === b.revision.securityRevision &&
34
+ a.revision.credentials.length === b.revision.credentials.length &&
35
+ new Set(a.revision.credentials.map((entry) => entry.credentialId)).size ===
36
+ a.revision.credentials.length &&
37
+ new Set(b.revision.credentials.map((entry) => entry.credentialId)).size ===
38
+ b.revision.credentials.length &&
39
+ a.revision.credentials.every((entry) =>
40
+ b.revision.credentials.some(
41
+ (other) => entry.credentialId === other.credentialId && entry.revision === other.revision,
42
+ ),
43
+ ) &&
44
+ a.credentialId === b.credentialId &&
45
+ a.credentialRevision === b.credentialRevision &&
46
+ a.verifierVersion === b.verifierVersion &&
47
+ Redacted.value(a.verifier) === Redacted.value(b.verifier) &&
48
+ a.normalization === b.normalization &&
49
+ a.identifier.namespace === b.identifier.namespace &&
50
+ a.identifier.value === b.identifier.value &&
51
+ a.identifierBindingRevision === b.identifierBindingRevision &&
52
+ a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;
53
+
54
+ /** Lookup is read-only; rehash is one conditional maintenance write, never authentication. */
55
+ export const makePasswordCredentials = Effect.fnUntraced(function* (
56
+ table: (table: object) => SqlTable,
57
+ mapping: AnyPasswordPersistenceMapping,
58
+ options: PasswordSqlConfiguration,
59
+ ): Effect.fn.Return<
60
+ Pick<PasswordPersistence["Service"], "findCredential" | "rehashIfCurrent">,
61
+ PasswordUnavailable,
62
+ NativeDatabase
63
+ > {
64
+ const sql = (yield* NativeDatabase).$client.withoutTransforms();
65
+ const parent = yield* Effect.serviceOption(CurrentCommitJournal);
66
+
67
+ const [s, i, c] = yield* sqlMapping(
68
+ () =>
69
+ [
70
+ table(mapping.subject.table),
71
+ table(mapping.identifier.table),
72
+ table(mapping.credential.table),
73
+ ] as const,
74
+ ).pipe(failure);
75
+
76
+ const standalone = Effect.gen(function* () {
77
+ if (options.coordinated) {
78
+ const current = yield* Effect.serviceOption(CurrentCommitJournal);
79
+
80
+ if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value)
81
+ return yield* unavailable();
82
+ } else {
83
+ if (yield* hasCommitScope) return yield* unavailable();
84
+ yield* options.standaloneGuard;
85
+ }
86
+ });
87
+
88
+ const allocate = <A>(
89
+ asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,
90
+ sync: (() => A) | undefined,
91
+ ) => {
92
+ if (options.mode !== "synchronous" && asynchronous !== undefined) return asynchronous;
93
+
94
+ return sync === undefined
95
+ ? Effect.fail(unavailable())
96
+ : Effect.try({ try: sync, catch: unavailable });
97
+ };
98
+
99
+ // Bind each native ID through its own column codec. Physical encodings of the
100
+ // same application ID need not match across subject, identifier and password.
101
+ const snapshot = Effect.fnUntraced(function* (
102
+ nativeId: unknown,
103
+ moduleId: string,
104
+ identifier: LoginIdentifier,
105
+ ) {
106
+ const subjectTable = s.as("password_subject");
107
+ const identifierTable = i.as("password_identifier");
108
+ const credentialTable = c.as("password_credential");
109
+
110
+ const rows = yield* sqlMapping(
111
+ () =>
112
+ sql<Row>`select ${subjectTable.fields("subject_")}, ${identifierTable.fields("identifier_")}, ${credentialTable.fields("credential_")}
113
+ from ${subjectTable.name}
114
+ inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}
115
+ and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}
116
+ and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}
117
+ inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}
118
+ and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}
119
+ where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}
120
+ `,
121
+ ).pipe(Effect.flatten);
122
+
123
+ if (rows.length !== 1) return undefined;
124
+
125
+ const [subject, identifierRow, credential] = yield* sqlMapping(
126
+ () =>
127
+ [
128
+ s.decode(rows[0]!, "subject_"),
129
+ i.decode(rows[0]!, "identifier_"),
130
+ c.decode(rows[0]!, "credential_"),
131
+ ] as const,
132
+ );
133
+
134
+ if (
135
+ yield* sqlMapping(
136
+ () =>
137
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
138
+ !mapping.identifier.isCurrent(identifierRow) ||
139
+ !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) ||
140
+ !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) ||
141
+ !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]),
142
+ )
143
+ )
144
+ return undefined;
145
+
146
+ const captured = yield* sqlMapping(() =>
147
+ mapping.credential.decode({ moduleId, subject, identifier: identifierRow, credential }),
148
+ ).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));
149
+
150
+ return captured;
151
+ });
152
+
153
+ return {
154
+ findCredential: (uncaptured) =>
155
+ Effect.gen(function* () {
156
+ const input = { ...uncaptured, identifier: { ...uncaptured.identifier } };
157
+
158
+ yield* standalone;
159
+
160
+ const identifiers = yield* sqlMapping(
161
+ () => sql<Row>`select ${i.fields("i_")} from ${i.name}
162
+ where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}
163
+ and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`,
164
+ ).pipe(Effect.flatten);
165
+
166
+ const identifier =
167
+ identifiers.length === 1
168
+ ? yield* sqlMapping(() => i.decode(identifiers[0]!, "i_"))
169
+ : undefined;
170
+
171
+ if (
172
+ identifier === undefined ||
173
+ !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))
174
+ )
175
+ return Option.none();
176
+
177
+ const credential = yield* snapshot(
178
+ identifier[mapping.identifier.subjectId],
179
+ input.moduleId,
180
+ input.identifier,
181
+ );
182
+
183
+ return credential === undefined ||
184
+ (input.subjectId !== undefined && credential.revision.subjectId !== input.subjectId)
185
+ ? Option.none()
186
+ : Option.some(credential);
187
+ }).pipe(failure),
188
+ rehashIfCurrent: (input) =>
189
+ Effect.gen(function* () {
190
+ const nextVerifier = Redacted.make(
191
+ yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)),
192
+ );
193
+
194
+ const credential = yield* snapshotPasswordCredential(input.credential);
195
+
196
+ yield* standalone;
197
+ const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);
198
+ const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
199
+
200
+ if (version === credential.verifierVersion) return yield* unavailable();
201
+ yield* sqlMapping(() => {
202
+ const encoded = mapping.credential.encodeVerifier(nextVerifier, version);
203
+
204
+ // A custom encoder cannot turn maintenance into a semantic credential change.
205
+ const values = {
206
+ [mapping.credential.verifier]: encoded[mapping.credential.verifier],
207
+ [mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion],
208
+ };
209
+
210
+ if (Object.values(values).some((value) => value === undefined)) throw unavailable();
211
+
212
+ // Ordinary identity predicates retain index use; the full CAS ignores text collation.
213
+ const exact = sql.and(
214
+ Object.entries({
215
+ [mapping.credential.moduleId]: credential.moduleId,
216
+ [mapping.credential.subjectId]: nativeId,
217
+ [mapping.credential.credentialId]: credential.credentialId,
218
+ [mapping.credential.credentialRevision]: credential.credentialRevision,
219
+ [mapping.credential.verifierVersion]: credential.verifierVersion,
220
+ [mapping.credential.verifier]: Redacted.value(credential.verifier),
221
+ [mapping.credential.normalization]: credential.normalization,
222
+ }).map(([key, value]) => {
223
+ const column = c.column(key),
224
+ bound = c.value(key, value);
225
+
226
+ if (typeof value !== "string") return sql`${column} = ${bound}`;
227
+
228
+ return sql.onDialectOrElse({
229
+ mysql: () => sql`binary ${column} = binary ${bound}`,
230
+ pg: () =>
231
+ sql`convert_to(cast(${column} as text), 'UTF8') = convert_to(cast(${bound} as text), 'UTF8')`,
232
+ orElse: () => sql`cast(${column} as blob) = cast(${bound} as blob)`,
233
+ });
234
+ }),
235
+ );
236
+
237
+ return sql`${c.update(values)}
238
+ where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, credential.moduleId)}
239
+ and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, nativeId)}
240
+ and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, credential.credentialId)}
241
+ and ${exact}`;
242
+ }).pipe(Effect.flatten);
243
+ }).pipe(failure),
244
+ } satisfies Pick<PasswordPersistence["Service"], "findCredential" | "rehashIfCurrent">;
245
+ });
@@ -41,9 +41,9 @@ import {
41
41
  } from "./models/password-model";
42
42
  import { makeNativeSqlTables, sqlMapping, type NativeSqlTables } from "./native-sql-table";
43
43
  import {
44
- makePasswordAttempts,
44
+ makePasswordCredentials,
45
45
  samePasswordCredentialSnapshot as sameCredentialSnapshot,
46
- } from "./password-attempts";
46
+ } from "./password-credentials";
47
47
  import {
48
48
  CurrentPasswordPreparedTransaction,
49
49
  PasswordPreparedPostconditions,
@@ -119,7 +119,7 @@ export const makePasswordKernel = <
119
119
  ) => {
120
120
  type Mapping = AnyPasswordPersistenceMapping<Fragment>;
121
121
 
122
- const { and, eq, inArray, lte, column, updateValues } = operations;
122
+ const { and, eq, inArray, column, updateValues } = operations;
123
123
  const { completeProofPlanIn } = proofs;
124
124
  const unavailable = () => PasswordUnavailable.make({});
125
125
 
@@ -178,27 +178,6 @@ export const makePasswordKernel = <
178
178
  revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision),
179
179
  });
180
180
 
181
- const attemptColumns = (mapping: Mapping) => ({
182
- moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
183
- action: column(mapping.attempt.table, mapping.attempt.action),
184
- attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
185
- identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
186
- identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
187
- subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
188
- credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
189
- securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
190
- credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
191
- verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
192
- identifierBindingRevision: column(
193
- mapping.attempt.table,
194
- mapping.attempt.identifierBindingRevision,
195
- ),
196
- admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
197
- deadline: column(mapping.attempt.table, mapping.attempt.deadline),
198
- retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
199
- state: column(mapping.attempt.table, mapping.attempt.state),
200
- });
201
-
202
181
  const commandColumns = (mapping: Mapping) => ({
203
182
  moduleId: column(mapping.command.table, mapping.command.moduleId),
204
183
  commandId: column(mapping.command.table, mapping.command.commandId),
@@ -943,7 +922,7 @@ export const makePasswordKernel = <
943
922
  if (!validConstraints(mapping)) return yield* unavailable();
944
923
  const root = yield* NativeDatabase;
945
924
 
946
- const attempts = yield* makePasswordAttempts(
925
+ const credentials = yield* makePasswordCredentials(
947
926
  yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(
948
927
  translateFailure,
949
928
  ),
@@ -952,7 +931,7 @@ export const makePasswordKernel = <
952
931
  );
953
932
 
954
933
  return PasswordPersistence.of({
955
- ...attempts,
934
+ ...credentials,
956
935
  readForSubject: (input) =>
957
936
  safeRead(
958
937
  database,
@@ -1288,78 +1267,6 @@ export const makePasswordKernel = <
1288
1267
  Effect.provideService(LifecycleHooks, hooks),
1289
1268
  translateFailure,
1290
1269
  ),
1291
- cleanupAttempts: (input, prepare) =>
1292
- owned(
1293
- database,
1294
- mapping,
1295
- configuration,
1296
- Effect.gen(function* () {
1297
- const transaction = yield* CurrentPasswordSql;
1298
- const journal = yield* CurrentCommitJournal;
1299
-
1300
- const now = yield* nowMillis;
1301
- const nativeNow = mapping.encodeInstant(now);
1302
- const a = attemptColumns(mapping);
1303
-
1304
- const attempts = yield* selectRows(
1305
- transaction
1306
- .select({ attemptId: a.attemptId })
1307
- .from(mapping.attempt.table)
1308
- .where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow)))
1309
- .limit(input.limit + 1),
1310
- false,
1311
- );
1312
-
1313
- const hasMore = attempts.length > input.limit;
1314
- const attemptCandidates = attempts.slice(0, input.limit);
1315
- const selectedAttempts: typeof attemptCandidates = [];
1316
-
1317
- for (const candidate of attemptCandidates) {
1318
- const locked = yield* selectRows(
1319
- transaction
1320
- .select({ attemptId: a.attemptId })
1321
- .from(mapping.attempt.table)
1322
- .where(
1323
- and(
1324
- eq(a.moduleId, input.moduleId),
1325
- eq(a.attemptId, candidate.attemptId),
1326
- lte(a.retentionUntil, nativeNow),
1327
- ),
1328
- )
1329
- .limit(1),
1330
- configuration.locking,
1331
- );
1332
-
1333
- if (locked[0] !== undefined) selectedAttempts.push(locked[0] as any);
1334
- }
1335
-
1336
- const prepared = prepare(
1337
- {
1338
- removed: selectedAttempts.length,
1339
- hasMore,
1340
- },
1341
- journal,
1342
- );
1343
-
1344
- if (selectedAttempts.length > 0)
1345
- yield* transaction.delete(mapping.attempt.table).where(
1346
- and(
1347
- eq(a.moduleId, input.moduleId),
1348
- inArray(
1349
- a.attemptId,
1350
- selectedAttempts.map((row: any) => row.attemptId),
1351
- ),
1352
- lte(a.retentionUntil, nativeNow),
1353
- ),
1354
- );
1355
-
1356
- return prepared;
1357
- }),
1358
- ).pipe(
1359
- Effect.provideService(CurrentPasswordSql, database),
1360
- Effect.provideService(LifecycleHooks, hooks),
1361
- translateFailure,
1362
- ),
1363
1270
  });
1364
1271
  });
1365
1272
 
@@ -141,7 +141,7 @@ export const createPersistence = <T extends object, R>(
141
141
 
142
142
  const roles: StorageRole[] = ["identifiers", "credentials", "sessions", "sessionFlows"];
143
143
 
144
- if (password) roles.push("passwords", "passwordAttempts", "passwordCommands");
144
+ if (password) roles.push("passwords", "passwordCommands");
145
145
  if (management) roles.push("passwordRegistrations");
146
146
  if (email) roles.push("emailCredentials", "emailCommands");
147
147
  if (phone) roles.push("phoneState");
@@ -60,10 +60,12 @@ export interface QueryOperations<
60
60
  readonly balancedD1And: Predicate<Fragment>;
61
61
  readonly asc: (value: unknown) => Fragment;
62
62
  readonly eq: Comparison<Fragment>;
63
+ readonly ne: Comparison<Fragment>;
63
64
  readonly gt: Comparison<Fragment>;
64
65
  readonly gte: Comparison<Fragment>;
65
66
  readonly lte: Comparison<Fragment>;
66
67
  readonly inArray: (value: unknown, values: ReadonlyArray<unknown>) => Fragment;
68
+ readonly isNotNull: (value: unknown) => Fragment;
67
69
  readonly isNull: (value: unknown) => Fragment;
68
70
  readonly notExists: (value: SqlExpression) => Fragment;
69
71
  readonly sql: {