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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +12 -6
  3. package/dist/Adapter.mjs +8 -3
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/configuration.d.mts +1 -1
  11. package/dist/internal/configuration.mjs.map +1 -1
  12. package/dist/internal/email-kernel.d.mts +1 -1
  13. package/dist/internal/email-kernel.mjs +1 -1
  14. package/dist/internal/email-kernel.mjs.map +1 -1
  15. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  16. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  17. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  18. package/dist/internal/models/oauth-model.d.mts +467 -0
  19. package/dist/internal/models/oauth-model.mjs +49 -0
  20. package/dist/internal/models/oauth-model.mjs.map +1 -0
  21. package/dist/internal/models/password-model.d.mts +6 -48
  22. package/dist/internal/models/password-model.mjs +0 -1
  23. package/dist/internal/models/password-model.mjs.map +1 -1
  24. package/dist/internal/native-sql-table.d.mts +4 -0
  25. package/dist/internal/native-sql-table.mjs +7 -0
  26. package/dist/internal/native-sql-table.mjs.map +1 -1
  27. package/dist/internal/oauth/accounts.d.mts +8 -0
  28. package/dist/internal/oauth/accounts.mjs +379 -0
  29. package/dist/internal/oauth/accounts.mjs.map +1 -0
  30. package/dist/internal/oauth/connected-access.d.mts +9 -0
  31. package/dist/internal/oauth/connected-access.mjs +229 -0
  32. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  33. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  34. package/dist/internal/oauth/connected-collection.mjs +214 -0
  35. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  36. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  37. package/dist/internal/oauth/connected-custody.mjs +165 -0
  38. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  39. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  40. package/dist/internal/oauth/connected-flow.mjs +324 -0
  41. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  42. package/dist/internal/oauth/connected-input.mjs +122 -0
  43. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  44. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  45. package/dist/internal/oauth/connected-keys.mjs +22 -0
  46. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  47. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  48. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  49. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  50. package/dist/internal/oauth/connected-management.d.mts +9 -0
  51. package/dist/internal/oauth/connected-management.mjs +243 -0
  52. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  53. package/dist/internal/oauth/connected-reference.mjs +63 -0
  54. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  55. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  56. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  57. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  58. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  59. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  60. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  61. package/dist/internal/oauth/connected-state.d.mts +14 -0
  62. package/dist/internal/oauth/connected-state.mjs +469 -0
  63. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  64. package/dist/internal/oauth/connected-target.d.mts +21 -0
  65. package/dist/internal/oauth/connected-target.mjs +117 -0
  66. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  67. package/dist/internal/oauth/flow.d.mts +5 -0
  68. package/dist/internal/oauth/flow.mjs +279 -0
  69. package/dist/internal/oauth/flow.mjs.map +1 -0
  70. package/dist/internal/oauth/input.mjs +127 -0
  71. package/dist/internal/oauth/input.mjs.map +1 -0
  72. package/dist/internal/oauth/owner.d.mts +10 -0
  73. package/dist/internal/oauth/owner.mjs +10 -0
  74. package/dist/internal/oauth/owner.mjs.map +1 -0
  75. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  76. package/dist/internal/oauth/query-compiler.mjs +9 -0
  77. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  78. package/dist/internal/oauth/registration.d.mts +7 -0
  79. package/dist/internal/oauth/registration.mjs +393 -0
  80. package/dist/internal/oauth/registration.mjs.map +1 -0
  81. package/dist/internal/oauth/state.mjs +107 -0
  82. package/dist/internal/oauth/state.mjs.map +1 -0
  83. package/dist/internal/oauth/target.d.mts +31 -0
  84. package/dist/internal/oauth/target.mjs +273 -0
  85. package/dist/internal/oauth/target.mjs.map +1 -0
  86. package/dist/internal/oauth-kernel.d.mts +1423 -0
  87. package/dist/internal/oauth-kernel.mjs +187 -0
  88. package/dist/internal/oauth-kernel.mjs.map +1 -0
  89. package/dist/internal/oauth-proxy.d.mts +31 -0
  90. package/dist/internal/oauth-proxy.mjs +129 -0
  91. package/dist/internal/oauth-proxy.mjs.map +1 -0
  92. package/dist/internal/passkey/write-state.mjs +9 -1
  93. package/dist/internal/passkey/write-state.mjs.map +1 -1
  94. package/dist/internal/passkey-kernel.d.mts +9 -9
  95. package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
  96. package/dist/internal/password-credentials.mjs +121 -0
  97. package/dist/internal/password-credentials.mjs.map +1 -0
  98. package/dist/internal/password-kernel.d.mts +8 -24
  99. package/dist/internal/password-kernel.mjs +5 -43
  100. package/dist/internal/password-kernel.mjs.map +1 -1
  101. package/dist/internal/persistence.d.mts +1 -1
  102. package/dist/internal/persistence.mjs +2 -2
  103. package/dist/internal/persistence.mjs.map +1 -1
  104. package/dist/internal/query-operations.d.mts +2 -0
  105. package/dist/internal/session-kernel.d.mts +2 -2
  106. package/dist/internal/session-kernel.mjs +74 -18
  107. package/dist/internal/session-kernel.mjs.map +1 -1
  108. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  109. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  110. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  111. package/dist/internal/sql-oauth-model.d.mts +60 -0
  112. package/dist/internal/sql-oauth-model.mjs +10 -0
  113. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  114. package/dist/internal/sql-query.d.mts +12 -0
  115. package/dist/internal/sql-query.mjs +15 -18
  116. package/dist/internal/sql-query.mjs.map +1 -1
  117. package/dist/internal/sql-storage-validation.mjs +54 -0
  118. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  119. package/dist/internal/storage-mapping.mjs +1 -37
  120. package/dist/internal/storage-mapping.mjs.map +1 -1
  121. package/dist/internal/storage-plans.d.mts +7 -0
  122. package/dist/internal/storage-plans.mjs +189 -0
  123. package/dist/internal/storage-plans.mjs.map +1 -0
  124. package/dist/internal/storage-tables.d.mts +0 -56
  125. package/dist/internal/storage-tables.mjs +0 -17
  126. package/dist/internal/storage-tables.mjs.map +1 -1
  127. package/dist/internal/storage-validation.d.mts +6 -1
  128. package/dist/internal/storage-validation.mjs +42 -1
  129. package/dist/internal/storage-validation.mjs.map +1 -1
  130. package/dist/internal/transaction-kernel.mjs +3 -2
  131. package/dist/internal/transaction-kernel.mjs.map +1 -1
  132. package/package.json +1 -1
  133. package/src/Adapter.ts +89 -5
  134. package/src/OAuthPersistence.ts +427 -0
  135. package/src/index.ts +3 -0
  136. package/src/internal/configuration.ts +1 -3
  137. package/src/internal/email-kernel.ts +2 -1
  138. package/src/internal/models/oauth-connected-model.ts +396 -0
  139. package/src/internal/models/oauth-model.ts +708 -0
  140. package/src/internal/models/password-model.ts +2 -57
  141. package/src/internal/native-sql-table.ts +14 -0
  142. package/src/internal/oauth/accounts.ts +839 -0
  143. package/src/internal/oauth/connected-access.ts +388 -0
  144. package/src/internal/oauth/connected-collection.ts +345 -0
  145. package/src/internal/oauth/connected-custody.ts +308 -0
  146. package/src/internal/oauth/connected-flow.ts +562 -0
  147. package/src/internal/oauth/connected-input.ts +147 -0
  148. package/src/internal/oauth/connected-keys.ts +31 -0
  149. package/src/internal/oauth/connected-maintenance.ts +379 -0
  150. package/src/internal/oauth/connected-management.ts +391 -0
  151. package/src/internal/oauth/connected-reference.ts +135 -0
  152. package/src/internal/oauth/connected-settlement.ts +278 -0
  153. package/src/internal/oauth/connected-sign-in.ts +472 -0
  154. package/src/internal/oauth/connected-state.ts +924 -0
  155. package/src/internal/oauth/connected-target.ts +307 -0
  156. package/src/internal/oauth/flow.ts +505 -0
  157. package/src/internal/oauth/input.ts +201 -0
  158. package/src/internal/oauth/owner.ts +22 -0
  159. package/src/internal/oauth/query-compiler.ts +15 -0
  160. package/src/internal/oauth/registration.ts +676 -0
  161. package/src/internal/oauth/state.ts +218 -0
  162. package/src/internal/oauth/target.ts +803 -0
  163. package/src/internal/oauth-kernel.ts +294 -0
  164. package/src/internal/oauth-proxy.ts +176 -0
  165. package/src/internal/passkey/write-state.ts +18 -4
  166. package/src/internal/password-credentials.ts +245 -0
  167. package/src/internal/password-kernel.ts +7 -99
  168. package/src/internal/persistence.ts +1 -1
  169. package/src/internal/query-operations.ts +2 -0
  170. package/src/internal/session-kernel.ts +143 -55
  171. package/src/internal/sql-oauth-connected-model.ts +145 -0
  172. package/src/internal/sql-oauth-model.ts +263 -0
  173. package/src/internal/sql-query.ts +16 -13
  174. package/src/internal/sql-storage-validation.ts +64 -0
  175. package/src/internal/storage-mapping.ts +0 -37
  176. package/src/internal/storage-plans.ts +218 -0
  177. package/src/internal/storage-tables.ts +0 -20
  178. package/src/internal/storage-validation.ts +58 -0
  179. package/src/internal/transaction-kernel.ts +6 -2
  180. package/dist/internal/password-attempts.mjs +0 -197
  181. package/dist/internal/password-attempts.mjs.map +0 -1
  182. package/src/internal/password-attempts.ts +0 -466
