@yielded/auth-persistence 0.1.0-beta.30 → 0.1.0-beta.32

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 (130) hide show
  1. package/README.md +11 -7
  2. package/dist/Adapter.d.mts +1 -1
  3. package/dist/Adapter.mjs +8 -8
  4. package/dist/OAuthPersistence.mjs +10 -5
  5. package/dist/OAuthPersistence.mjs.map +1 -1
  6. package/dist/internal/configuration.d.mts +49 -13
  7. package/dist/internal/configuration.mjs.map +1 -1
  8. package/dist/internal/email-native.mjs +10 -2
  9. package/dist/internal/email-native.mjs.map +1 -1
  10. package/dist/internal/email-policy.mjs +3 -2
  11. package/dist/internal/email-policy.mjs.map +1 -1
  12. package/dist/internal/models/oauth-model.d.mts +4 -2
  13. package/dist/internal/models/oauth-model.mjs.map +1 -1
  14. package/dist/internal/models/password-model.d.mts +2 -1
  15. package/dist/internal/models/password-model.mjs.map +1 -1
  16. package/dist/internal/oauth/native-accounts.d.mts +1 -1
  17. package/dist/internal/oauth/native-accounts.mjs +6 -6
  18. package/dist/internal/oauth/native-accounts.mjs.map +1 -1
  19. package/dist/internal/oauth/native-connected.d.mts +1 -1
  20. package/dist/internal/oauth/native-connected.mjs +1 -1
  21. package/dist/internal/oauth/native-registration.mjs +19 -8
  22. package/dist/internal/oauth/native-registration.mjs.map +1 -1
  23. package/dist/internal/oauth/native-sign-in.d.mts +1 -1
  24. package/dist/internal/oauth/native-sign-in.mjs +1 -1
  25. package/dist/internal/oauth/native-state.mjs +21 -11
  26. package/dist/internal/oauth/native-state.mjs.map +1 -1
  27. package/dist/internal/oauth/state.d.mts +1 -1
  28. package/dist/internal/oauth/state.mjs +1 -1
  29. package/dist/internal/oauth-managed.mjs +182 -0
  30. package/dist/internal/oauth-managed.mjs.map +1 -0
  31. package/dist/internal/passkey-managed.mjs +5 -3
  32. package/dist/internal/passkey-managed.mjs.map +1 -1
  33. package/dist/internal/passkey-native-registration.mjs +1 -1
  34. package/dist/internal/passkey-native-state.mjs +33 -23
  35. package/dist/internal/passkey-native-state.mjs.map +1 -1
  36. package/dist/internal/passkey-native-write.mjs +3 -3
  37. package/dist/internal/passkey-native-write.mjs.map +1 -1
  38. package/dist/internal/passkey-native.d.mts +15 -7
  39. package/dist/internal/passkey-native.mjs +2 -1
  40. package/dist/internal/passkey-native.mjs.map +1 -1
  41. package/dist/internal/passkey-policy.mjs +9 -7
  42. package/dist/internal/passkey-policy.mjs.map +1 -1
  43. package/dist/internal/password-native.mjs +22 -5
  44. package/dist/internal/password-native.mjs.map +1 -1
  45. package/dist/internal/password-policy.mjs +7 -6
  46. package/dist/internal/password-policy.mjs.map +1 -1
  47. package/dist/internal/password-registration-native.d.mts +1 -1
  48. package/dist/internal/persistence.d.mts +3 -0
  49. package/dist/internal/persistence.mjs +99 -42
  50. package/dist/internal/persistence.mjs.map +1 -1
  51. package/dist/internal/phone-native.mjs +1 -1
  52. package/dist/internal/phone-native.mjs.map +1 -1
  53. package/dist/internal/registration.mjs +17 -8
  54. package/dist/internal/registration.mjs.map +1 -1
  55. package/dist/internal/session-native-authority.mjs +8 -6
  56. package/dist/internal/session-native-authority.mjs.map +1 -1
  57. package/dist/internal/session-native-login.d.mts +2 -2
  58. package/dist/internal/session-native-login.mjs +4 -2
  59. package/dist/internal/session-native-login.mjs.map +1 -1
  60. package/dist/internal/session-native-pending-read.mjs +26 -20
  61. package/dist/internal/session-native-pending-read.mjs.map +1 -1
  62. package/dist/internal/session-native-record.mjs +6 -4
  63. package/dist/internal/session-native-record.mjs.map +1 -1
  64. package/dist/internal/session-native-state.mjs +24 -8
  65. package/dist/internal/session-native-state.mjs.map +1 -1
  66. package/dist/internal/session-native-stateful.d.mts +2 -2
  67. package/dist/internal/session-native-stateful.mjs +67 -38
  68. package/dist/internal/session-native-stateful.mjs.map +1 -1
  69. package/dist/internal/session-native-step-up.d.mts +2 -2
  70. package/dist/internal/session-native-step-up.mjs +17 -19
  71. package/dist/internal/session-native-step-up.mjs.map +1 -1
  72. package/dist/internal/session-native-validity.mjs +3 -1
  73. package/dist/internal/session-native-validity.mjs.map +1 -1
  74. package/dist/internal/sql-authority.mjs +5 -1
  75. package/dist/internal/sql-authority.mjs.map +1 -1
  76. package/dist/internal/sql-storage-validation.mjs +2 -1
  77. package/dist/internal/sql-storage-validation.mjs.map +1 -1
  78. package/dist/internal/sql-table.d.mts +1 -0
  79. package/dist/internal/sql-table.mjs.map +1 -1
  80. package/dist/internal/sqlite-policy-guard.mjs +23 -0
  81. package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
  82. package/dist/internal/step-up-state.mjs +19 -6
  83. package/dist/internal/step-up-state.mjs.map +1 -1
  84. package/dist/internal/storage-mapping.d.mts +9 -5
  85. package/dist/internal/storage-mapping.mjs +17 -5
  86. package/dist/internal/storage-mapping.mjs.map +1 -1
  87. package/dist/internal/storage-tables.d.mts +237 -0
  88. package/dist/internal/storage-tables.mjs +68 -0
  89. package/dist/internal/storage-tables.mjs.map +1 -1
  90. package/dist/internal/storage-validation.d.mts +3 -1
  91. package/dist/internal/storage-validation.mjs +70 -17
  92. package/dist/internal/storage-validation.mjs.map +1 -1
  93. package/dist/internal/totp-native.mjs +1 -1
  94. package/package.json +1 -1
  95. package/src/OAuthPersistence.ts +7 -2
  96. package/src/internal/configuration.ts +72 -15
  97. package/src/internal/email-native.ts +16 -6
  98. package/src/internal/email-policy.ts +12 -3
  99. package/src/internal/models/oauth-model.ts +7 -1
  100. package/src/internal/models/password-model.ts +2 -1
  101. package/src/internal/oauth/native-accounts.ts +3 -3
  102. package/src/internal/oauth/native-registration.ts +17 -6
  103. package/src/internal/oauth/native-state.ts +38 -18
  104. package/src/internal/oauth-managed.ts +277 -0
  105. package/src/internal/passkey-managed.ts +8 -2
  106. package/src/internal/passkey-native-state.ts +59 -52
  107. package/src/internal/passkey-native-write.ts +2 -2
  108. package/src/internal/passkey-native.ts +4 -2
  109. package/src/internal/passkey-policy.ts +13 -9
  110. package/src/internal/password-native.ts +45 -11
  111. package/src/internal/password-policy.ts +16 -8
  112. package/src/internal/persistence.ts +360 -204
  113. package/src/internal/phone-native.ts +1 -1
  114. package/src/internal/registration.ts +53 -40
  115. package/src/internal/session-native-authority.ts +26 -8
  116. package/src/internal/session-native-login.ts +9 -4
  117. package/src/internal/session-native-pending-read.ts +46 -33
  118. package/src/internal/session-native-record.ts +6 -4
  119. package/src/internal/session-native-state.ts +32 -10
  120. package/src/internal/session-native-stateful.ts +147 -52
  121. package/src/internal/session-native-step-up.ts +31 -24
  122. package/src/internal/session-native-validity.ts +3 -1
  123. package/src/internal/sql-authority.ts +5 -1
  124. package/src/internal/sql-storage-validation.ts +53 -46
  125. package/src/internal/sql-table.ts +1 -0
  126. package/src/internal/sqlite-policy-guard.ts +34 -0
  127. package/src/internal/step-up-state.ts +43 -6
  128. package/src/internal/storage-mapping.ts +40 -4
  129. package/src/internal/storage-tables.ts +76 -0
  130. package/src/internal/storage-validation.ts +127 -27
