@yielded/auth-persistence-drizzle 0.1.0-beta.21 → 0.1.0-beta.23
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/dist/Libsql.d.mts +19 -19
- package/dist/Mysql2.d.mts +14 -14
- package/dist/Pglite.d.mts +14 -14
- package/dist/Postgres.d.mts +6 -6
- package/dist/SqliteBun.d.mts +27 -27
- package/dist/SqliteDo.d.mts +13 -13
- package/dist/SqliteNode.d.mts +23 -23
- package/dist/SqliteWasm.d.mts +13 -13
- package/dist/drizzle/d1-password-prepared.d.mts +3 -3
- package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
- package/dist/drizzle/d1-passwords.d.mts +8 -9
- package/dist/drizzle/d1-passwords.mjs +103 -173
- package/dist/drizzle/d1-passwords.mjs.map +1 -1
- package/dist/drizzle/d1-proofs.d.mts +1 -1
- package/dist/drizzle/d1-sessions.d.mts +1 -1
- package/dist/drizzle/mysql-password-prepared.d.mts +5 -5
- package/dist/drizzle/mysql-password-prepared.mjs.map +1 -1
- package/dist/drizzle/mysql-passwords.d.mts +9 -9
- package/dist/drizzle/mysql-passwords.mjs.map +1 -1
- package/dist/drizzle/native-sql-table.mjs +110 -0
- package/dist/drizzle/native-sql-table.mjs.map +1 -0
- package/dist/drizzle/oauth-connected-target.d.mts +5 -5
- package/dist/drizzle/oauth-target.d.mts +1 -1
- package/dist/drizzle/password-model.d.mts +5 -7
- package/dist/drizzle/password-prepared-model.d.mts +5 -5
- package/dist/drizzle/password-prepared-model.mjs.map +1 -1
- package/dist/drizzle/password-sql.mjs +2 -1
- package/dist/drizzle/password-sql.mjs.map +1 -1
- package/dist/drizzle/password-target.mjs +13 -2
- package/dist/drizzle/password-target.mjs.map +1 -1
- package/dist/drizzle/pg-password-prepared.d.mts +5 -5
- package/dist/drizzle/pg-password-prepared.mjs.map +1 -1
- package/dist/drizzle/pg-passwords.d.mts +9 -9
- package/dist/drizzle/pg-passwords.mjs.map +1 -1
- package/dist/drizzle/phone-target.d.mts +1 -1
- package/dist/drizzle/session-sql.mjs +2 -1
- package/dist/drizzle/session-sql.mjs.map +1 -1
- package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -1
- package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
- package/dist/drizzle/storage-validation.mjs +0 -13
- package/dist/drizzle/storage-validation.mjs.map +1 -1
- package/dist/drizzle/totp-target.d.mts +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/internal/drizzle-postgres.mjs +2 -0
- package/dist/internal/drizzle-postgres.mjs.map +1 -1
- package/dist/internal/drizzle-sqlite.mjs +2 -0
- package/dist/internal/drizzle-sqlite.mjs.map +1 -1
- package/package.json +1 -1
- package/src/drizzle/d1-password-prepared.ts +2 -20
- package/src/drizzle/d1-passwords.ts +232 -519
- package/src/drizzle/mysql-password-prepared.ts +4 -12
- package/src/drizzle/mysql-passwords.ts +5 -15
- package/src/drizzle/native-sql-table.ts +229 -0
- package/src/drizzle/password-model.ts +0 -20
- package/src/drizzle/password-prepared-model.ts +0 -12
- package/src/drizzle/password-sql.ts +3 -0
- package/src/drizzle/password-target.ts +41 -4
- package/src/drizzle/pg-password-prepared.ts +4 -12
- package/src/drizzle/pg-passwords.ts +5 -15
- package/src/drizzle/session-sql.ts +2 -1
- package/src/drizzle/sqlite-password-prepared.ts +4 -48
- package/src/drizzle/sqlite-passwords.ts +7 -21
- package/src/drizzle/storage-validation.ts +0 -8
- package/src/index.ts +0 -4
- package/src/internal/drizzle-postgres.ts +2 -0
- package/src/internal/drizzle-sqlite.ts +2 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any -- D1 plans bridge consumer-owned Drizzle rows and SQL statements. */
|
|
2
2
|
import type { D1Client } from "@effect/sql-d1/D1Client";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
NativeDatabase,
|
|
5
|
+
requireStandalone,
|
|
6
|
+
samePasswordCredentialSnapshot as sameSnapshot,
|
|
7
|
+
} from "@yielded/auth-persistence/Adapter";
|
|
4
8
|
import {
|
|
5
9
|
coordinateCommit,
|
|
6
10
|
CurrentCommitJournal,
|
|
@@ -23,7 +27,8 @@ import {
|
|
|
23
27
|
type PasswordAttemptDecision,
|
|
24
28
|
type PasswordCredentialSnapshot,
|
|
25
29
|
type PasswordMutationDecision,
|
|
26
|
-
type
|
|
30
|
+
type PasswordAttemptPreparation,
|
|
31
|
+
type PasswordAttemptAdmission,
|
|
27
32
|
} from "@yielded/auth/Password";
|
|
28
33
|
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
29
34
|
import {
|
|
@@ -38,20 +43,7 @@ import {
|
|
|
38
43
|
AuthenticationRequirement,
|
|
39
44
|
type SecurityRevision,
|
|
40
45
|
} from "@yielded/auth/Sessions";
|
|
41
|
-
import {
|
|
42
|
-
and,
|
|
43
|
-
count,
|
|
44
|
-
eq,
|
|
45
|
-
gte,
|
|
46
|
-
inArray,
|
|
47
|
-
lte,
|
|
48
|
-
notExists,
|
|
49
|
-
sql,
|
|
50
|
-
type AnyRelations,
|
|
51
|
-
type SQL,
|
|
52
|
-
type Table,
|
|
53
|
-
} from "drizzle-orm";
|
|
54
|
-
import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core";
|
|
46
|
+
import { and, eq, inArray, lte, sql, type AnyRelations, type SQL, type Table } from "drizzle-orm";
|
|
55
47
|
import type { EffectSQLiteD1Database } from "drizzle-orm/effect-d1";
|
|
56
48
|
import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
|
|
57
49
|
import { Cause, DateTime, Effect, Option, Predicate, Redacted, Schema, Context } from "effect";
|
|
@@ -68,9 +60,7 @@ import {
|
|
|
68
60
|
type AnyPasswordRegistrationMapping,
|
|
69
61
|
type D1PasswordPersistenceMapping,
|
|
70
62
|
type PasswordAttemptRecord,
|
|
71
|
-
type PasswordRateScopeKind,
|
|
72
63
|
type PasswordRegistrationMapping,
|
|
73
|
-
type PasswordScopeKeys,
|
|
74
64
|
requiredPasswordConstraints,
|
|
75
65
|
requiredPasswordRegistrationConstraints,
|
|
76
66
|
} from "./password-model";
|
|
@@ -85,24 +75,14 @@ type PlanPrepare<Method extends (...args: any[]) => any, A> = (
|
|
|
85
75
|
) => PreparedCommit<A>;
|
|
86
76
|
|
|
87
77
|
type Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };
|
|
88
|
-
type Mapping = D1PasswordPersistenceMapping<
|
|
89
|
-
Table,
|
|
90
|
-
Table,
|
|
91
|
-
Table,
|
|
92
|
-
Table,
|
|
93
|
-
Table,
|
|
94
|
-
Table,
|
|
95
|
-
Table,
|
|
96
|
-
Table,
|
|
97
|
-
unknown
|
|
98
|
-
>;
|
|
78
|
+
type Mapping = D1PasswordPersistenceMapping<Table, Table, Table, Table, Table, Table, unknown>;
|
|
99
79
|
type RegistrationMapping<Registration> = AnyPasswordRegistrationMapping<Registration>;
|
|
100
|
-
type AdapterFailure = PersistenceMappingError | EffectDrizzleQueryError;
|
|
101
|
-
const mapAdapter = <A>(effect: Effect.Effect<A, AdapterFailure>) => effect;
|
|
102
80
|
|
|
103
81
|
interface Planned<A> {
|
|
104
82
|
readonly receipt: A;
|
|
105
83
|
readonly statements: ReadonlyArray<Statement<any>>;
|
|
84
|
+
/** Final-state guards without `changes()`, repeated after coordinated statements. */
|
|
85
|
+
readonly postconditions?: ReadonlyArray<Statement<any>>;
|
|
106
86
|
readonly retryable?: (cause: unknown) => boolean;
|
|
107
87
|
readonly journalGuard?: PreparedCommit<void>;
|
|
108
88
|
}
|
|
@@ -290,23 +270,6 @@ const attemptColumns = (mapping: Mapping) => ({
|
|
|
290
270
|
state: column(mapping.attempt.table, mapping.attempt.state),
|
|
291
271
|
});
|
|
292
272
|
|
|
293
|
-
const scopeColumns = (mapping: Mapping) => ({
|
|
294
|
-
moduleId: column(mapping.rateScope.table, mapping.rateScope.moduleId),
|
|
295
|
-
action: column(mapping.rateScope.table, mapping.rateScope.action),
|
|
296
|
-
scopeKind: column(mapping.rateScope.table, mapping.rateScope.scopeKind),
|
|
297
|
-
scopeKey: column(mapping.rateScope.table, mapping.rateScope.scopeKey),
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const chargeColumns = (mapping: Mapping) => ({
|
|
301
|
-
moduleId: column(mapping.charge.table, mapping.charge.moduleId),
|
|
302
|
-
action: column(mapping.charge.table, mapping.charge.action),
|
|
303
|
-
scopeKind: column(mapping.charge.table, mapping.charge.scopeKind),
|
|
304
|
-
scopeKey: column(mapping.charge.table, mapping.charge.scopeKey),
|
|
305
|
-
attemptId: column(mapping.charge.table, mapping.charge.attemptId),
|
|
306
|
-
occurredAt: column(mapping.charge.table, mapping.charge.occurredAt),
|
|
307
|
-
retentionUntil: column(mapping.charge.table, mapping.charge.retentionUntil),
|
|
308
|
-
});
|
|
309
|
-
|
|
310
273
|
const commandColumns = (mapping: Mapping) => ({
|
|
311
274
|
moduleId: column(mapping.command.table, mapping.command.moduleId),
|
|
312
275
|
commandId: column(mapping.command.table, mapping.command.commandId),
|
|
@@ -396,137 +359,6 @@ const resolveCredential = Effect.fn("DrizzleD1Password.resolveCredential")(funct
|
|
|
396
359
|
return { nativeSubjectId, subject, identifier: identifierRow, credential, snapshot };
|
|
397
360
|
});
|
|
398
361
|
|
|
399
|
-
interface ScopeEntry {
|
|
400
|
-
readonly kind: PasswordRateScopeKind;
|
|
401
|
-
readonly key: string;
|
|
402
|
-
readonly limit: number;
|
|
403
|
-
readonly windowMillis: number;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const scopeEntries = (keys: PasswordScopeKeys, policy: PasswordAttemptPolicy) => {
|
|
407
|
-
const entries: ScopeEntry[] = [
|
|
408
|
-
{ kind: "action", key: keys.action, ...policy.action },
|
|
409
|
-
{ kind: "identifier", key: keys.identifier, ...policy.identifier },
|
|
410
|
-
];
|
|
411
|
-
|
|
412
|
-
if (keys.subject !== undefined)
|
|
413
|
-
entries.push({ kind: "subject", key: keys.subject, ...policy.subject });
|
|
414
|
-
|
|
415
|
-
return entries;
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
const livePendingAttempts = (
|
|
419
|
-
mapping: Mapping,
|
|
420
|
-
moduleId: string,
|
|
421
|
-
action: "sign-in" | "change",
|
|
422
|
-
entry: ScopeEntry,
|
|
423
|
-
identifier: LoginIdentifier,
|
|
424
|
-
nativeSubjectId: unknown | undefined,
|
|
425
|
-
activeAt: unknown,
|
|
426
|
-
) => {
|
|
427
|
-
const a = attemptColumns(mapping);
|
|
428
|
-
|
|
429
|
-
return and(
|
|
430
|
-
entry.kind === "action"
|
|
431
|
-
? and(eq(a.moduleId, moduleId), eq(a.action, action))
|
|
432
|
-
: entry.kind === "identifier"
|
|
433
|
-
? and(
|
|
434
|
-
eq(a.moduleId, moduleId),
|
|
435
|
-
eq(a.action, action),
|
|
436
|
-
eq(a.identifierNamespace, identifier.namespace),
|
|
437
|
-
eq(a.identifierValue, identifier.value),
|
|
438
|
-
)
|
|
439
|
-
: and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.subjectId, nativeSubjectId)),
|
|
440
|
-
eq(a.state, "pending"),
|
|
441
|
-
sql`${a.retentionUntil} > ${activeAt}`,
|
|
442
|
-
);
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
const scopeAdmissionCondition = (
|
|
446
|
-
mapping: Mapping,
|
|
447
|
-
moduleId: string,
|
|
448
|
-
action: "sign-in" | "change",
|
|
449
|
-
entry: ScopeEntry,
|
|
450
|
-
policy: PasswordAttemptPolicy,
|
|
451
|
-
identifier: LoginIdentifier,
|
|
452
|
-
nativeSubjectId: unknown | undefined,
|
|
453
|
-
) => {
|
|
454
|
-
const c = chargeColumns(mapping);
|
|
455
|
-
|
|
456
|
-
const pending = livePendingAttempts(
|
|
457
|
-
mapping,
|
|
458
|
-
moduleId,
|
|
459
|
-
action,
|
|
460
|
-
entry,
|
|
461
|
-
identifier,
|
|
462
|
-
nativeSubjectId,
|
|
463
|
-
mapping.d1.engineNow,
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
return sql`(select count(*) from ${mapping.charge.table} where ${c.moduleId} = ${sql.param(moduleId, c.moduleId)} and ${c.action} = ${sql.param(action, c.action)} and ${c.scopeKind} = ${sql.param(entry.kind, c.scopeKind)} and ${c.scopeKey} = ${sql.param(entry.key, c.scopeKey)} and ${c.occurredAt} >= ${mapping.d1.engineInstantMinus(entry.windowMillis)}) < ${entry.limit} and (select count(*) from ${mapping.attempt.table} where ${pending}) < ${policy.maximumPending}`;
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
const readScopeAdmission = Effect.fn("DrizzleD1Password.readScopeAdmission")(function* (
|
|
470
|
-
mapping: Mapping,
|
|
471
|
-
moduleId: string,
|
|
472
|
-
action: "sign-in" | "change",
|
|
473
|
-
entries: ReadonlyArray<ScopeEntry>,
|
|
474
|
-
policy: PasswordAttemptPolicy,
|
|
475
|
-
identifier: LoginIdentifier,
|
|
476
|
-
nativeSubjectId: unknown | undefined,
|
|
477
|
-
now: number,
|
|
478
|
-
) {
|
|
479
|
-
const database = yield* CurrentD1PlanningDatabase;
|
|
480
|
-
|
|
481
|
-
const c = chargeColumns(mapping);
|
|
482
|
-
const admitted: ScopeEntry[] = [];
|
|
483
|
-
|
|
484
|
-
for (let index = 0; index < entries.length; index++) {
|
|
485
|
-
const entry = entries[index]!;
|
|
486
|
-
|
|
487
|
-
const chargeRows = yield* mapAdapter(
|
|
488
|
-
database
|
|
489
|
-
.select({ total: count() })
|
|
490
|
-
.from(mapping.charge.table)
|
|
491
|
-
.where(
|
|
492
|
-
and(
|
|
493
|
-
eq(c.moduleId, moduleId),
|
|
494
|
-
eq(c.action, action),
|
|
495
|
-
eq(c.scopeKind, entry.kind),
|
|
496
|
-
eq(c.scopeKey, entry.key),
|
|
497
|
-
gte(c.occurredAt, mapping.encodeInstant(now - entry.windowMillis)),
|
|
498
|
-
),
|
|
499
|
-
),
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
const pendingRows = yield* mapAdapter(
|
|
503
|
-
database
|
|
504
|
-
.select({ total: count() })
|
|
505
|
-
.from(mapping.attempt.table)
|
|
506
|
-
.where(
|
|
507
|
-
livePendingAttempts(
|
|
508
|
-
mapping,
|
|
509
|
-
moduleId,
|
|
510
|
-
action,
|
|
511
|
-
entry,
|
|
512
|
-
identifier,
|
|
513
|
-
nativeSubjectId,
|
|
514
|
-
mapping.encodeInstant(now),
|
|
515
|
-
),
|
|
516
|
-
),
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
const admittedHere =
|
|
520
|
-
Number(chargeRows[0]?.total ?? 0) < entry.limit &&
|
|
521
|
-
Number(pendingRows[0]?.total ?? 0) < policy.maximumPending;
|
|
522
|
-
|
|
523
|
-
if (admittedHere) admitted.push(entry);
|
|
524
|
-
else if (index === 0) break;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return admitted;
|
|
528
|
-
});
|
|
529
|
-
|
|
530
362
|
const evidenceProofsJson = Schema.encodeSync(
|
|
531
363
|
Schema.fromJsonString(AuthenticationEvidence.fields.proofs),
|
|
532
364
|
);
|
|
@@ -555,25 +387,6 @@ const assuranceCondition = (
|
|
|
555
387
|
const sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>
|
|
556
388
|
left.namespace === right.namespace && left.value === right.value;
|
|
557
389
|
|
|
558
|
-
const sameSnapshot = (left: PasswordCredentialSnapshot, right: PasswordCredentialSnapshot) =>
|
|
559
|
-
left.moduleId === right.moduleId &&
|
|
560
|
-
left.revision.subjectId === right.revision.subjectId &&
|
|
561
|
-
left.revision.securityRevision === right.revision.securityRevision &&
|
|
562
|
-
left.credentialId === right.credentialId &&
|
|
563
|
-
left.credentialRevision === right.credentialRevision &&
|
|
564
|
-
left.verifierVersion === right.verifierVersion &&
|
|
565
|
-
left.normalization === right.normalization &&
|
|
566
|
-
left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis &&
|
|
567
|
-
left.revision.credentials.length === right.revision.credentials.length &&
|
|
568
|
-
left.revision.credentials.every((entry) =>
|
|
569
|
-
right.revision.credentials.some(
|
|
570
|
-
(other) => other.credentialId === entry.credentialId && other.revision === entry.revision,
|
|
571
|
-
),
|
|
572
|
-
) &&
|
|
573
|
-
left.identifierBindingRevision === right.identifierBindingRevision &&
|
|
574
|
-
sameIdentifier(left.identifier, right.identifier) &&
|
|
575
|
-
Redacted.value(left.verifier) === Redacted.value(right.verifier);
|
|
576
|
-
|
|
577
390
|
const evidenceSatisfiedAt = (
|
|
578
391
|
evidence: PasswordMutationInput["authorization"]["evidence"],
|
|
579
392
|
requirement: PasswordMutationInput["authorization"]["requirement"],
|
|
@@ -947,7 +760,7 @@ const admittedSnapshotCondition = (
|
|
|
947
760
|
identifier: snapshot.identifier,
|
|
948
761
|
nativeSubjectId: resolved.nativeSubjectId,
|
|
949
762
|
}),
|
|
950
|
-
sql`exists(select 1 from ${mapping.identifier.table} where ${i.namespace} = ${sql.param(snapshot.identifier.namespace, i.namespace)} and ${i.value} = ${sql.param(snapshot.identifier.value, i.value)} and ${i.subjectId} = ${sql.param(resolved.nativeSubjectId, i.subjectId)} and ${i.bindingRevision} = ${sql.param(snapshot.identifierBindingRevision, i.bindingRevision)})`,
|
|
763
|
+
sql`exists(select 1 from ${mapping.identifier.table} where ${i.namespace} = ${sql.param(snapshot.identifier.namespace, i.namespace)} and ${i.value} = ${sql.param(snapshot.identifier.value, i.value)} and ${i.subjectId} = ${sql.param(resolved.nativeSubjectId, i.subjectId)} and ${i.bindingRevision} = ${sql.param(snapshot.identifierBindingRevision, i.bindingRevision)} and ${Predicate.isNullish(resolved.identifier[mapping.identifier.verifiedAt]) ? sql`${i.verifiedAt} is null` : sql`${i.verifiedAt} = ${sql.param(resolved.identifier[mapping.identifier.verifiedAt], i.verifiedAt)}`})`,
|
|
951
764
|
sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(resolved.nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(snapshot.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(snapshot.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(snapshot.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(snapshot.verifier), c.verifier)} and ${c.normalization} = ${sql.param(snapshot.normalization, c.normalization)})`,
|
|
952
765
|
...snapshot.revision.credentials.map(
|
|
953
766
|
(item) =>
|
|
@@ -1150,6 +963,7 @@ const compileAddMutation = Effect.fn("DrizzleD1Password.compileAddMutation")(fun
|
|
|
1150
963
|
return {
|
|
1151
964
|
statements,
|
|
1152
965
|
appliedCondition,
|
|
966
|
+
marker,
|
|
1153
967
|
retryable: (cause: unknown) => isGuardFailure(cause, marker),
|
|
1154
968
|
};
|
|
1155
969
|
});
|
|
@@ -1179,200 +993,125 @@ const makePasswordPlans = (
|
|
|
1179
993
|
});
|
|
1180
994
|
|
|
1181
995
|
return {
|
|
1182
|
-
|
|
1183
|
-
input: Parameters<PasswordPersistence["Service"]["admitAttempt"]>[0],
|
|
1184
|
-
prepare: PlanPrepare<PasswordPersistence["Service"]["admitAttempt"], A>,
|
|
1185
|
-
) =>
|
|
996
|
+
prepareAttempt: (uncaptured: Parameters<PasswordPersistence["Service"]["prepareAttempt"]>[0]) =>
|
|
1186
997
|
Effect.gen(function* () {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
|
|
1190
|
-
|
|
1191
|
-
return yield* run(
|
|
1192
|
-
Effect.gen(function* () {
|
|
1193
|
-
const journal = yield* CurrentCommitJournal;
|
|
1194
|
-
|
|
1195
|
-
const now = yield* readEngineNowMillis(mapping);
|
|
1196
|
-
|
|
1197
|
-
const resolved = yield* resolveCredential(
|
|
1198
|
-
mapping,
|
|
1199
|
-
input.moduleId,
|
|
1200
|
-
input.identifier,
|
|
1201
|
-
input.subjectId,
|
|
1202
|
-
);
|
|
998
|
+
if (!validConstraints(mapping)) return yield* unavailable();
|
|
1203
999
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
...(resolved?.snapshot === undefined
|
|
1209
|
-
? {}
|
|
1210
|
-
: { subjectId: resolved.snapshot.revision.subjectId }),
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
const scopes = scopeEntries(keys, input.policy);
|
|
1214
|
-
|
|
1215
|
-
const admitted = yield* readScopeAdmission(
|
|
1216
|
-
mapping,
|
|
1217
|
-
input.moduleId,
|
|
1218
|
-
input.action,
|
|
1219
|
-
scopes,
|
|
1220
|
-
input.policy,
|
|
1221
|
-
input.identifier,
|
|
1222
|
-
resolved?.nativeSubjectId,
|
|
1223
|
-
now,
|
|
1224
|
-
);
|
|
1000
|
+
const input = Object.freeze({
|
|
1001
|
+
...uncaptured,
|
|
1002
|
+
identifier: Object.freeze({ ...uncaptured.identifier }),
|
|
1003
|
+
});
|
|
1225
1004
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1005
|
+
const resolved = yield* resolveCredential(
|
|
1006
|
+
mapping,
|
|
1007
|
+
input.moduleId,
|
|
1008
|
+
input.identifier,
|
|
1009
|
+
input.subjectId,
|
|
1010
|
+
);
|
|
1228
1011
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
_tag: "Admitted" as const,
|
|
1233
|
-
attemptId,
|
|
1234
|
-
...(resolved?.snapshot === undefined ? {} : { credential: resolved.snapshot }),
|
|
1235
|
-
}
|
|
1236
|
-
: { _tag: "Denied" as const },
|
|
1237
|
-
journal,
|
|
1238
|
-
);
|
|
1012
|
+
const snapshot = resolved?.snapshot;
|
|
1013
|
+
const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
|
|
1014
|
+
let used = false;
|
|
1239
1015
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1016
|
+
return Object.freeze({
|
|
1017
|
+
...(snapshot === undefined ? {} : { credential: snapshot }),
|
|
1018
|
+
// Claim outside execution: a definite batch rollback may replan other
|
|
1019
|
+
// operations, but a prepared admission never consumes its limits twice.
|
|
1020
|
+
claim: Effect.suspend(() => {
|
|
1021
|
+
if (used) return Effect.fail(unavailable());
|
|
1022
|
+
used = true;
|
|
1242
1023
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1024
|
+
return Effect.void;
|
|
1025
|
+
}),
|
|
1026
|
+
admit: <A>(prepare: PreparePasswordCommit<PasswordAttemptAdmission, A>) =>
|
|
1027
|
+
run(
|
|
1028
|
+
Effect.gen(function* () {
|
|
1029
|
+
const database = yield* CurrentD1PlanningDatabase;
|
|
1030
|
+
const journal = yield* CurrentCommitJournal;
|
|
1031
|
+
const now = yield* readEngineNowMillis(mapping);
|
|
1032
|
+
const marker = `effect-auth-password-guard:admit:${attemptId}`;
|
|
1033
|
+
const a = attemptColumns(mapping);
|
|
1034
|
+
const key = and(eq(a.moduleId, input.moduleId), eq(a.attemptId, attemptId))!;
|
|
1035
|
+
const current = admittedSnapshotCondition(mapping, resolved, input.moduleId);
|
|
1036
|
+
|
|
1037
|
+
const record: PasswordAttemptRecord = {
|
|
1038
|
+
moduleId: input.moduleId,
|
|
1039
|
+
action: input.action,
|
|
1040
|
+
attemptId,
|
|
1041
|
+
identifier: input.identifier,
|
|
1042
|
+
...(snapshot === undefined
|
|
1043
|
+
? {}
|
|
1044
|
+
: {
|
|
1045
|
+
subjectId: snapshot.revision.subjectId,
|
|
1046
|
+
credentialId: snapshot.credentialId,
|
|
1047
|
+
securityRevision: snapshot.revision.securityRevision,
|
|
1048
|
+
credentialRevision: snapshot.credentialRevision,
|
|
1049
|
+
verifierVersion: snapshot.verifierVersion,
|
|
1050
|
+
identifierBindingRevision: snapshot.identifierBindingRevision,
|
|
1254
1051
|
}),
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
input.identifier,
|
|
1269
|
-
resolved?.nativeSubjectId,
|
|
1270
|
-
);
|
|
1271
|
-
|
|
1272
|
-
return admitted.includes(entry) ? condition : sql`not (${condition})`;
|
|
1273
|
-
});
|
|
1052
|
+
admittedAtMillis: now,
|
|
1053
|
+
deadlineMillis: now + input.attemptLifetimeMillis,
|
|
1054
|
+
retentionUntilMillis: now + input.attemptLifetimeMillis,
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
const receipt = prepare(
|
|
1058
|
+
{
|
|
1059
|
+
_tag: "Admitted",
|
|
1060
|
+
attemptId,
|
|
1061
|
+
...(snapshot === undefined ? {} : { credential: snapshot }),
|
|
1062
|
+
},
|
|
1063
|
+
journal,
|
|
1064
|
+
);
|
|
1274
1065
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
statements.push(
|
|
1288
|
-
yield* statement(
|
|
1289
|
-
database.insert(mapping.charge.table).values(
|
|
1290
|
-
driverValues(
|
|
1291
|
-
mapping.charge.encodeInsert({
|
|
1292
|
-
moduleId: input.moduleId,
|
|
1293
|
-
action: input.action,
|
|
1294
|
-
scopeKind: entry.kind,
|
|
1295
|
-
scopeKey: entry.key,
|
|
1296
|
-
attemptId,
|
|
1297
|
-
occurredAtMillis: now,
|
|
1298
|
-
retentionUntilMillis: now + entry.windowMillis,
|
|
1299
|
-
}),
|
|
1300
|
-
[
|
|
1301
|
-
[mapping.charge.occurredAt, mapping.d1.engineNow],
|
|
1302
|
-
[
|
|
1303
|
-
mapping.charge.retentionUntil,
|
|
1304
|
-
mapping.d1.engineInstantPlus(entry.windowMillis),
|
|
1305
|
-
],
|
|
1306
|
-
],
|
|
1307
|
-
),
|
|
1066
|
+
const admitted = and(
|
|
1067
|
+
current,
|
|
1068
|
+
sql`exists(select 1 from ${mapping.attempt.table} where ${key} and ${a.state} = ${"pending"} and ${a.deadline} > ${mapping.d1.engineNow})`,
|
|
1069
|
+
)!;
|
|
1070
|
+
|
|
1071
|
+
const statements: Statement<any>[] = [
|
|
1072
|
+
yield* assertion(
|
|
1073
|
+
and(
|
|
1074
|
+
current,
|
|
1075
|
+
sql`not exists(select 1 from ${mapping.attempt.table} where ${key})`,
|
|
1076
|
+
)!,
|
|
1077
|
+
marker,
|
|
1308
1078
|
),
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
identifier: input.identifier,
|
|
1319
|
-
...(snapshot === undefined
|
|
1320
|
-
? {}
|
|
1321
|
-
: {
|
|
1322
|
-
subjectId: snapshot.revision.subjectId,
|
|
1323
|
-
credentialId: snapshot.credentialId,
|
|
1324
|
-
securityRevision: snapshot.revision.securityRevision,
|
|
1325
|
-
credentialRevision: snapshot.credentialRevision,
|
|
1326
|
-
verifierVersion: snapshot.verifierVersion,
|
|
1327
|
-
identifierBindingRevision: snapshot.identifierBindingRevision,
|
|
1328
|
-
}),
|
|
1329
|
-
admittedAtMillis: now,
|
|
1330
|
-
deadlineMillis: now + input.policy.attemptLifetimeMillis,
|
|
1331
|
-
retentionUntilMillis: Math.max(
|
|
1332
|
-
now + input.policy.attemptLifetimeMillis,
|
|
1333
|
-
...scopes.map((entry) => now + entry.windowMillis),
|
|
1334
|
-
),
|
|
1335
|
-
};
|
|
1336
|
-
|
|
1337
|
-
statements.push(
|
|
1338
|
-
yield* statement(
|
|
1339
|
-
database.insert(mapping.attempt.table).values(
|
|
1340
|
-
driverValues(
|
|
1341
|
-
mapping.attempt.encodeInsert(record, {
|
|
1342
|
-
...(resolved?.nativeSubjectId === undefined
|
|
1343
|
-
? {}
|
|
1344
|
-
: { nativeSubjectId: resolved.nativeSubjectId }),
|
|
1345
|
-
state: "pending",
|
|
1346
|
-
}),
|
|
1347
|
-
[
|
|
1348
|
-
[mapping.attempt.admittedAt, mapping.d1.engineNow],
|
|
1349
|
-
[
|
|
1350
|
-
mapping.attempt.deadline,
|
|
1351
|
-
mapping.d1.engineInstantPlus(input.policy.attemptLifetimeMillis),
|
|
1352
|
-
],
|
|
1079
|
+
yield* statement(
|
|
1080
|
+
database.insert(mapping.attempt.table).values(
|
|
1081
|
+
driverValues(
|
|
1082
|
+
mapping.attempt.encodeInsert(record, {
|
|
1083
|
+
...(resolved?.nativeSubjectId === undefined
|
|
1084
|
+
? {}
|
|
1085
|
+
: { nativeSubjectId: resolved.nativeSubjectId }),
|
|
1086
|
+
state: "pending",
|
|
1087
|
+
}),
|
|
1353
1088
|
[
|
|
1354
|
-
mapping.attempt.
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1089
|
+
[mapping.attempt.admittedAt, mapping.d1.engineNow],
|
|
1090
|
+
[
|
|
1091
|
+
mapping.attempt.deadline,
|
|
1092
|
+
mapping.d1.engineInstantPlus(input.attemptLifetimeMillis),
|
|
1093
|
+
],
|
|
1094
|
+
[
|
|
1095
|
+
mapping.attempt.retentionUntil,
|
|
1096
|
+
mapping.d1.engineInstantPlus(input.attemptLifetimeMillis),
|
|
1097
|
+
],
|
|
1361
1098
|
],
|
|
1362
|
-
|
|
1099
|
+
),
|
|
1363
1100
|
),
|
|
1364
1101
|
),
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1102
|
+
yield* assertion(and(sql`changes() = 1`, admitted)!, marker),
|
|
1103
|
+
];
|
|
1104
|
+
|
|
1105
|
+
// A failed captured-snapshot guard cannot authorize refreshing the
|
|
1106
|
+
// candidate after the strategy has consumed that subject's limit.
|
|
1107
|
+
return {
|
|
1108
|
+
receipt,
|
|
1109
|
+
statements,
|
|
1110
|
+
postconditions: [yield* assertion(admitted, marker)],
|
|
1111
|
+
};
|
|
1112
|
+
}),
|
|
1113
|
+
),
|
|
1114
|
+
});
|
|
1376
1115
|
}),
|
|
1377
1116
|
settleAttempt: <A>(
|
|
1378
1117
|
uncaptured: Parameters<PasswordPersistence["Service"]["settleAttempt"]>[0],
|
|
@@ -1456,8 +1195,24 @@ const makePasswordPlans = (
|
|
|
1456
1195
|
eq(a.state, "pending"),
|
|
1457
1196
|
)!;
|
|
1458
1197
|
|
|
1198
|
+
const attemptSnapshot =
|
|
1199
|
+
verified && resolved?.snapshot !== undefined
|
|
1200
|
+
? and(
|
|
1201
|
+
eq(a.action, row[mapping.attempt.action]),
|
|
1202
|
+
eq(a.subjectId, resolved.nativeSubjectId),
|
|
1203
|
+
eq(a.identifierNamespace, input.captured!.identifier.namespace),
|
|
1204
|
+
eq(a.identifierValue, input.captured!.identifier.value),
|
|
1205
|
+
eq(a.credentialId, input.captured!.credentialId),
|
|
1206
|
+
eq(a.securityRevision, input.captured!.revision.securityRevision),
|
|
1207
|
+
eq(a.credentialRevision, input.captured!.credentialRevision),
|
|
1208
|
+
eq(a.verifierVersion, input.captured!.verifierVersion),
|
|
1209
|
+
eq(a.identifierBindingRevision, input.captured!.identifierBindingRevision),
|
|
1210
|
+
eq(a.deadline, row[mapping.attempt.deadline]),
|
|
1211
|
+
)!
|
|
1212
|
+
: sql`true`;
|
|
1213
|
+
|
|
1459
1214
|
const condition = verified
|
|
1460
|
-
? and(base, sql`${a.deadline} > ${mapping.d1.engineNow}`)!
|
|
1215
|
+
? and(base, attemptSnapshot, sql`${a.deadline} > ${mapping.d1.engineNow}`)!
|
|
1461
1216
|
: base;
|
|
1462
1217
|
|
|
1463
1218
|
const statements: Statement<any>[] = [
|
|
@@ -1473,23 +1228,15 @@ const makePasswordPlans = (
|
|
|
1473
1228
|
),
|
|
1474
1229
|
];
|
|
1475
1230
|
|
|
1231
|
+
let finalAuthority = sql`true`;
|
|
1232
|
+
|
|
1476
1233
|
if (verified && resolved?.snapshot !== undefined) {
|
|
1477
1234
|
const captured = input.captured!;
|
|
1478
|
-
const s = subjectColumns(mapping);
|
|
1479
|
-
const i = identifierColumns(mapping);
|
|
1480
1235
|
const c = credentialColumns(mapping);
|
|
1481
1236
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
identifier: input.captured!.identifier,
|
|
1486
|
-
nativeSubjectId: resolved.nativeSubjectId,
|
|
1487
|
-
}),
|
|
1488
|
-
sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(resolved.nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(input.captured!.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(input.captured!.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(input.captured!.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.captured!.verifier), c.verifier)})`,
|
|
1489
|
-
sql`exists(select 1 from ${mapping.identifier.table} where ${i.namespace} = ${sql.param(input.captured!.identifier.namespace, i.namespace)} and ${i.value} = ${sql.param(input.captured!.identifier.value, i.value)} and ${i.subjectId} = ${sql.param(resolved.nativeSubjectId, i.subjectId)} and ${i.bindingRevision} = ${sql.param(input.captured!.identifierBindingRevision, i.bindingRevision)})`,
|
|
1490
|
-
)!;
|
|
1491
|
-
|
|
1492
|
-
statements.splice(1, 0, yield* assertion(authority, marker));
|
|
1237
|
+
finalAuthority = admittedSnapshotCondition(mapping, resolved, input.moduleId);
|
|
1238
|
+
|
|
1239
|
+
statements.splice(1, 0, yield* assertion(finalAuthority, marker));
|
|
1493
1240
|
if (
|
|
1494
1241
|
input.rehash !== undefined &&
|
|
1495
1242
|
input.rehash.expectedVersion === captured.verifierVersion &&
|
|
@@ -1501,6 +1248,7 @@ const makePasswordPlans = (
|
|
|
1501
1248
|
);
|
|
1502
1249
|
|
|
1503
1250
|
statements.push(
|
|
1251
|
+
yield* assertion(sql`changes() = 1`, marker),
|
|
1504
1252
|
yield* statement(
|
|
1505
1253
|
database
|
|
1506
1254
|
.update(mapping.credential.table)
|
|
@@ -1519,12 +1267,33 @@ const makePasswordPlans = (
|
|
|
1519
1267
|
),
|
|
1520
1268
|
),
|
|
1521
1269
|
);
|
|
1270
|
+
finalAuthority = admittedSnapshotCondition(
|
|
1271
|
+
mapping,
|
|
1272
|
+
{
|
|
1273
|
+
...resolved,
|
|
1274
|
+
snapshot: {
|
|
1275
|
+
...resolved.snapshot,
|
|
1276
|
+
verifierVersion: nextVersion,
|
|
1277
|
+
verifier: input.rehash.nextVerifier,
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
input.moduleId,
|
|
1281
|
+
);
|
|
1522
1282
|
}
|
|
1523
1283
|
}
|
|
1524
1284
|
|
|
1285
|
+
const settled = and(
|
|
1286
|
+
finalAuthority,
|
|
1287
|
+
sql`exists(select 1 from ${mapping.attempt.table} where ${a.moduleId} = ${sql.param(input.moduleId, a.moduleId)} and ${a.attemptId} = ${sql.param(input.attemptId, a.attemptId)} and ${a.state} = ${sql.param(decision, a.state)} and ${attemptSnapshot}${verified ? sql` and ${a.deadline} > ${mapping.d1.engineNow}` : sql``})`,
|
|
1288
|
+
)!;
|
|
1289
|
+
|
|
1290
|
+
// Run after attempt/rehash triggers, before the batch can publish its journal.
|
|
1291
|
+
statements.push(yield* assertion(and(sql`changes() = 1`, settled)!, marker));
|
|
1292
|
+
|
|
1525
1293
|
return {
|
|
1526
1294
|
receipt,
|
|
1527
1295
|
statements,
|
|
1296
|
+
postconditions: [yield* assertion(settled, marker)],
|
|
1528
1297
|
retryable: (cause: unknown) => isGuardFailure(cause, marker),
|
|
1529
1298
|
};
|
|
1530
1299
|
}),
|
|
@@ -1619,10 +1388,14 @@ const makePasswordPlans = (
|
|
|
1619
1388
|
nextSecurityRevision,
|
|
1620
1389
|
});
|
|
1621
1390
|
|
|
1391
|
+
if (compiled === undefined)
|
|
1392
|
+
return { receipt: prepare("rejected", journal), statements: [] };
|
|
1393
|
+
|
|
1622
1394
|
return {
|
|
1623
|
-
receipt: prepare(
|
|
1624
|
-
statements: compiled
|
|
1625
|
-
|
|
1395
|
+
receipt: prepare("changed", journal),
|
|
1396
|
+
statements: compiled.statements,
|
|
1397
|
+
postconditions: [yield* assertion(compiled.appliedCondition, compiled.marker)],
|
|
1398
|
+
retryable: compiled.retryable,
|
|
1626
1399
|
};
|
|
1627
1400
|
}),
|
|
1628
1401
|
);
|
|
@@ -1826,6 +1599,7 @@ const replacementPlan = Effect.fn("Drizzle.replacementPlan")(function* <A>(
|
|
|
1826
1599
|
return {
|
|
1827
1600
|
receipt: passwordReceipt ?? prepare("rejected", journal),
|
|
1828
1601
|
statements: compiled.statements,
|
|
1602
|
+
...(compiled.postconditions === undefined ? {} : { postconditions: compiled.postconditions }),
|
|
1829
1603
|
retryable: (cause: unknown) =>
|
|
1830
1604
|
isGuardFailure(cause, marker) ||
|
|
1831
1605
|
isGuardFailure(
|
|
@@ -1839,12 +1613,12 @@ const replacementPlan = Effect.fn("Drizzle.replacementPlan")(function* <A>(
|
|
|
1839
1613
|
};
|
|
1840
1614
|
}
|
|
1841
1615
|
|
|
1616
|
+
const applied = yield* assertion(protectedMutation.appliedCondition, marker);
|
|
1617
|
+
|
|
1842
1618
|
return {
|
|
1843
1619
|
receipt: prepare("changed", journal),
|
|
1844
|
-
statements: [
|
|
1845
|
-
|
|
1846
|
-
yield* assertion(protectedMutation.appliedCondition, marker),
|
|
1847
|
-
],
|
|
1620
|
+
statements: [...protectedMutation.statements, applied],
|
|
1621
|
+
postconditions: [applied],
|
|
1848
1622
|
retryable: (cause: unknown) => isGuardFailure(cause, marker),
|
|
1849
1623
|
};
|
|
1850
1624
|
});
|
|
@@ -1860,63 +1634,15 @@ const cleanupPlan = Effect.fn("Drizzle.cleanupPlan")(function* <A>(
|
|
|
1860
1634
|
const now = yield* readEngineNowMillis(mapping);
|
|
1861
1635
|
const nativeNow = mapping.encodeInstant(now);
|
|
1862
1636
|
const a = attemptColumns(mapping);
|
|
1863
|
-
const c = chargeColumns(mapping);
|
|
1864
|
-
const sc = scopeColumns(mapping);
|
|
1865
|
-
let remaining = input.limit;
|
|
1866
|
-
let hasMore = false;
|
|
1867
1637
|
|
|
1868
1638
|
const attempts = yield* database
|
|
1869
1639
|
.select({ attemptId: a.attemptId })
|
|
1870
1640
|
.from(mapping.attempt.table)
|
|
1871
1641
|
.where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow)))
|
|
1872
|
-
.limit(
|
|
1873
|
-
|
|
1874
|
-
if (attempts.length > remaining) hasMore = true;
|
|
1875
|
-
const selectedAttempts = attempts.slice(0, remaining);
|
|
1876
|
-
|
|
1877
|
-
remaining -= selectedAttempts.length;
|
|
1878
|
-
|
|
1879
|
-
const charges = yield* database
|
|
1880
|
-
.select({
|
|
1881
|
-
action: c.action,
|
|
1882
|
-
scopeKind: c.scopeKind,
|
|
1883
|
-
scopeKey: c.scopeKey,
|
|
1884
|
-
attemptId: c.attemptId,
|
|
1885
|
-
})
|
|
1886
|
-
.from(mapping.charge.table)
|
|
1887
|
-
.where(and(eq(c.moduleId, input.moduleId), lte(c.retentionUntil, nativeNow)))
|
|
1888
|
-
.limit(remaining + 1);
|
|
1889
|
-
|
|
1890
|
-
if (charges.length > remaining) hasMore = true;
|
|
1891
|
-
const selectedCharges = charges.slice(0, remaining);
|
|
1892
|
-
|
|
1893
|
-
remaining -= selectedCharges.length;
|
|
1894
|
-
|
|
1895
|
-
const scopes = yield* database
|
|
1896
|
-
.select({ action: sc.action, scopeKind: sc.scopeKind, scopeKey: sc.scopeKey })
|
|
1897
|
-
.from(mapping.rateScope.table)
|
|
1898
|
-
.where(
|
|
1899
|
-
and(
|
|
1900
|
-
eq(sc.moduleId, input.moduleId),
|
|
1901
|
-
notExists(
|
|
1902
|
-
database
|
|
1903
|
-
.select({ one: sql`1` })
|
|
1904
|
-
.from(mapping.charge.table)
|
|
1905
|
-
.where(
|
|
1906
|
-
and(
|
|
1907
|
-
eq(c.moduleId, sc.moduleId),
|
|
1908
|
-
eq(c.action, sc.action),
|
|
1909
|
-
eq(c.scopeKind, sc.scopeKind),
|
|
1910
|
-
eq(c.scopeKey, sc.scopeKey),
|
|
1911
|
-
),
|
|
1912
|
-
),
|
|
1913
|
-
),
|
|
1914
|
-
),
|
|
1915
|
-
)
|
|
1916
|
-
.limit(remaining + 1);
|
|
1642
|
+
.limit(input.limit + 1);
|
|
1917
1643
|
|
|
1918
|
-
|
|
1919
|
-
const
|
|
1644
|
+
const selectedAttempts = attempts.slice(0, input.limit);
|
|
1645
|
+
const hasMore = attempts.length > selectedAttempts.length;
|
|
1920
1646
|
const marker = `effect-auth-password-guard:cleanup:${input.moduleId}:${now}`;
|
|
1921
1647
|
const statements: Statement<any>[] = [];
|
|
1922
1648
|
|
|
@@ -1935,58 +1661,11 @@ const cleanupPlan = Effect.fn("Drizzle.cleanupPlan")(function* <A>(
|
|
|
1935
1661
|
yield* statement(database.delete(mapping.attempt.table).where(condition)),
|
|
1936
1662
|
);
|
|
1937
1663
|
}
|
|
1938
|
-
for (const row of selectedCharges) {
|
|
1939
|
-
const condition = and(
|
|
1940
|
-
eq(c.moduleId, input.moduleId),
|
|
1941
|
-
eq(c.action, row.action),
|
|
1942
|
-
eq(c.scopeKind, row.scopeKind),
|
|
1943
|
-
eq(c.scopeKey, row.scopeKey),
|
|
1944
|
-
eq(c.attemptId, row.attemptId),
|
|
1945
|
-
lte(c.retentionUntil, mapping.d1.engineNow),
|
|
1946
|
-
)!;
|
|
1947
|
-
|
|
1948
|
-
statements.push(
|
|
1949
|
-
yield* assertion(
|
|
1950
|
-
sql`exists(select 1 from ${mapping.charge.table} where ${condition})`,
|
|
1951
|
-
marker,
|
|
1952
|
-
),
|
|
1953
|
-
yield* statement(database.delete(mapping.charge.table).where(condition)),
|
|
1954
|
-
);
|
|
1955
|
-
}
|
|
1956
|
-
for (const row of selectedScopes) {
|
|
1957
|
-
const condition = and(
|
|
1958
|
-
eq(sc.moduleId, input.moduleId),
|
|
1959
|
-
eq(sc.action, row.action),
|
|
1960
|
-
eq(sc.scopeKind, row.scopeKind),
|
|
1961
|
-
eq(sc.scopeKey, row.scopeKey),
|
|
1962
|
-
notExists(
|
|
1963
|
-
database
|
|
1964
|
-
.select({ one: sql`1` })
|
|
1965
|
-
.from(mapping.charge.table)
|
|
1966
|
-
.where(
|
|
1967
|
-
and(
|
|
1968
|
-
eq(c.moduleId, sc.moduleId),
|
|
1969
|
-
eq(c.action, sc.action),
|
|
1970
|
-
eq(c.scopeKind, sc.scopeKind),
|
|
1971
|
-
eq(c.scopeKey, sc.scopeKey),
|
|
1972
|
-
),
|
|
1973
|
-
),
|
|
1974
|
-
),
|
|
1975
|
-
)!;
|
|
1976
|
-
|
|
1977
|
-
statements.push(
|
|
1978
|
-
yield* assertion(
|
|
1979
|
-
sql`exists(select 1 from ${mapping.rateScope.table} where ${condition})`,
|
|
1980
|
-
marker,
|
|
1981
|
-
),
|
|
1982
|
-
yield* statement(database.delete(mapping.rateScope.table).where(condition)),
|
|
1983
|
-
);
|
|
1984
|
-
}
|
|
1985
1664
|
|
|
1986
1665
|
return {
|
|
1987
1666
|
receipt: prepare(
|
|
1988
1667
|
{
|
|
1989
|
-
removed: selectedAttempts.length
|
|
1668
|
+
removed: selectedAttempts.length,
|
|
1990
1669
|
hasMore,
|
|
1991
1670
|
},
|
|
1992
1671
|
journal,
|
|
@@ -2002,12 +1681,10 @@ export const makeD1PasswordPersistenceServices = Effect.fnUntraced(function* <
|
|
|
2002
1681
|
C extends AnySQLiteTable,
|
|
2003
1682
|
AC extends AnySQLiteTable,
|
|
2004
1683
|
A extends AnySQLiteTable,
|
|
2005
|
-
RS extends AnySQLiteTable,
|
|
2006
|
-
CE extends AnySQLiteTable,
|
|
2007
1684
|
M extends AnySQLiteTable,
|
|
2008
1685
|
NativeId,
|
|
2009
1686
|
>(
|
|
2010
|
-
mapping: D1PasswordPersistenceMapping<S, I, C, AC, A,
|
|
1687
|
+
mapping: D1PasswordPersistenceMapping<S, I, C, AC, A, M, NativeId>,
|
|
2011
1688
|
proofMapping?: D1ProofPersistenceMapping<
|
|
2012
1689
|
any,
|
|
2013
1690
|
any,
|
|
@@ -2033,9 +1710,14 @@ export const makeD1PasswordPersistenceServices = Effect.fnUntraced(function* <
|
|
|
2033
1710
|
const hooks = yield* LifecycleHooks;
|
|
2034
1711
|
const plans = makePasswordPlans(mapping as unknown as Mapping, proofMapping);
|
|
2035
1712
|
|
|
1713
|
+
const standalone = Effect.gen(function* () {
|
|
1714
|
+
if (yield* hasCommitScope) return yield* unavailable();
|
|
1715
|
+
yield* requireStandalone(unavailable, database.$client.transactionService);
|
|
1716
|
+
});
|
|
1717
|
+
|
|
2036
1718
|
const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
|
|
2037
1719
|
Effect.gen(function* () {
|
|
2038
|
-
|
|
1720
|
+
yield* standalone;
|
|
2039
1721
|
|
|
2040
1722
|
return yield* executeStandalone(plan, 2);
|
|
2041
1723
|
}).pipe(
|
|
@@ -2044,8 +1726,19 @@ export const makeD1PasswordPersistenceServices = Effect.fnUntraced(function* <
|
|
|
2044
1726
|
);
|
|
2045
1727
|
|
|
2046
1728
|
const service: PasswordPersistence["Service"] = {
|
|
2047
|
-
|
|
2048
|
-
|
|
1729
|
+
prepareAttempt: (input) =>
|
|
1730
|
+
standalone.pipe(
|
|
1731
|
+
Effect.andThen(plans.prepareAttempt(input)),
|
|
1732
|
+
Effect.map((planned): PasswordAttemptPreparation =>
|
|
1733
|
+
Object.freeze({
|
|
1734
|
+
...(planned.credential === undefined ? {} : { credential: planned.credential }),
|
|
1735
|
+
admit: <A>(prepare: PreparePasswordCommit<PasswordAttemptAdmission, A>) =>
|
|
1736
|
+
planned.claim.pipe(Effect.andThen(run(planned.admit(prepare))), translateFailure),
|
|
1737
|
+
}),
|
|
1738
|
+
),
|
|
1739
|
+
Effect.provideService(CurrentD1PlanningDatabase, database),
|
|
1740
|
+
translateFailure,
|
|
1741
|
+
),
|
|
2049
1742
|
settleAttempt: (input, prepare) =>
|
|
2050
1743
|
run(plans.settleAttempt(input, prepare)).pipe(translateFailure),
|
|
2051
1744
|
readForSubject: (input) =>
|
|
@@ -2097,8 +1790,6 @@ export function coordinateD1PasswordPersistence<
|
|
|
2097
1790
|
C extends AnySQLiteTable,
|
|
2098
1791
|
AC extends AnySQLiteTable,
|
|
2099
1792
|
At extends AnySQLiteTable,
|
|
2100
|
-
RS extends AnySQLiteTable,
|
|
2101
|
-
CE extends AnySQLiteTable,
|
|
2102
1793
|
M extends AnySQLiteTable,
|
|
2103
1794
|
NativeId,
|
|
2104
1795
|
A,
|
|
@@ -2109,7 +1800,7 @@ export function coordinateD1PasswordPersistence<
|
|
|
2109
1800
|
>(
|
|
2110
1801
|
acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
|
|
2111
1802
|
options: {
|
|
2112
|
-
readonly mapping: D1PasswordPersistenceMapping<S, I, C, AC, At,
|
|
1803
|
+
readonly mapping: D1PasswordPersistenceMapping<S, I, C, AC, At, M, NativeId>;
|
|
2113
1804
|
readonly proofMapping?:
|
|
2114
1805
|
| D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>
|
|
2115
1806
|
| undefined;
|
|
@@ -2173,8 +1864,27 @@ export function coordinateD1PasswordPersistence<
|
|
|
2173
1864
|
);
|
|
2174
1865
|
|
|
2175
1866
|
const service: PasswordPersistence["Service"] = {
|
|
2176
|
-
|
|
2177
|
-
owner.run(
|
|
1867
|
+
prepareAttempt: (input) =>
|
|
1868
|
+
owner.run(
|
|
1869
|
+
plans.prepareAttempt(input).pipe(
|
|
1870
|
+
Effect.map((planned): PasswordAttemptPreparation =>
|
|
1871
|
+
Object.freeze({
|
|
1872
|
+
...(planned.credential === undefined
|
|
1873
|
+
? {}
|
|
1874
|
+
: { credential: planned.credential }),
|
|
1875
|
+
admit: <A>(prepare: PreparePasswordCommit<PasswordAttemptAdmission, A>) =>
|
|
1876
|
+
owner.run(
|
|
1877
|
+
planned.claim.pipe(
|
|
1878
|
+
Effect.andThen(run(planned.admit(prepare))),
|
|
1879
|
+
translateFailure,
|
|
1880
|
+
),
|
|
1881
|
+
),
|
|
1882
|
+
}),
|
|
1883
|
+
),
|
|
1884
|
+
Effect.provideService(CurrentD1PlanningDatabase, database),
|
|
1885
|
+
translateFailure,
|
|
1886
|
+
),
|
|
1887
|
+
),
|
|
2178
1888
|
settleAttempt: (input, prepare) =>
|
|
2179
1889
|
owner.run(run(plans.settleAttempt(input, prepare)).pipe(translateFailure)),
|
|
2180
1890
|
readForSubject: (input) =>
|
|
@@ -2229,16 +1939,19 @@ export function coordinateD1PasswordPersistence<
|
|
|
2229
1939
|
if (status._tag === "Success" || status.failure._tag !== "CommitPending")
|
|
2230
1940
|
return yield* unavailable();
|
|
2231
1941
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
1942
|
+
// Re-assert the password's final state after application statements.
|
|
1943
|
+
yield* database.$client
|
|
1944
|
+
.batch([...statements, ...(mutation?.postconditions ?? [])])
|
|
1945
|
+
.pipe(
|
|
1946
|
+
Effect.catchCause((cause) =>
|
|
1947
|
+
Effect.failCause(
|
|
1948
|
+
Cause.map(cause, (error) =>
|
|
1949
|
+
everyFailureMatches(cause, mutation?.retryable) ? unavailable() : error,
|
|
1950
|
+
),
|
|
2237
1951
|
),
|
|
2238
1952
|
),
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
);
|
|
1953
|
+
translateFailure,
|
|
1954
|
+
);
|
|
2242
1955
|
|
|
2243
1956
|
return value;
|
|
2244
1957
|
}),
|