@@ -4374,8 +4374,8 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
4374
4374
  readonly ceremony: import("@yielded/auth/Passkey").PasskeyCeremony;
4375
4375
  readonly claimId: string;
4376
4376
  }) => import("effect/Effect").Effect<{
4377
- readonly claim?: never;
4378
4377
  readonly _tag: "Rejected";
4378
+ readonly claim?: never;
4379
4379
  } | {
4380
4380
  readonly _tag: "Claimed";
4381
4381
  readonly claim: {
@@ -4979,7 +4979,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
4979
4979
  invalidationMatches: (mapping: any, supplied: import("@yielded/auth/Sessions").SessionInvalidationWindow, policy: import("@yielded/auth/Passkey").PasskeyManagementPolicy) => boolean;
4980
4980
  invalidate: (mapping: any, subject: WriteSubject, invalidation: {
4981
4981
  readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
4982
- readonly existingSessions: "immediate" | "original-absolute-expiry";
4982
+ readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
4983
4983
  readonly maximumExposureMillis: number;
4984
4984
  readonly oldAuthenticationEvidence: "rejected";
4985
4985
  }) => import("effect/Effect").Effect<string & import("effect/Brand").Brand<"effect-auth/SecurityRevision">, import("@yielded/auth/Passkey").PasskeyUnavailable | import("effect/PlatformError").PlatformError, import("effect/Crypto").Crypto | CurrentPasskeyTransaction>;
@@ -5635,7 +5635,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
5635
5635
  readonly nowMillis: number;
5636
5636
  }) => import("effect/Effect").Effect<{
5637
5637
  readonly _tag: "Rejected";
5638
- readonly credential?: never;
5638
+ credential?: never;
5639
5639
  } | {
5640
5640
  readonly _tag: "Enrolled";
5641
5641
  readonly credential: {
@@ -5706,20 +5706,19 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
5706
5706
  readonly _tag: "Rejected";
5707
5707
  readonly decision?: never;
5708
5708
  } | {
5709
- readonly credential?: never;
5710
5709
  readonly _tag: "Replay";
5711
5710
  readonly result: {
5712
5711
  readonly credentialId: string;
5713
5712
  readonly invalidation: {
5714
5713
  readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
5715
- readonly existingSessions: "immediate" | "original-absolute-expiry";
5714
+ readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
5716
5715
  readonly maximumExposureMillis: number;
5717
5716
  readonly oldAuthenticationEvidence: "rejected";
5718
5717
  };
5719
5718
  readonly replayed: true;
5720
5719
  };
5720
+ credential?: never;
5721
5721
  } | {
5722
- readonly result?: never;
5723
5722
  readonly _tag: "Target";
5724
5723
  readonly credential: {
5725
5724
  readonly credentialId: string;
@@ -5757,6 +5756,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
5757
5756
  readonly counter: number;
5758
5757
  readonly maximumCounter: number;
5759
5758
  };
5759
+ result?: never;
5760
5760
  }, import("@yielded/auth/Passkey").PasskeyUnavailable | import("effect/PlatformError").PlatformError, import("effect/Crypto").Crypto | CurrentPasskeyTransaction>;
5761
5761
  removeCredential: (mapping: any, input: {
5762
5762
  readonly moduleId: string;
@@ -5777,15 +5777,15 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
5777
5777
  readonly credentialId: string;
5778
5778
  readonly invalidation: {
5779
5779
  readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
5780
- readonly existingSessions: "immediate" | "original-absolute-expiry";
5780
+ readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
5781
5781
  readonly maximumExposureMillis: number;
5782
5782
  readonly oldAuthenticationEvidence: "rejected";
5783
5783
  };
5784
5784
  readonly replayed: true;
5785
5785
  };
5786
5786
  } | {
5787
- readonly result?: never;
5788
5787
  readonly _tag: "LastSignInMethod";
5788
+ result?: never;
5789
5789
  } | {
5790
5790
  readonly _tag: "Removed";
5791
5791
  readonly result: {
@@ -5793,7 +5793,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
5793
5793
  replayed: boolean;
5794
5794
  invalidation: {
5795
5795
  readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
5796
- readonly existingSessions: "immediate" | "original-absolute-expiry";
5796
+ readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
5797
5797
  readonly maximumExposureMillis: number;
5798
5798
  readonly oldAuthenticationEvidence: "rejected";
5799
5799
  };
@@ -3,10 +3,9 @@ import "./models/password-model.mjs";
3
3
  import "./native-sql-table.mjs";
4
4
  import "./password-kernel.mjs";
5
5
  import { Effect, Option, Redacted } from "effect";
6
- import { LifecycleHooks } from "@yielded/auth/Hooks";
7
- import { PasswordCredentialSnapshot, PasswordUnavailable, PreparePasswordCommit } from "@yielded/auth/Password";
6
+ import { EncodedPasswordHash, PasswordCredentialSnapshot, PasswordUnavailable } from "@yielded/auth/Password";
8
7
  import { LoginIdentifier } from "@yielded/auth/Identity";
9
- //#region src/internal/password-attempts.d.ts
8
+ //#region src/internal/password-credentials.d.ts
10
9
  export declare const samePasswordCredentialSnapshot: (a: PasswordCredentialSnapshot, b: PasswordCredentialSnapshot) => boolean;
11
10
  //#endregion
12
- //# sourceMappingURL=password-attempts.d.mts.map
11
+ //# sourceMappingURL=password-credentials.d.mts.map
@@ -0,0 +1,121 @@
1
+ import { sqlMapping } from "./native-sql-table.mjs";
2
+ import { NativeDatabase } from "./transaction-kernel.mjs";
3
+ import { Effect, Option, Redacted, Schema } from "effect";
4
+ import { CurrentCommitJournal, hasCommitScope } from "@yielded/auth/Hooks";
5
+ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
6
+ import { EncodedPasswordHash, PasswordUnavailable, snapshotPasswordCredential } from "@yielded/auth/Password";
7
+ //#region src/internal/password-credentials.ts
8
+ const unavailable = () => PasswordUnavailable.make({});
9
+ const failure = (effect) => reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(Effect.mapError(unavailable));
10
+ const samePasswordCredentialSnapshot = (a, b) => a.moduleId === b.moduleId && a.revision.subjectId === b.revision.subjectId && a.revision.securityRevision === b.revision.securityRevision && a.revision.credentials.length === b.revision.credentials.length && new Set(a.revision.credentials.map((entry) => entry.credentialId)).size === a.revision.credentials.length && new Set(b.revision.credentials.map((entry) => entry.credentialId)).size === b.revision.credentials.length && a.revision.credentials.every((entry) => b.revision.credentials.some((other) => entry.credentialId === other.credentialId && entry.revision === other.revision)) && a.credentialId === b.credentialId && a.credentialRevision === b.credentialRevision && a.verifierVersion === b.verifierVersion && Redacted.value(a.verifier) === Redacted.value(b.verifier) && a.normalization === b.normalization && a.identifier.namespace === b.identifier.namespace && a.identifier.value === b.identifier.value && a.identifierBindingRevision === b.identifierBindingRevision && a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;
11
+ /** Lookup is read-only; rehash is one conditional maintenance write, never authentication. */
12
+ const makePasswordCredentials = Effect.fnUntraced(function* (table, mapping, options) {
13
+ const sql = (yield* NativeDatabase).$client.withoutTransforms();
14
+ const parent = yield* Effect.serviceOption(CurrentCommitJournal);
15
+ const [s, i, c] = yield* sqlMapping(() => [
16
+ table(mapping.subject.table),
17
+ table(mapping.identifier.table),
18
+ table(mapping.credential.table)
19
+ ]).pipe(failure);
20
+ const standalone = Effect.gen(function* () {
21
+ if (options.coordinated) {
22
+ const current = yield* Effect.serviceOption(CurrentCommitJournal);
23
+ if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value) return yield* unavailable();
24
+ } else {
25
+ if (yield* hasCommitScope) return yield* unavailable();
26
+ yield* options.standaloneGuard;
27
+ }
28
+ });
29
+ const allocate = (asynchronous, sync) => {
30
+ if (options.mode !== "synchronous" && asynchronous !== void 0) return asynchronous;
31
+ return sync === void 0 ? Effect.fail(unavailable()) : Effect.try({
32
+ try: sync,
33
+ catch: unavailable
34
+ });
35
+ };
36
+ const snapshot = Effect.fnUntraced(function* (nativeId, moduleId, identifier) {
37
+ const subjectTable = s.as("password_subject");
38
+ const identifierTable = i.as("password_identifier");
39
+ const credentialTable = c.as("password_credential");
40
+ const rows = yield* sqlMapping(() => sql`select ${subjectTable.fields("subject_")}, ${identifierTable.fields("identifier_")}, ${credentialTable.fields("credential_")}
41
+ from ${subjectTable.name}
42
+ inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}
43
+ and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}
44
+ and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}
45
+ inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}
46
+ and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}
47
+ where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}
48
+ `).pipe(Effect.flatten);
49
+ if (rows.length !== 1) return void 0;
50
+ const [subject, identifierRow, credential] = yield* sqlMapping(() => [
51
+ s.decode(rows[0], "subject_"),
52
+ i.decode(rows[0], "identifier_"),
53
+ c.decode(rows[0], "credential_")
54
+ ]);
55
+ if (yield* sqlMapping(() => !mapping.subject.isActiveStatus(subject[mapping.subject.status]) || !mapping.identifier.isCurrent(identifierRow) || !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) || !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) || !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]))) return void 0;
56
+ return yield* sqlMapping(() => mapping.credential.decode({
57
+ moduleId,
58
+ subject,
59
+ identifier: identifierRow,
60
+ credential
61
+ })).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));
62
+ });
63
+ return {
64
+ findCredential: (uncaptured) => Effect.gen(function* () {
65
+ const input = {
66
+ ...uncaptured,
67
+ identifier: { ...uncaptured.identifier }
68
+ };
69
+ yield* standalone;
70
+ const identifiers = yield* sqlMapping(() => sql`select ${i.fields("i_")} from ${i.name}
71
+ where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}
72
+ and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`).pipe(Effect.flatten);
73
+ const identifier = identifiers.length === 1 ? yield* sqlMapping(() => i.decode(identifiers[0], "i_")) : void 0;
74
+ if (identifier === void 0 || !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))) return Option.none();
75
+ const credential = yield* snapshot(identifier[mapping.identifier.subjectId], input.moduleId, input.identifier);
76
+ return credential === void 0 || input.subjectId !== void 0 && credential.revision.subjectId !== input.subjectId ? Option.none() : Option.some(credential);
77
+ }).pipe(failure),
78
+ rehashIfCurrent: (input) => Effect.gen(function* () {
79
+ const nextVerifier = Redacted.make(yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)));
80
+ const credential = yield* snapshotPasswordCredential(input.credential);
81
+ yield* standalone;
82
+ const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);
83
+ const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
84
+ if (version === credential.verifierVersion) return yield* unavailable();
85
+ yield* sqlMapping(() => {
86
+ const encoded = mapping.credential.encodeVerifier(nextVerifier, version);
87
+ const values = {
88
+ [mapping.credential.verifier]: encoded[mapping.credential.verifier],
89
+ [mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion]
90
+ };
91
+ if (Object.values(values).some((value) => value === void 0)) throw unavailable();
92
+ const exact = sql.and(Object.entries({
93
+ [mapping.credential.moduleId]: credential.moduleId,
94
+ [mapping.credential.subjectId]: nativeId,
95
+ [mapping.credential.credentialId]: credential.credentialId,
96
+ [mapping.credential.credentialRevision]: credential.credentialRevision,
97
+ [mapping.credential.verifierVersion]: credential.verifierVersion,
98
+ [mapping.credential.verifier]: Redacted.value(credential.verifier),
99
+ [mapping.credential.normalization]: credential.normalization
100
+ }).map(([key, value]) => {
101
+ const column = c.column(key), bound = c.value(key, value);
102
+ if (typeof value !== "string") return sql`${column} = ${bound}`;
103
+ return sql.onDialectOrElse({
104
+ mysql: () => sql`binary ${column} = binary ${bound}`,
105
+ pg: () => sql`convert_to(cast(${column} as text), 'UTF8') = convert_to(cast(${bound} as text), 'UTF8')`,
106
+ orElse: () => sql`cast(${column} as blob) = cast(${bound} as blob)`
107
+ });
108
+ }));
109
+ return sql`${c.update(values)}
110
+ where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, credential.moduleId)}
111
+ and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, nativeId)}
112
+ and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, credential.credentialId)}
113
+ and ${exact}`;
114
+ }).pipe(Effect.flatten);
115
+ }).pipe(failure)
116
+ };
117
+ });
118
+ //#endregion
119
+ export { makePasswordCredentials, samePasswordCredentialSnapshot };
120
+
121
+ //# sourceMappingURL=password-credentials.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-credentials.mjs","names":[],"sources":["../../src/internal/password-credentials.ts"],"sourcesContent":["import { CurrentCommitJournal, hasCommitScope } from \"@yielded/auth/Hooks\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n EncodedPasswordHash,\n type PasswordPersistence,\n PasswordUnavailable,\n snapshotPasswordCredential,\n type PasswordCredentialSnapshot,\n} from \"@yielded/auth/Password\";\nimport { reportPersistenceFailure } from \"@yielded/auth/Persistence\";\nimport { Effect, Option, Redacted, Schema } from \"effect\";\n\nimport type { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport { sqlMapping, type SqlTable } from \"./native-sql-table\";\nimport type { PasswordSqlConfiguration } from \"./password-kernel\";\nimport { NativeDatabase } from \"./transaction-kernel\";\n\ntype Row = Readonly<Record<string, unknown>>;\nconst unavailable = () => PasswordUnavailable.make({});\n\nconst failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(\n Effect.mapError(unavailable),\n );\n\nexport const samePasswordCredentialSnapshot = (\n a: PasswordCredentialSnapshot,\n b: PasswordCredentialSnapshot,\n) =>\n a.moduleId === b.moduleId &&\n a.revision.subjectId === b.revision.subjectId &&\n a.revision.securityRevision === b.revision.securityRevision &&\n a.revision.credentials.length === b.revision.credentials.length &&\n new Set(a.revision.credentials.map((entry) => entry.credentialId)).size ===\n a.revision.credentials.length &&\n new Set(b.revision.credentials.map((entry) => entry.credentialId)).size ===\n b.revision.credentials.length &&\n a.revision.credentials.every((entry) =>\n b.revision.credentials.some(\n (other) => entry.credentialId === other.credentialId && entry.revision === other.revision,\n ),\n ) &&\n a.credentialId === b.credentialId &&\n a.credentialRevision === b.credentialRevision &&\n a.verifierVersion === b.verifierVersion &&\n Redacted.value(a.verifier) === Redacted.value(b.verifier) &&\n a.normalization === b.normalization &&\n a.identifier.namespace === b.identifier.namespace &&\n a.identifier.value === b.identifier.value &&\n a.identifierBindingRevision === b.identifierBindingRevision &&\n a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;\n\n/** Lookup is read-only; rehash is one conditional maintenance write, never authentication. */\nexport const makePasswordCredentials = Effect.fnUntraced(function* (\n table: (table: object) => SqlTable,\n mapping: AnyPasswordPersistenceMapping,\n options: PasswordSqlConfiguration,\n): Effect.fn.Return<\n Pick<PasswordPersistence[\"Service\"], \"findCredential\" | \"rehashIfCurrent\">,\n PasswordUnavailable,\n NativeDatabase\n> {\n const sql = (yield* NativeDatabase).$client.withoutTransforms();\n const parent = yield* Effect.serviceOption(CurrentCommitJournal);\n\n const [s, i, c] = yield* sqlMapping(\n () =>\n [\n table(mapping.subject.table),\n table(mapping.identifier.table),\n table(mapping.credential.table),\n ] as const,\n ).pipe(failure);\n\n const standalone = Effect.gen(function* () {\n if (options.coordinated) {\n const current = yield* Effect.serviceOption(CurrentCommitJournal);\n\n if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value)\n return yield* unavailable();\n } else {\n if (yield* hasCommitScope) return yield* unavailable();\n yield* options.standaloneGuard;\n }\n });\n\n const allocate = <A>(\n asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,\n sync: (() => A) | undefined,\n ) => {\n if (options.mode !== \"synchronous\" && asynchronous !== undefined) return asynchronous;\n\n return sync === undefined\n ? Effect.fail(unavailable())\n : Effect.try({ try: sync, catch: unavailable });\n };\n\n // Bind each native ID through its own column codec. Physical encodings of the\n // same application ID need not match across subject, identifier and password.\n const snapshot = Effect.fnUntraced(function* (\n nativeId: unknown,\n moduleId: string,\n identifier: LoginIdentifier,\n ) {\n const subjectTable = s.as(\"password_subject\");\n const identifierTable = i.as(\"password_identifier\");\n const credentialTable = c.as(\"password_credential\");\n\n const rows = yield* sqlMapping(\n () =>\n sql<Row>`select ${subjectTable.fields(\"subject_\")}, ${identifierTable.fields(\"identifier_\")}, ${credentialTable.fields(\"credential_\")}\n from ${subjectTable.name}\n inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}\n and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}\n and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}\n inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}\n and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}\n where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}\n `,\n ).pipe(Effect.flatten);\n\n if (rows.length !== 1) return undefined;\n\n const [subject, identifierRow, credential] = yield* sqlMapping(\n () =>\n [\n s.decode(rows[0]!, \"subject_\"),\n i.decode(rows[0]!, \"identifier_\"),\n c.decode(rows[0]!, \"credential_\"),\n ] as const,\n );\n\n if (\n yield* sqlMapping(\n () =>\n !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||\n !mapping.identifier.isCurrent(identifierRow) ||\n !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) ||\n !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) ||\n !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]),\n )\n )\n return undefined;\n\n const captured = yield* sqlMapping(() =>\n mapping.credential.decode({ moduleId, subject, identifier: identifierRow, credential }),\n ).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));\n\n return captured;\n });\n\n return {\n findCredential: (uncaptured) =>\n Effect.gen(function* () {\n const input = { ...uncaptured, identifier: { ...uncaptured.identifier } };\n\n yield* standalone;\n\n const identifiers = yield* sqlMapping(\n () => sql<Row>`select ${i.fields(\"i_\")} from ${i.name}\n where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}\n and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`,\n ).pipe(Effect.flatten);\n\n const identifier =\n identifiers.length === 1\n ? yield* sqlMapping(() => i.decode(identifiers[0]!, \"i_\"))\n : undefined;\n\n if (\n identifier === undefined ||\n !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))\n )\n return Option.none();\n\n const credential = yield* snapshot(\n identifier[mapping.identifier.subjectId],\n input.moduleId,\n input.identifier,\n );\n\n return credential === undefined ||\n (input.subjectId !== undefined && credential.revision.subjectId !== input.subjectId)\n ? Option.none()\n : Option.some(credential);\n }).pipe(failure),\n rehashIfCurrent: (input) =>\n Effect.gen(function* () {\n const nextVerifier = Redacted.make(\n yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)),\n );\n\n const credential = yield* snapshotPasswordCredential(input.credential);\n\n yield* standalone;\n const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);\n const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);\n\n if (version === credential.verifierVersion) return yield* unavailable();\n yield* sqlMapping(() => {\n const encoded = mapping.credential.encodeVerifier(nextVerifier, version);\n\n // A custom encoder cannot turn maintenance into a semantic credential change.\n const values = {\n [mapping.credential.verifier]: encoded[mapping.credential.verifier],\n [mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion],\n };\n\n if (Object.values(values).some((value) => value === undefined)) throw unavailable();\n\n // Ordinary identity predicates retain index use; the full CAS ignores text collation.\n const exact = sql.and(\n Object.entries({\n [mapping.credential.moduleId]: credential.moduleId,\n [mapping.credential.subjectId]: nativeId,\n [mapping.credential.credentialId]: credential.credentialId,\n [mapping.credential.credentialRevision]: credential.credentialRevision,\n [mapping.credential.verifierVersion]: credential.verifierVersion,\n [mapping.credential.verifier]: Redacted.value(credential.verifier),\n [mapping.credential.normalization]: credential.normalization,\n }).map(([key, value]) => {\n const column = c.column(key),\n bound = c.value(key, value);\n\n if (typeof value !== \"string\") return sql`${column} = ${bound}`;\n\n return sql.onDialectOrElse({\n mysql: () => sql`binary ${column} = binary ${bound}`,\n pg: () =>\n sql`convert_to(cast(${column} as text), 'UTF8') = convert_to(cast(${bound} as text), 'UTF8')`,\n orElse: () => sql`cast(${column} as blob) = cast(${bound} as blob)`,\n });\n }),\n );\n\n return sql`${c.update(values)}\n where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, credential.moduleId)}\n and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, nativeId)}\n and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, credential.credentialId)}\n and ${exact}`;\n }).pipe(Effect.flatten);\n }).pipe(failure),\n } satisfies Pick<PasswordPersistence[\"Service\"], \"findCredential\" | \"rehashIfCurrent\">;\n});\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { QueryFailure, QueryOperations, SqlColumn, SqlFragment } from "./query-o
4
4
  import { AnyPasswordPersistenceMapping } from "./models/password-model.mjs";