@@ -1,18 +1,19 @@
1
1
  import { CurrentCommitJournal, type LifecycleHooks } from "@yielded/auth/Hooks";
2
2
  import type { LoginIdentifier } from "@yielded/auth/Identity";
3
+ import { AuthenticationClock } from "@yielded/auth/Operations";
3
4
  import * as P from "@yielded/auth/Password";
4
5
  import { ProofRedemptionInput } from "@yielded/auth/Proofs";
5
6
  import type { SubjectId } from "@yielded/auth/Schema";
6
7
  import { SecurityRevision, type AuthenticationRequirement } from "@yielded/auth/Sessions";
7
8
  import { DateTime, Effect, Option, Redacted, Schema, Crypto } from "effect";
8
9
  import { SqlClient } from "effect/sql/SqlClient";
9
- import type { Fragment } from "effect/sql/Statement";
10
+ import { fragment, parameter, type Fragment } from "effect/sql/Statement";
10
11
 
11
12
  import { sqlBatchAssertion } from "./d1-planning";
12
13
  import { PersistenceMappingError } from "./mapping-error";
13
14
  import type { AnyPasswordPersistenceMapping } from "./models/password-model";
14
15
  import type { AnyProofPersistenceMapping } from "./models/proof-model";
15
- import type { NativeSqlTables, SqlTable } from "./native-sql-table";
16
+ import { nativeSqlAlias, type NativeSqlTables, type SqlTable } from "./native-sql-table";
16
17
  import { makePasswordCredentials, samePasswordCredentialSnapshot } from "./password-credentials";
