@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.
- package/README.md +11 -7
- package/dist/Adapter.d.mts +1 -1
- package/dist/Adapter.mjs +8 -8
- package/dist/OAuthPersistence.mjs +10 -5
- package/dist/OAuthPersistence.mjs.map +1 -1
- package/dist/internal/configuration.d.mts +49 -13
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-native.mjs +10 -2
- package/dist/internal/email-native.mjs.map +1 -1
- package/dist/internal/email-policy.mjs +3 -2
- package/dist/internal/email-policy.mjs.map +1 -1
- package/dist/internal/models/oauth-model.d.mts +4 -2
- package/dist/internal/models/oauth-model.mjs.map +1 -1
- package/dist/internal/models/password-model.d.mts +2 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/oauth/native-accounts.d.mts +1 -1
- package/dist/internal/oauth/native-accounts.mjs +6 -6
- package/dist/internal/oauth/native-accounts.mjs.map +1 -1
- package/dist/internal/oauth/native-connected.d.mts +1 -1
- package/dist/internal/oauth/native-connected.mjs +1 -1
- package/dist/internal/oauth/native-registration.mjs +19 -8
- package/dist/internal/oauth/native-registration.mjs.map +1 -1
- package/dist/internal/oauth/native-sign-in.d.mts +1 -1
- package/dist/internal/oauth/native-sign-in.mjs +1 -1
- package/dist/internal/oauth/native-state.mjs +21 -11
- package/dist/internal/oauth/native-state.mjs.map +1 -1
- package/dist/internal/oauth/state.d.mts +1 -1
- package/dist/internal/oauth/state.mjs +1 -1
- package/dist/internal/oauth-managed.mjs +182 -0
- package/dist/internal/oauth-managed.mjs.map +1 -0
- package/dist/internal/passkey-managed.mjs +5 -3
- package/dist/internal/passkey-managed.mjs.map +1 -1
- package/dist/internal/passkey-native-registration.mjs +1 -1
- package/dist/internal/passkey-native-state.mjs +33 -23
- package/dist/internal/passkey-native-state.mjs.map +1 -1
- package/dist/internal/passkey-native-write.mjs +3 -3
- package/dist/internal/passkey-native-write.mjs.map +1 -1
- package/dist/internal/passkey-native.d.mts +15 -7
- package/dist/internal/passkey-native.mjs +2 -1
- package/dist/internal/passkey-native.mjs.map +1 -1
- package/dist/internal/passkey-policy.mjs +9 -7
- package/dist/internal/passkey-policy.mjs.map +1 -1
- package/dist/internal/password-native.mjs +22 -5
- package/dist/internal/password-native.mjs.map +1 -1
- package/dist/internal/password-policy.mjs +7 -6
- package/dist/internal/password-policy.mjs.map +1 -1
- package/dist/internal/password-registration-native.d.mts +1 -1
- package/dist/internal/persistence.d.mts +3 -0
- package/dist/internal/persistence.mjs +99 -42
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/phone-native.mjs +1 -1
- package/dist/internal/phone-native.mjs.map +1 -1
- package/dist/internal/registration.mjs +17 -8
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +8 -6
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.d.mts +2 -2
- package/dist/internal/session-native-login.mjs +4 -2
- package/dist/internal/session-native-login.mjs.map +1 -1
- package/dist/internal/session-native-pending-read.mjs +26 -20
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-record.mjs +6 -4
- package/dist/internal/session-native-record.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +24 -8
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.d.mts +2 -2
- package/dist/internal/session-native-stateful.mjs +67 -38
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.d.mts +2 -2
- package/dist/internal/session-native-step-up.mjs +17 -19
- package/dist/internal/session-native-step-up.mjs.map +1 -1
- package/dist/internal/session-native-validity.mjs +3 -1
- package/dist/internal/session-native-validity.mjs.map +1 -1
- package/dist/internal/sql-authority.mjs +5 -1
- package/dist/internal/sql-authority.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +2 -1
- package/dist/internal/sql-storage-validation.mjs.map +1 -1
- package/dist/internal/sql-table.d.mts +1 -0
- package/dist/internal/sql-table.mjs.map +1 -1
- package/dist/internal/sqlite-policy-guard.mjs +23 -0
- package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
- package/dist/internal/step-up-state.mjs +19 -6
- package/dist/internal/step-up-state.mjs.map +1 -1
- package/dist/internal/storage-mapping.d.mts +9 -5
- package/dist/internal/storage-mapping.mjs +17 -5
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-tables.d.mts +237 -0
- package/dist/internal/storage-tables.mjs +68 -0
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +3 -1
- package/dist/internal/storage-validation.mjs +70 -17
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/totp-native.mjs +1 -1
- package/package.json +1 -1
- package/src/OAuthPersistence.ts +7 -2
- package/src/internal/configuration.ts +72 -15
- package/src/internal/email-native.ts +16 -6
- package/src/internal/email-policy.ts +12 -3
- package/src/internal/models/oauth-model.ts +7 -1
- package/src/internal/models/password-model.ts +2 -1
- package/src/internal/oauth/native-accounts.ts +3 -3
- package/src/internal/oauth/native-registration.ts +17 -6
- package/src/internal/oauth/native-state.ts +38 -18
- package/src/internal/oauth-managed.ts +277 -0
- package/src/internal/passkey-managed.ts +8 -2
- package/src/internal/passkey-native-state.ts +59 -52
- package/src/internal/passkey-native-write.ts +2 -2
- package/src/internal/passkey-native.ts +4 -2
- package/src/internal/passkey-policy.ts +13 -9
- package/src/internal/password-native.ts +45 -11
- package/src/internal/password-policy.ts +16 -8
- package/src/internal/persistence.ts +360 -204
- package/src/internal/phone-native.ts +1 -1
- package/src/internal/registration.ts +53 -40
- package/src/internal/session-native-authority.ts +26 -8
- package/src/internal/session-native-login.ts +9 -4
- package/src/internal/session-native-pending-read.ts +46 -33
- package/src/internal/session-native-record.ts +6 -4
- package/src/internal/session-native-state.ts +32 -10
- package/src/internal/session-native-stateful.ts +147 -52
- package/src/internal/session-native-step-up.ts +31 -24
- package/src/internal/session-native-validity.ts +3 -1
- package/src/internal/sql-authority.ts +5 -1
- package/src/internal/sql-storage-validation.ts +53 -46
- package/src/internal/sql-table.ts +1 -0
- package/src/internal/sqlite-policy-guard.ts +34 -0
- package/src/internal/step-up-state.ts +43 -6
- package/src/internal/storage-mapping.ts +40 -4
- package/src/internal/storage-tables.ts +76 -0
- 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
|
|
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
|
|
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 = (
|
|
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
|
-
|
|
343
|
-
|
|
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 (
|
|
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
|
|
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 (
|