5
5
  import { CurrentProofSql, ProofSqlConfiguration, makeProofKernel } from "./proof-kernel.mjs";
6
6
  import { NativeSqlTables } from "./native-sql-table.mjs";
7
- import { samePasswordCredentialSnapshot } from "./password-attempts.mjs";
7
+ import { samePasswordCredentialSnapshot } from "./password-credentials.mjs";
8
8
  import { Context, DateTime, Effect, Option, Redacted } from "effect";
9
9
  import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, PreparedCommit } from "@yielded/auth/Hooks";
10
10
  import { AuthenticationEvidence, AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
@@ -52,24 +52,15 @@ export interface PasswordSqlConfiguration {
52
52
  type Database = PasswordSqlDatabase;
53
53
  export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFragment, Column extends SqlColumn = SqlColumn>(operations: QueryOperations<Fragment, Column>, proofs: Pick<ReturnType<typeof makeProofKernel>, "completeProofPlanIn">, nativeTables?: (client: SqlClient, database: object) => NativeSqlTables) => {
54
54
  makeSqlPasswordPersistence: (mapping: AnyPasswordPersistenceMapping<Fragment>, configuration: PasswordSqlConfiguration) => Effect.Effect<{
55
- readonly prepareAttempt: (input: {
55
+ readonly findCredential: (input: {
56
56
  readonly moduleId: string;
57
- readonly action: "sign-in" | "change";
58
57
  readonly identifier: LoginIdentifier;
59
58
  readonly subjectId?: import("@yielded/auth/Schema").SubjectId;
60
- readonly attemptLifetimeMillis: number;
61
- }) => Effect.Effect<import("@yielded/auth/Password").PasswordAttemptPreparation, PasswordUnavailable>;
62
- readonly settleAttempt: <A>(input: {
63
- readonly moduleId: string;
64
- readonly attemptId: import("@yielded/auth/Password").PasswordAttemptId;
65
- readonly captured?: import("@yielded/auth/Password").PasswordCredentialSnapshot;
66
- readonly outcome: import("@yielded/auth/Password").PasswordAttemptDecision;
67
- readonly rehash?: {
68
- readonly expectedVersion: SecurityRevision;
69
- readonly expectedVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
70
- readonly nextVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
71
- };
72
- }, prepare: import("@yielded/auth/Password").PreparePasswordCommit<import("@yielded/auth/Password").PasswordAttemptDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
59
+ }) => Effect.Effect<Option.Option<import("@yielded/auth/Password").PasswordCredentialSnapshot>, PasswordUnavailable>;
60
+ readonly rehashIfCurrent: (input: {
61
+ readonly credential: import("@yielded/auth/Password").PasswordCredentialSnapshot;
62
+ readonly nextVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
63
+ }) => Effect.Effect<void, PasswordUnavailable>;
73
64
  readonly readForSubject: (input: {
74
65
  readonly moduleId: string;
75
66
  readonly subjectId: import("@yielded/auth/Schema").SubjectId;
@@ -84,13 +75,6 @@ export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFrag
84
75
  readonly resetWithProof: <A>(input: PasswordMutationInput & {
85
76
  readonly completion: ProofCompletionPlan;
86
77
  }, prepare: import("@yielded/auth/Password").PreparePasswordCommit<PasswordMutationDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
87
- readonly cleanupAttempts: <A>(input: {
88
- readonly moduleId: string;
89
- readonly limit: number;
90
- }, prepare: import("@yielded/auth/Password").PreparePasswordCommit<{
91
- readonly removed: number;
92
- readonly hasMore: boolean;
93
- }, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
94
78
  }, PasswordUnavailable, CurrentPasswordSql | LifecycleHooks | NativeDatabase>;
95
79
  passwordSqlKernel: {
96
80
  allocate: <A>(mode: PasswordSqlConfiguration["mode"], asyncValue: Effect.Effect<A, PersistenceMappingError> | undefined, syncValue: (() => A) | undefined) => Effect.Effect<A, PasswordUnavailable | PersistenceMappingError>;
@@ -133,7 +117,7 @@ export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFrag
133
117
  }>;
134
118
  invalidation: Readonly<{
135
119
  trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
136
- existingSessions: "immediate" | "original-absolute-expiry";
120
+ existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
137
121
  maximumExposureMillis: number;
138
122
  oldAuthenticationEvidence: "rejected";
139
123
  }>;
@@ -4,7 +4,7 @@ import { NativeDatabase } from "./transaction-kernel.mjs";
4
4
  import { CurrentPasswordPreparedTransaction, PasswordPreparedPostconditions } from "./PasswordPreparedPostconditions.mjs";
5
5
  import { CurrentProofSql } from "./proof-kernel.mjs";
6
6
  import { requiredPasswordConstraints } from "./models/password-model.mjs";
7
- import { makePasswordAttempts, samePasswordCredentialSnapshot } from "./password-attempts.mjs";
7
+ import { makePasswordCredentials, samePasswordCredentialSnapshot } from "./password-credentials.mjs";
8
8
  import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
9
9
  import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
10
10
  import { snapshotAuthenticationEvidence } from "@yielded/auth/Sessions";
@@ -14,7 +14,7 @@ import { PasswordPersistence, PasswordUnavailable, snapshotPasswordCredential, s
14
14
  //#region src/internal/password-kernel.ts
15
15
  var CurrentPasswordSql = class extends Context.Service()("effect-auth/CurrentPasswordSql") {};
16
16
  const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeNativeSqlTables(client)) => {
17
- const { and, eq, inArray, lte, column, updateValues } = operations;
17
+ const { and, eq, inArray, column, updateValues } = operations;
18
18
  const { completeProofPlanIn } = proofs;
19
19
  const unavailable = () => PasswordUnavailable.make({});
20
20
  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()))));
@@ -47,23 +47,6 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
47
47
  credentialId: column(mapping.authorityCredential.table, mapping.authorityCredential.credentialId),
48
48
  revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision)
49
49
  });
50
- const attemptColumns = (mapping) => ({
51
- moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
52
- action: column(mapping.attempt.table, mapping.attempt.action),
53
- attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
54
- identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
55
- identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
56
- subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
57
- credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
58
- securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
59
- credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
60
- verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
61
- identifierBindingRevision: column(mapping.attempt.table, mapping.attempt.identifierBindingRevision),
62
- admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
63
- deadline: column(mapping.attempt.table, mapping.attempt.deadline),
64
- retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
65
- state: column(mapping.attempt.table, mapping.attempt.state)
66
- });
67
50
  const commandColumns = (mapping) => ({
68
51
  moduleId: column(mapping.command.table, mapping.command.moduleId),
69
52
  commandId: column(mapping.command.table, mapping.command.commandId),
@@ -271,7 +254,7 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
271
254
  const currentRequirement = yield* mapping.subject.decodeActionRequirement(row, action);
272
255
  const now = yield* nowMillis;
273
256
  if (!evidenceSatisfiedAt(input.authorization.evidence, input.authorization.requirement, now) || !evidenceSatisfiedAt(input.authorization.evidence, currentRequirement, now)) return void 0;
274
- if (input.invalidation.existingSessions === "immediate" && mapping.sessionInvalidation !== "same-authority-immediate") return void 0;
257
+ if ((input.invalidation.existingSessions === "immediate" || input.invalidation.existingSessions === "cache-expiry") && mapping.sessionInvalidation !== "same-authority-immediate") return void 0;
275
258
  return {
276
259
  ...locked,
277
260
  now
@@ -346,9 +329,9 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
346
329
  const hooks = yield* LifecycleHooks;
347
330
  if (!validConstraints(mapping)) return yield* unavailable();
348
331
  const root = yield* NativeDatabase;
349
- const attempts = yield* makePasswordAttempts(yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(translateFailure), mapping, configuration);
332
+ const credentials = yield* makePasswordCredentials(yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(translateFailure), mapping, configuration);
350
333
  return PasswordPersistence.of({
351
- ...attempts,
334
+ ...credentials,
352
335
  readForSubject: (input) => safeRead(database, configuration, Effect.gen(function* () {
353
336
  const transaction = yield* CurrentPasswordSql;
354
337
  const locked = yield* lockSubject(mapping, input.subjectId, false);
@@ -442,27 +425,6 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
442
425
  });
443
426
  return passwordReceipt ?? prepare("rejected", journal);
444
427
  }));
445
- })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
446
- cleanupAttempts: (input, prepare) => owned(database, mapping, configuration, Effect.gen(function* () {
447
- const transaction = yield* CurrentPasswordSql;
448
- const journal = yield* CurrentCommitJournal;
449
- const now = yield* nowMillis;
450
- const nativeNow = mapping.encodeInstant(now);
451
- const a = attemptColumns(mapping);
452
- 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(input.limit + 1), false);
453
- const hasMore = attempts.length > input.limit;
454
- const attemptCandidates = attempts.slice(0, input.limit);
455
- const selectedAttempts = [];
456
- for (const candidate of attemptCandidates) {
457
- 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);
458
- if (locked[0] !== void 0) selectedAttempts.push(locked[0]);
459
- }
460
- const prepared = prepare({
461
- removed: selectedAttempts.length,
462
- hasMore
463
- }, journal);
464
- 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)));
465
- return prepared;
466
428
  })).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure)
467
429
  });
468
430
  });