17
18
  import {
18
19
  allocatePasswordNextSecurityRevision,
@@ -33,6 +34,7 @@ import {
33
34
  registerSqlPostcondition,
34
35
  SqlBatchCommit,
35
36
  } from "./sql-commit";
37
+ import { guardSqlitePolicy } from "./sqlite-policy-guard";
36
38
  import { requireStandalone } from "./standalone";
37
39
 
38
40
  const unavailable = () => P.PasswordUnavailable.make({});
@@ -51,6 +53,7 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
51
53
  SqlClient | LifecycleHooks | Crypto.Crypto | SqlBatchCommit
52
54
  > {
53
55
  const batch = yield* SqlBatchCommit;
56
+ const clockPolicy = yield* AuthenticationClock;
54
57
  const sql = (yield* SqlClient).withoutTransforms();
55
58
  const executor = yield* makeSqlCommitExecutor(unavailable);
56
59
  const crypto = yield* Crypto.Crypto;
@@ -70,6 +73,12 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
70
73
  rawPassword = tables(c.table),
71
74
  rawAuthority = tables(a.table);
72
75
 
76
+ // Action policy may inspect any application column. Authority columns have
77
+ // separate checks and securityRevision intentionally changes in this mutation.
78
+ const policyColumns = rawSubject.keys.filter(
79
+ (key) => key !== s.id && key !== s.status && key !== s.securityRevision,
80
+ );
81
+
73
82
  const now = tables.expression(mapping.clock.engineNowMillis);
74
83
  const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });
75
84
 
@@ -80,11 +89,12 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
80
89
  sql`${table.column(key)} = ${table.value(key, nativeId)}`;
81
90
 
82
91
  const run = <A, E, R>(work: Effect.Effect<A, E, R>) =>
83
- batch === undefined
92
+ (batch === undefined
84
93
  ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))
85
94
  : executor
86
95
  .batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))
87
- .pipe(Effect.provideService(SqlBatchCommit, batch));
96
+ .pipe(Effect.provideService(SqlBatchCommit, batch))
97
+ ).pipe(Effect.provideService(AuthenticationClock, clockPolicy));
88
98
 
