@yielded/auth-persistence 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/Adapter.d.mts +4 -2
- package/dist/Adapter.mjs +3 -1
- package/dist/internal/authority-snapshot.mjs +53 -0
- package/dist/internal/authority-snapshot.mjs.map +1 -0
- package/dist/internal/configuration.d.mts +1 -1
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/models/password-model.d.mts +3 -53
- package/dist/internal/models/password-model.mjs +0 -2
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +30 -0
- package/dist/internal/native-sql-table.mjs +104 -0
- package/dist/internal/native-sql-table.mjs.map +1 -0
- package/dist/internal/passkey/target.d.mts +1 -1
- package/dist/internal/passkeys.mjs +1 -1
- package/dist/internal/password-attempts.d.mts +12 -0
- package/dist/internal/password-attempts.mjs +197 -0
- package/dist/internal/password-attempts.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +15 -12
- package/dist/internal/password-kernel.mjs +39 -202
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +2 -0
- package/dist/internal/persistence.mjs +4 -4
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/session-kernel.d.mts +3 -1
- package/dist/internal/session-kernel.mjs +22 -12
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-persistence.mjs +3 -1
- package/dist/internal/sql-persistence.mjs.map +1 -1
- package/dist/internal/storage-mapping.mjs +1 -30
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-tables.d.mts +0 -43
- package/dist/internal/storage-tables.mjs +0 -26
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/transaction-execution-kernel.d.mts +1 -1
- package/dist/internal/transaction-kernel.d.mts +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +9 -4
- package/src/internal/authority-snapshot.ts +91 -0
- package/src/internal/configuration.ts +1 -6
- package/src/internal/models/password-model.ts +0 -64
- package/src/internal/native-sql-table.ts +164 -0
- package/src/internal/password-attempts.ts +466 -0
- package/src/internal/password-kernel.ts +102 -623
- package/src/internal/persistence.ts +5 -10
- package/src/internal/session-kernel.ts +89 -27
- package/src/internal/sql-persistence.ts +2 -0
- package/src/internal/storage-mapping.ts +0 -29
- package/src/internal/storage-tables.ts +0 -15
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
2
|
+
import { makeNativeSqlTables, sqlMapping } from "./native-sql-table.mjs";
|
|
3
|
+
import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
4
|
+
import { CurrentPasswordPreparedTransaction, PasswordPreparedPostconditions } from "./PasswordPreparedPostconditions.mjs";
|
|
2
5
|
import { CurrentProofSql } from "./proof-kernel.mjs";
|
|
3
6
|
import { requiredPasswordConstraints } from "./models/password-model.mjs";
|
|
7
|
+
import { makePasswordAttempts, samePasswordCredentialSnapshot } from "./password-attempts.mjs";
|
|
4
8
|
import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
|
|
5
9
|
import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
|
|
6
10
|
import { snapshotAuthenticationEvidence } from "@yielded/auth/Sessions";
|
|
@@ -9,8 +13,8 @@ import { ProofUnavailable } from "@yielded/auth/Proofs";
|
|
|
9
13
|
import { PasswordPersistence, PasswordUnavailable, snapshotPasswordCredential, snapshotPasswordRequirement, snapshotPasswordRevision } from "@yielded/auth/Password";
|
|
10
14
|
//#region src/internal/password-kernel.ts
|
|
11
15
|
var CurrentPasswordSql = class extends Context.Service()("effect-auth/CurrentPasswordSql") {};
|
|
12
|
-
const makePasswordKernel = (operations, proofs) => {
|
|
13
|
-
const { and, eq,
|
|
16
|
+
const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeNativeSqlTables(client)) => {
|
|
17
|
+
const { and, eq, inArray, lte, column, updateValues } = operations;
|
|
14
18
|
const { completeProofPlanIn } = proofs;
|
|
15
19
|
const unavailable = () => PasswordUnavailable.make({});
|
|
16
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()))));
|
|
@@ -60,21 +64,6 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
60
64
|
retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
|
|
61
65
|
state: column(mapping.attempt.table, mapping.attempt.state)
|
|
62
66
|
});
|
|
63
|
-
const scopeColumns = (mapping) => ({
|
|
64
|
-
moduleId: column(mapping.rateScope.table, mapping.rateScope.moduleId),
|
|
65
|
-
action: column(mapping.rateScope.table, mapping.rateScope.action),
|
|
66
|
-
scopeKind: column(mapping.rateScope.table, mapping.rateScope.scopeKind),
|
|
67
|
-
scopeKey: column(mapping.rateScope.table, mapping.rateScope.scopeKey)
|
|
68
|
-
});
|
|
69
|
-
const chargeColumns = (mapping) => ({
|
|
70
|
-
moduleId: column(mapping.charge.table, mapping.charge.moduleId),
|
|
71
|
-
action: column(mapping.charge.table, mapping.charge.action),
|
|
72
|
-
scopeKind: column(mapping.charge.table, mapping.charge.scopeKind),
|
|
73
|
-
scopeKey: column(mapping.charge.table, mapping.charge.scopeKey),
|
|
74
|
-
attemptId: column(mapping.charge.table, mapping.charge.attemptId),
|
|
75
|
-
occurredAt: column(mapping.charge.table, mapping.charge.occurredAt),
|
|
76
|
-
retentionUntil: column(mapping.charge.table, mapping.charge.retentionUntil)
|
|
77
|
-
});
|
|
78
67
|
const commandColumns = (mapping) => ({
|
|
79
68
|
moduleId: column(mapping.command.table, mapping.command.moduleId),
|
|
80
69
|
commandId: column(mapping.command.table, mapping.command.commandId),
|
|
@@ -106,7 +95,6 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
106
95
|
return allocate(mode, asyncValue ?? mapping.allocateRevision, syncValue ?? mapping.allocateRevisionSync);
|
|
107
96
|
};
|
|
108
97
|
const sameIdentifier = (left, right) => left.namespace === right.namespace && left.value === right.value;
|
|
109
|
-
const sameCredentialSnapshot = (left, right) => left.moduleId === right.moduleId && left.revision.subjectId === right.revision.subjectId && left.revision.securityRevision === right.revision.securityRevision && left.credentialId === right.credentialId && left.credentialRevision === right.credentialRevision && left.verifierVersion === right.verifierVersion && left.normalization === right.normalization && left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis && left.revision.credentials.length === right.revision.credentials.length && left.revision.credentials.every((entry) => right.revision.credentials.some((other) => other.credentialId === entry.credentialId && other.revision === entry.revision)) && left.identifierBindingRevision === right.identifierBindingRevision && sameIdentifier(left.identifier, right.identifier) && Redacted.value(left.verifier) === Redacted.value(right.verifier);
|
|
110
98
|
const evidenceSatisfiedAt = (evidence, requirement, now) => {
|
|
111
99
|
if (evidence.proofs.length > 64 || evidence.revision.credentials.length > 64) return false;
|
|
112
100
|
const revisions = /* @__PURE__ */ new Map();
|
|
@@ -197,54 +185,6 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
197
185
|
}).pipe(Effect.flatMap(snapshotPasswordCredential))
|
|
198
186
|
};
|
|
199
187
|
});
|
|
200
|
-
const scopeEntries = (keys, policy) => {
|
|
201
|
-
const values = [{
|
|
202
|
-
kind: "action",
|
|
203
|
-
key: keys.action,
|
|
204
|
-
...policy.action
|
|
205
|
-
}, {
|
|
206
|
-
kind: "identifier",
|
|
207
|
-
key: keys.identifier,
|
|
208
|
-
...policy.identifier
|
|
209
|
-
}];
|
|
210
|
-
if (keys.subject !== void 0) values.push({
|
|
211
|
-
kind: "subject",
|
|
212
|
-
key: keys.subject,
|
|
213
|
-
...policy.subject
|
|
214
|
-
});
|
|
215
|
-
return values;
|
|
216
|
-
};
|
|
217
|
-
const lockScope = Effect.fn("DrizzlePassword.lockScope")(function* (mapping, configuration, moduleId, action, entry) {
|
|
218
|
-
const database = yield* CurrentPasswordSql;
|
|
219
|
-
const query = database.insert(mapping.rateScope.table).values(mapping.rateScope.encodeInsert({
|
|
220
|
-
moduleId,
|
|
221
|
-
action,
|
|
222
|
-
scopeKind: entry.kind,
|
|
223
|
-
scopeKey: entry.key
|
|
224
|
-
}));
|
|
225
|
-
yield* configuration.insertIfAbsent(query, mapping.rateScope.scopeKey, entry.key);
|
|
226
|
-
const c = scopeColumns(mapping);
|
|
227
|
-
yield* selectRows(database.select().from(mapping.rateScope.table).where(and(eq(c.moduleId, moduleId), eq(c.action, action), eq(c.scopeKind, entry.kind), eq(c.scopeKey, entry.key))).limit(1), configuration.locking);
|
|
228
|
-
});
|
|
229
|
-
const scopeAdmits = Effect.fn("DrizzlePassword.scopeAdmits")(function* (mapping, moduleId, action, entry, policy, identifier, nativeSubjectId, now) {
|
|
230
|
-
const database = yield* CurrentPasswordSql;
|
|
231
|
-
const c = chargeColumns(mapping);
|
|
232
|
-
if ((yield* selectRows(database.select({ attemptId: c.attemptId }).from(mapping.charge.table).where(and(eq(c.moduleId, moduleId), eq(c.action, action), eq(c.scopeKind, entry.kind), eq(c.scopeKey, entry.key), gte(c.occurredAt, mapping.encodeInstant(now - entry.windowMillis)))).limit(entry.limit), true)).length >= entry.limit) return false;
|
|
233
|
-
const a = attemptColumns(mapping);
|
|
234
|
-
const pendingWhere = and(entry.kind === "action" ? and(eq(a.moduleId, moduleId), eq(a.action, action)) : entry.kind === "identifier" ? and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.identifierNamespace, identifier.namespace), eq(a.identifierValue, identifier.value)) : and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.subjectId, nativeSubjectId)), eq(a.state, "pending"), gt(a.retentionUntil, mapping.encodeInstant(now)));
|
|
235
|
-
return (yield* selectRows(database.select({ attemptId: a.attemptId }).from(mapping.attempt.table).where(pendingWhere).limit(policy.maximumPending), true)).length < policy.maximumPending;
|
|
236
|
-
});
|
|
237
|
-
const insertCharge = Effect.fn("Drizzle.insertCharge")(function* (mapping, moduleId, action, entry, attemptId, now) {
|
|
238
|
-
return yield* (yield* CurrentPasswordSql).insert(mapping.charge.table).values(mapping.charge.encodeInsert({
|
|
239
|
-
moduleId,
|
|
240
|
-
action,
|
|
241
|
-
scopeKind: entry.kind,
|
|
242
|
-
scopeKey: entry.key,
|
|
243
|
-
attemptId,
|
|
244
|
-
occurredAtMillis: now,
|
|
245
|
-
retentionUntilMillis: now + entry.windowMillis
|
|
246
|
-
}));
|
|
247
|
-
});
|
|
248
188
|
const owned = (database, mapping, configuration, body) => {
|
|
249
189
|
if (!validConstraints(mapping)) return Effect.fail(unavailable());
|
|
250
190
|
const run = coordinateCommit(() => database.transaction((transaction) => body.pipe(Effect.provideService(CurrentPasswordSql, transaction), Effect.provideService(CurrentProofSql, transaction))), { mode: configuration.mode }).pipe(Effect.map((result) => result.value));
|
|
@@ -254,6 +194,15 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
254
194
|
return yield* run;
|
|
255
195
|
}) : run;
|
|
256
196
|
};
|
|
197
|
+
const registerFinal = Effect.fnUntraced(function* (check) {
|
|
198
|
+
const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);
|
|
199
|
+
if (Option.isNone(postconditions)) return;
|
|
200
|
+
const postcondition = Effect.gen(function* () {
|
|
201
|
+
const transaction = yield* CurrentPasswordPreparedTransaction;
|
|
202
|
+
if (!(yield* check.pipe(Effect.provideService(CurrentPasswordSql, transaction)))) return yield* unavailable();
|
|
203
|
+
}).pipe(translateFailure);
|
|
204
|
+
if (!postconditions.value.register(postcondition)) return yield* unavailable();
|
|
205
|
+
});
|
|
257
206
|
const safeRead = (database, configuration, body) => Effect.gen(function* () {
|
|
258
207
|
if (configuration.coordinated !== true) {
|
|
259
208
|
if (yield* hasCommitScope) return yield* unavailable();
|
|
@@ -317,7 +266,7 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
317
266
|
identifier,
|
|
318
267
|
credential
|
|
319
268
|
});
|
|
320
|
-
if (!
|
|
269
|
+
if (!samePasswordCredentialSnapshot(snapshot, input.credential)) return void 0;
|
|
321
270
|
}
|
|
322
271
|
const currentRequirement = yield* mapping.subject.decodeActionRequirement(row, action);
|
|
323
272
|
const now = yield* nowMillis;
|
|
@@ -383,10 +332,9 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
383
332
|
}));
|
|
384
333
|
yield* database.update(mapping.subject.table).set(updateValues([[mapping.subject.securityRevision, nextSecurityRevision]])).where(and(eq(subjectColumns(mapping).id, authority.nativeSubjectId), eq(subjectColumns(mapping).securityRevision, input.expectedRevision.securityRevision)));
|
|
385
334
|
yield* insertCommand(mapping, input, "add-password", commandNow ?? authority.now);
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (inserted === void 0 || inserted[mapping.credential.credentialId] !== credentialId || inserted[mapping.credential.credentialRevision] !== credentialRevision || inserted[mapping.credential.verifierVersion] !== verifierVersion || inserted[mapping.credential.verifier] !== Redacted.value(input.replacement.verifier) || inserted[mapping.credential.normalization] !== input.replacement.normalization || authorityInserted === void 0 || subjectUpdated === void 0 || !(yield* mutationPostconditions(mapping, input, authority.nativeSubjectId, subjectUpdated, authorityInserted, commandNow ?? authority.now))) return yield* unavailable();
|
|
335
|
+
const applied = checkMutationApplied(mapping, input, authority.nativeSubjectId, revisions, commandNow ?? authority.now);
|
|
336
|
+
if (!(yield* applied)) return yield* unavailable();
|
|
337
|
+
yield* registerFinal(applied);
|
|
390
338
|
return true;
|
|
391
339
|
});
|
|
392
340
|
const makeSqlPasswordPersistence = Effect.fn("makeSqlPasswordPersistence")(function* (mapping, configuration) {
|
|
@@ -396,96 +344,11 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
396
344
|
proof: configuration.proof?.mapping
|
|
397
345
|
}) ?? Effect.void).pipe(Effect.mapError(unavailable));
|
|
398
346
|
const hooks = yield* LifecycleHooks;
|
|
347
|
+
if (!validConstraints(mapping)) return yield* unavailable();
|
|
348
|
+
const root = yield* NativeDatabase;
|
|
349
|
+
const attempts = yield* makePasswordAttempts(yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(translateFailure), mapping, configuration);
|
|
399
350
|
return PasswordPersistence.of({
|
|
400
|
-
|
|
401
|
-
const attemptId = yield* allocate(configuration.mode, mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
|
|
402
|
-
return yield* owned(database, mapping, configuration, Effect.gen(function* () {
|
|
403
|
-
const transaction = yield* CurrentPasswordSql;
|
|
404
|
-
const journal = yield* CurrentCommitJournal;
|
|
405
|
-
const resolved = yield* resolveCredential(mapping, input.moduleId, input.identifier, input.subjectId, configuration.locking);
|
|
406
|
-
const keys = mapping.scopeKeys({
|
|
407
|
-
moduleId: input.moduleId,
|
|
408
|
-
action: input.action,
|
|
409
|
-
identifier: input.identifier,
|
|
410
|
-
...resolved?.snapshot === void 0 ? {} : { subjectId: resolved.snapshot.revision.subjectId }
|
|
411
|
-
});
|
|
412
|
-
const scopes = scopeEntries(keys, input.policy);
|
|
413
|
-
const admitted = [];
|
|
414
|
-
yield* lockScope(mapping, configuration, input.moduleId, input.action, scopes[0]);
|
|
415
|
-
let currentNow = yield* nowMillis;
|
|
416
|
-
if (yield* scopeAdmits(mapping, input.moduleId, input.action, scopes[0], input.policy, input.identifier, resolved?.nativeSubjectId, currentNow)) admitted.push(scopes[0]);
|
|
417
|
-
if (admitted.length === 1) for (const entry of scopes.slice(1)) {
|
|
418
|
-
yield* lockScope(mapping, configuration, input.moduleId, input.action, entry);
|
|
419
|
-
currentNow = yield* nowMillis;
|
|
420
|
-
if (yield* scopeAdmits(mapping, input.moduleId, input.action, entry, input.policy, input.identifier, resolved?.nativeSubjectId, currentNow)) admitted.push(entry);
|
|
421
|
-
}
|
|
422
|
-
const now = yield* nowMillis;
|
|
423
|
-
for (const entry of admitted) yield* insertCharge(mapping, input.moduleId, input.action, entry, attemptId, now);
|
|
424
|
-
if (admitted.length !== scopes.length) return prepare({ _tag: "Denied" }, journal);
|
|
425
|
-
const snapshot = resolved?.snapshot;
|
|
426
|
-
const deadline = now + input.policy.attemptLifetimeMillis;
|
|
427
|
-
const retentionUntil = Math.max(deadline, ...scopes.map((entry) => now + entry.windowMillis));
|
|
428
|
-
yield* transaction.insert(mapping.attempt.table).values(mapping.attempt.encodeInsert({
|
|
429
|
-
moduleId: input.moduleId,
|
|
430
|
-
action: input.action,
|
|
431
|
-
attemptId,
|
|
432
|
-
identifier: input.identifier,
|
|
433
|
-
...snapshot === void 0 ? {} : {
|
|
434
|
-
subjectId: snapshot.revision.subjectId,
|
|
435
|
-
credentialId: snapshot.credentialId,
|
|
436
|
-
securityRevision: snapshot.revision.securityRevision,
|
|
437
|
-
credentialRevision: snapshot.credentialRevision,
|
|
438
|
-
verifierVersion: snapshot.verifierVersion,
|
|
439
|
-
identifierBindingRevision: snapshot.identifierBindingRevision
|
|
440
|
-
},
|
|
441
|
-
admittedAtMillis: now,
|
|
442
|
-
deadlineMillis: deadline,
|
|
443
|
-
retentionUntilMillis: retentionUntil
|
|
444
|
-
}, {
|
|
445
|
-
...resolved?.nativeSubjectId === void 0 ? {} : { nativeSubjectId: resolved.nativeSubjectId },
|
|
446
|
-
state: "pending"
|
|
447
|
-
}));
|
|
448
|
-
return prepare({
|
|
449
|
-
_tag: "Admitted",
|
|
450
|
-
attemptId,
|
|
451
|
-
...snapshot === void 0 ? {} : { credential: snapshot }
|
|
452
|
-
}, journal);
|
|
453
|
-
}));
|
|
454
|
-
}).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
|
|
455
|
-
settleAttempt: (uncaptured, prepare) => Effect.gen(function* () {
|
|
456
|
-
const captured = uncaptured.captured === void 0 ? void 0 : yield* snapshotPasswordCredential(uncaptured.captured);
|
|
457
|
-
const input = {
|
|
458
|
-
...uncaptured,
|
|
459
|
-
...captured === void 0 ? {} : { captured }
|
|
460
|
-
};
|
|
461
|
-
return yield* owned(database, mapping, configuration, Effect.gen(function* () {
|
|
462
|
-
const transaction = yield* CurrentPasswordSql;
|
|
463
|
-
const journal = yield* CurrentCommitJournal;
|
|
464
|
-
const c = attemptColumns(mapping);
|
|
465
|
-
let row = (yield* selectRows(transaction.select().from(mapping.attempt.table).where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId))).limit(1), false))[0];
|
|
466
|
-
if (row === void 0 || row[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
|
|
467
|
-
let decision = "rejected";
|
|
468
|
-
let current;
|
|
469
|
-
let attemptMatches = false;
|
|
470
|
-
if (input.outcome === "verified" && input.captured !== void 0) {
|
|
471
|
-
current = yield* resolveCredential(mapping, input.moduleId, input.captured.identifier, input.captured.revision.subjectId, configuration.locking);
|
|
472
|
-
attemptMatches = input.captured.moduleId === input.moduleId && row[mapping.attempt.identifierNamespace] === input.captured.identifier.namespace && row[mapping.attempt.identifierValue] === input.captured.identifier.value && mapping.subjectId.equals(row[mapping.attempt.subjectId], current?.nativeSubjectId) && row[mapping.attempt.credentialId] === input.captured.credentialId && row[mapping.attempt.securityRevision] === input.captured.revision.securityRevision && row[mapping.attempt.credentialRevision] === input.captured.credentialRevision && row[mapping.attempt.verifierVersion] === input.captured.verifierVersion && row[mapping.attempt.identifierBindingRevision] === input.captured.identifierBindingRevision;
|
|
473
|
-
}
|
|
474
|
-
row = (yield* selectRows(transaction.select().from(mapping.attempt.table).where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId))).limit(1), configuration.locking))[0];
|
|
475
|
-
if (row === void 0 || row[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
|
|
476
|
-
const now = yield* nowMillis;
|
|
477
|
-
const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
|
|
478
|
-
if (attemptMatches && current?.snapshot !== void 0 && input.captured !== void 0 && deadline > now && sameCredentialSnapshot(current.snapshot, input.captured)) decision = "verified";
|
|
479
|
-
const prepared = prepare(decision, journal);
|
|
480
|
-
yield* transaction.update(mapping.attempt.table).set(updateValues([[mapping.attempt.state, decision]])).where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId), eq(c.state, "pending")));
|
|
481
|
-
if (decision === "verified" && input.rehash !== void 0 && current?.snapshot !== void 0) {
|
|
482
|
-
const cc = credentialColumns(mapping);
|
|
483
|
-
const nextVersion = yield* allocate(configuration.mode, mapping.allocateRevision, mapping.allocateRevisionSync);
|
|
484
|
-
yield* transaction.update(mapping.credential.table).set(mapping.credential.encodeVerifier(input.rehash.nextVerifier, nextVersion)).where(and(eq(cc.moduleId, input.moduleId), eq(cc.subjectId, current.nativeSubjectId), eq(cc.credentialId, current.snapshot.credentialId), eq(cc.credentialRevision, current.snapshot.credentialRevision), eq(cc.verifierVersion, input.rehash.expectedVersion), eq(cc.verifier, Redacted.value(input.rehash.expectedVerifier))));
|
|
485
|
-
}
|
|
486
|
-
return prepared;
|
|
487
|
-
}));
|
|
488
|
-
}).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
|
|
351
|
+
...attempts,
|
|
489
352
|
readForSubject: (input) => safeRead(database, configuration, Effect.gen(function* () {
|
|
490
353
|
const transaction = yield* CurrentPasswordSql;
|
|
491
354
|
const locked = yield* lockSubject(mapping, input.subjectId, false);
|
|
@@ -565,11 +428,14 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
565
428
|
if (!(yield* currentExpectedCredential(mapping, input, authority.nativeSubjectId))) return prepare("rejected", journal);
|
|
566
429
|
let passwordReceipt;
|
|
567
430
|
yield* completeProofPlanIn(configuration.proof.mapping, configuration.proof.configuration, input.completion, Effect.gen(function* () {
|
|
568
|
-
|
|
431
|
+
const revisions = {
|
|
569
432
|
credentialRevision,
|
|
570
433
|
verifierVersion,
|
|
571
434
|
nextSecurityRevision
|
|
572
|
-
}
|
|
435
|
+
};
|
|
436
|
+
const applied = yield* writeReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now);
|
|
437
|
+
if (applied) yield* registerReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now);
|
|
438
|
+
return applied;
|
|
573
439
|
}), (decision) => {
|
|
574
440
|
passwordReceipt = prepare(decision === "completed" ? "changed" : "rejected", journal);
|
|
575
441
|
return decision;
|
|
@@ -582,54 +448,20 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
582
448
|
const journal = yield* CurrentCommitJournal;
|
|
583
449
|
const now = yield* nowMillis;
|
|
584
450
|
const nativeNow = mapping.encodeInstant(now);
|
|
585
|
-
let remaining = input.limit;
|
|
586
|
-
let hasMore = false;
|
|
587
451
|
const a = attemptColumns(mapping);
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
const attemptCandidates = attempts.slice(0, remaining);
|
|
592
|
-
remaining -= attemptCandidates.length;
|
|
593
|
-
const charges = yield* selectRows(transaction.select({
|
|
594
|
-
action: c.action,
|
|
595
|
-
scopeKind: c.scopeKind,
|
|
596
|
-
scopeKey: c.scopeKey,
|
|
597
|
-
attemptId: c.attemptId
|
|
598
|
-
}).from(mapping.charge.table).where(and(eq(c.moduleId, input.moduleId), lte(c.retentionUntil, nativeNow))).limit(remaining + 1), false);
|
|
599
|
-
if (charges.length > remaining) hasMore = true;
|
|
600
|
-
const chargeCandidates = charges.slice(0, remaining);
|
|
601
|
-
remaining -= chargeCandidates.length;
|
|
602
|
-
const sc = scopeColumns(mapping);
|
|
603
|
-
const scopes = yield* selectRows(transaction.select({
|
|
604
|
-
action: sc.action,
|
|
605
|
-
scopeKind: sc.scopeKind,
|
|
606
|
-
scopeKey: sc.scopeKey
|
|
607
|
-
}).from(mapping.rateScope.table).where(and(eq(sc.moduleId, input.moduleId), notExists(transaction.select({ one: sql`1` }).from(mapping.charge.table).where(and(eq(c.moduleId, sc.moduleId), eq(c.action, sc.action), eq(c.scopeKind, sc.scopeKind), eq(c.scopeKey, sc.scopeKey)))))).limit(remaining + 1), remaining === 0 ? false : configuration.locking);
|
|
608
|
-
if (scopes.length > remaining) hasMore = true;
|
|
609
|
-
const selectedScopes = scopes.slice(0, remaining);
|
|
610
|
-
remaining -= selectedScopes.length;
|
|
611
|
-
const selectedCharges = [];
|
|
612
|
-
for (const candidate of chargeCandidates) {
|
|
613
|
-
const locked = yield* selectRows(transaction.select({
|
|
614
|
-
action: c.action,
|
|
615
|
-
scopeKind: c.scopeKind,
|
|
616
|
-
scopeKey: c.scopeKey,
|
|
617
|
-
attemptId: c.attemptId
|
|
618
|
-
}).from(mapping.charge.table).where(and(eq(c.moduleId, input.moduleId), eq(c.action, candidate.action), eq(c.scopeKind, candidate.scopeKind), eq(c.scopeKey, candidate.scopeKey), eq(c.attemptId, candidate.attemptId), lte(c.retentionUntil, nativeNow))).limit(1), configuration.locking);
|
|
619
|
-
if (locked[0] !== void 0) selectedCharges.push(locked[0]);
|
|
620
|
-
}
|
|
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);
|
|
621
455
|
const selectedAttempts = [];
|
|
622
456
|
for (const candidate of attemptCandidates) {
|
|
623
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);
|
|
624
458
|
if (locked[0] !== void 0) selectedAttempts.push(locked[0]);
|
|
625
459
|
}
|
|
626
460
|
const prepared = prepare({
|
|
627
|
-
removed: selectedAttempts.length
|
|
461
|
+
removed: selectedAttempts.length,
|
|
628
462
|
hasMore
|
|
629
463
|
}, journal);
|
|
630
|
-
for (const row of selectedCharges) yield* transaction.delete(mapping.charge.table).where(and(eq(c.moduleId, input.moduleId), eq(c.action, row.action), eq(c.scopeKind, row.scopeKind), eq(c.scopeKey, row.scopeKey), eq(c.attemptId, row.attemptId), lte(c.retentionUntil, nativeNow)));
|
|
631
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)));
|
|
632
|
-
for (const row of selectedScopes) yield* transaction.delete(mapping.rateScope.table).where(and(eq(sc.moduleId, input.moduleId), eq(sc.action, row.action), eq(sc.scopeKind, row.scopeKind), eq(sc.scopeKey, row.scopeKey), notExists(transaction.select({ one: sql`1` }).from(mapping.charge.table).where(and(eq(c.moduleId, sc.moduleId), eq(c.action, sc.action), eq(c.scopeKind, sc.scopeKind), eq(c.scopeKey, sc.scopeKey))))));
|
|
633
465
|
return prepared;
|
|
634
466
|
})).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure)
|
|
635
467
|
});
|
|
@@ -646,7 +478,7 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
646
478
|
const credential = (yield* database.select().from(mapping.credential.table).where(and(eq(c.moduleId, input.moduleId), eq(c.subjectId, nativeSubjectId), eq(c.credentialId, revisions.credentialId), eq(c.credentialRevision, revisions.credentialRevision), eq(c.verifierVersion, revisions.verifierVersion), eq(c.verifier, Redacted.value(input.replacement.verifier)), eq(c.normalization, input.replacement.normalization))).limit(1))[0];
|
|
647
479
|
const subject = (yield* database.select().from(mapping.subject.table).where(and(eq(s.id, nativeSubjectId), eq(s.securityRevision, revisions.nextSecurityRevision))).limit(1))[0];
|
|
648
480
|
const authorityCredential = (yield* database.select().from(mapping.authorityCredential.table).where(and(eq(authorityCredentialColumns(mapping).subjectId, nativeSubjectId), eq(authorityCredentialColumns(mapping).credentialId, revisions.credentialId), eq(authorityCredentialColumns(mapping).revision, revisions.credentialRevision))).limit(1))[0];
|
|
649
|
-
return credential !== void 0 && (yield* mutationPostconditions(mapping, input, nativeSubjectId, subject, authorityCredential, now));
|
|
481
|
+
return credential !== void 0 && credential[mapping.credential.credentialId] === revisions.credentialId && credential[mapping.credential.credentialRevision] === revisions.credentialRevision && credential[mapping.credential.verifierVersion] === revisions.verifierVersion && credential[mapping.credential.verifier] === Redacted.value(input.replacement.verifier) && credential[mapping.credential.normalization] === input.replacement.normalization && (yield* mutationPostconditions(mapping, input, nativeSubjectId, subject, authorityCredential, now));
|
|
650
482
|
});
|
|
651
483
|
const writeReplacement = Effect.fn("DrizzlePassword.writeReplacement")(function* (mapping, input, nativeSubjectId, revisions, now) {
|
|
652
484
|
const database = yield* CurrentPasswordSql;
|
|
@@ -665,6 +497,10 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
665
497
|
credentialId: input.credential.credentialId
|
|
666
498
|
}, now);
|
|
667
499
|
});
|
|
500
|
+
const registerReplacement = (mapping, input, nativeSubjectId, revisions, now) => registerFinal(checkMutationApplied(mapping, input, nativeSubjectId, {
|
|
501
|
+
...revisions,
|
|
502
|
+
credentialId: input.credential.credentialId
|
|
503
|
+
}, now));
|
|
668
504
|
const replaceIn = Effect.fn("Drizzle.replaceIn")(function* (mapping, configuration, input, action, revisions, prepare) {
|
|
669
505
|
const journal = yield* CurrentCommitJournal;
|
|
670
506
|
const authority = yield* validateMutationAuthority(mapping, configuration, input, action);
|
|
@@ -673,6 +509,7 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
673
509
|
if (!(yield* currentExpectedCredential(mapping, input, authority.nativeSubjectId))) return prepare("rejected", journal);
|
|
674
510
|
const prepared = prepare("changed", journal);
|
|
675
511
|
if (!(yield* writeReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now))) return yield* unavailable();
|
|
512
|
+
yield* registerReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now);
|
|
676
513
|
return prepared;
|
|
677
514
|
});
|
|
678
515
|
return {
|
|
@@ -694,7 +531,7 @@ const makePasswordKernel = (operations, proofs) => {
|
|
|
694
531
|
checkMutationApplied,
|
|
695
532
|
writeReplacement,
|
|
696
533
|
evidenceSatisfiedAt,
|
|
697
|
-
sameCredentialSnapshot,
|
|
534
|
+
sameCredentialSnapshot: samePasswordCredentialSnapshot,
|
|
698
535
|
subjectColumns,
|
|
699
536
|
identifierColumns,
|
|
700
537
|
credentialColumns,
|