89
99
  const read = Effect.fnUntraced(function* (
90
100
  moduleId: string,
@@ -193,6 +203,17 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
193
203
  return {
194
204
  nativeId,
195
205
  subjectRow,
206
+ policyValues:
207
+ batch === undefined
208
+ ? []
209
+ : policyColumns.map((key) => ({
210
+ column: rawSubject.selectedColumn(key),
211
+ value: fragment([
212
+ parameter(
213
+ rows[0][nativeSqlAlias("password_subject_", rawSubject.keys.indexOf(key))],
214
+ ),
215
+ ]),
216
+ })),
196
217
  identifierRow,
197
218
  passwordRow,
198
219
  revision,
@@ -328,7 +349,9 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
328
349
  verifierVersion !== input.credential?.verifierVersion,
329
350
  );
330
351
 
331
- const freshUntil = (requirement: AuthenticationRequirement) => {
352
+ const freshUntil = Effect.fnUntraced(function* (
353
+ requirement: AuthenticationRequirement,
354
+ ): Effect.fn.Return<number | undefined> {
332
355
  const deadlines = [
333
356
  ...new Set(
334
357
  input.authorization.evidence.proofs.map(
@@ -339,13 +362,24 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
339
362
  .filter((time) => time > state.now)
340
363
  .sort((a, b) => a - b);
341
364
 
342
- return deadlines.find(
343
- (time) => !passwordEvidenceSatisfiedAt(input.authorization.evidence, requirement, time),
344
- );
345
- };
365
+ for (const time of deadlines) {
366
+ if (
367
+ !(yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, requirement, time))
368
+ )
369
+ return time;
370
+ }
371
+ });
372
+
373
+ if (batch !== undefined)
374
+ yield* guardSqlitePolicy({
375
+ name: "password-subject-policy",
376
+ table: rawSubject.name,
377
+ owner: subjectKey(rawSubject, s.id, nativeId),
378
+ values: state.policyValues,
379
+ });
346
380
 
347
- const requestedDeadline = freshUntil(input.authorization.requirement),
348
- currentDeadline = freshUntil(requirement);
381
+ const requestedDeadline = yield* freshUntil(input.authorization.requirement),
382
+ currentDeadline = yield* freshUntil(requirement);
349
383
 
350
384
  ensure(requestedDeadline !== undefined && currentDeadline !== undefined);
351
385
  const deadline = Math.min(requestedDeadline, currentDeadline);
@@ -1,4 +1,5 @@
1
1
  import type { LoginIdentifier } from "@yielded/auth/Identity";
2
+ import { AuthenticationClock } from "@yielded/auth/Operations";
2
3
  import {
3
4
  PasswordUnavailable,
4
5
  snapshotPasswordCredential,
@@ -47,11 +48,13 @@ export const samePasswordCredential = (
47
48
  samePasswordIdentifier(left.identifier, right.identifier) &&
48
49
  Redacted.value(left.verifier) === Redacted.value(right.verifier);
49
50
 
50
- export const passwordEvidenceSatisfiedAt = (
51
+ export const passwordEvidenceSatisfiedAt = Effect.fnUntraced(function* (
51
52
  evidence: AuthenticationEvidence,
52
53
  requirement: AuthenticationRequirement,
53
54
  now: number,
54
- ): boolean => {
55
+ ): Effect.fn.Return<boolean> {
56
+ const { futureToleranceMillis } = yield* AuthenticationClock;
57
+
55
58
  if (evidence.proofs.length > 64 || evidence.revision.credentials.length > 64) return false;
56
59
  const revisions = new Map<string, string>();
57
60
 
@@ -60,12 +63,17 @@ export const passwordEvidenceSatisfiedAt = (
60
63
  revisions.set(item.credentialId, item.revision);
61
64
  }
62
65
  if (evidence.proofs.some((proof) => !revisions.has(proof.credentialId))) return false;
63
- if (evidence.proofs.some((proof) => now < DateTime.toEpochMillis(proof.verifiedAt))) return false;
66
+ if (
67
+ evidence.proofs.some(
68
+ (proof) => DateTime.toEpochMillis(proof.verifiedAt) - now > futureToleranceMillis,
69
+ )
70
+ )
71
+ return false;
64
72
 
65
73
  const eligible = evidence.proofs.filter((proof) => {
66
- const age = now - DateTime.toEpochMillis(proof.verifiedAt);
74
+ const age = Math.max(0, now - DateTime.toEpochMillis(proof.verifiedAt));
67
75
 
68
- return age >= 0 && age < requirement.maximumAgeMillis;
76
+ return age < requirement.maximumAgeMillis;
69
77
  });
70
78
 
71
79
  const factors = new Set(eligible.flatMap((proof) => proof.factors));
@@ -81,7 +89,7 @@ export const passwordEvidenceSatisfiedAt = (
81
89
  (!alternative.phishingResistant || proof.phishingResistant),
82
90
  ),
83
91
  );
84
- };
92
+ });
85
93
 
86
94
  export const snapshotPasswordMutation = Effect.fn("PasswordPersistence.snapshotMutation")(
87
95
  function* (input: PasswordMutationInput) {
@@ -272,8 +280,8 @@ export const validatePasswordMutation = Effect.fnUntraced(function* (
272
280
  const requirement = yield* current.requirement;
273
281
 
274
282
  if (
275
- !passwordEvidenceSatisfiedAt(evidence, input.authorization.requirement, now) ||
276
- !passwordEvidenceSatisfiedAt(evidence, requirement, now)
283
+ !(yield* passwordEvidenceSatisfiedAt(evidence, input.authorization.requirement, now)) ||
284
+ !(yield* passwordEvidenceSatisfiedAt(evidence, requirement, now))
277
285
  )
278
286
  return undefined;
279
287
  if (