@yielded/auth-persistence 0.1.0-beta.22 → 0.1.0-beta.24
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 -0
- package/dist/Adapter.d.mts +13 -5
- package/dist/Adapter.mjs +9 -2
- package/dist/OAuthPersistence.d.mts +87 -0
- package/dist/OAuthPersistence.mjs +125 -0
- package/dist/OAuthPersistence.mjs.map +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.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/oauth-connected-model.d.mts +294 -0
- package/dist/internal/models/oauth-connected-model.mjs +22 -0
- package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
- package/dist/internal/models/oauth-model.d.mts +467 -0
- package/dist/internal/models/oauth-model.mjs +49 -0
- package/dist/internal/models/oauth-model.mjs.map +1 -0
- package/dist/internal/models/password-model.d.mts +4 -97
- package/dist/internal/models/password-model.mjs +0 -3
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +34 -0
- package/dist/internal/native-sql-table.mjs +111 -0
- package/dist/internal/native-sql-table.mjs.map +1 -0
- package/dist/internal/oauth/accounts.d.mts +8 -0
- package/dist/internal/oauth/accounts.mjs +379 -0
- package/dist/internal/oauth/accounts.mjs.map +1 -0
- package/dist/internal/oauth/connected-access.d.mts +9 -0
- package/dist/internal/oauth/connected-access.mjs +229 -0
- package/dist/internal/oauth/connected-access.mjs.map +1 -0
- package/dist/internal/oauth/connected-collection.d.mts +7 -0
- package/dist/internal/oauth/connected-collection.mjs +214 -0
- package/dist/internal/oauth/connected-collection.mjs.map +1 -0
- package/dist/internal/oauth/connected-custody.d.mts +6 -0
- package/dist/internal/oauth/connected-custody.mjs +165 -0
- package/dist/internal/oauth/connected-custody.mjs.map +1 -0
- package/dist/internal/oauth/connected-flow.d.mts +7 -0
- package/dist/internal/oauth/connected-flow.mjs +324 -0
- package/dist/internal/oauth/connected-flow.mjs.map +1 -0
- package/dist/internal/oauth/connected-input.mjs +122 -0
- package/dist/internal/oauth/connected-input.mjs.map +1 -0
- package/dist/internal/oauth/connected-keys.d.mts +2 -0
- package/dist/internal/oauth/connected-keys.mjs +22 -0
- package/dist/internal/oauth/connected-keys.mjs.map +1 -0
- package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
- package/dist/internal/oauth/connected-maintenance.mjs +226 -0
- package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
- package/dist/internal/oauth/connected-management.d.mts +9 -0
- package/dist/internal/oauth/connected-management.mjs +243 -0
- package/dist/internal/oauth/connected-management.mjs.map +1 -0
- package/dist/internal/oauth/connected-reference.mjs +63 -0
- package/dist/internal/oauth/connected-reference.mjs.map +1 -0
- package/dist/internal/oauth/connected-settlement.d.mts +8 -0
- package/dist/internal/oauth/connected-settlement.mjs +141 -0
- package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
- package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
- package/dist/internal/oauth/connected-sign-in.mjs +281 -0
- package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
- package/dist/internal/oauth/connected-state.d.mts +14 -0
- package/dist/internal/oauth/connected-state.mjs +469 -0
- package/dist/internal/oauth/connected-state.mjs.map +1 -0
- package/dist/internal/oauth/connected-target.d.mts +21 -0
- package/dist/internal/oauth/connected-target.mjs +117 -0
- package/dist/internal/oauth/connected-target.mjs.map +1 -0
- package/dist/internal/oauth/flow.d.mts +5 -0
- package/dist/internal/oauth/flow.mjs +279 -0
- package/dist/internal/oauth/flow.mjs.map +1 -0
- package/dist/internal/oauth/input.mjs +127 -0
- package/dist/internal/oauth/input.mjs.map +1 -0
- package/dist/internal/oauth/owner.d.mts +10 -0
- package/dist/internal/oauth/owner.mjs +10 -0
- package/dist/internal/oauth/owner.mjs.map +1 -0
- package/dist/internal/oauth/query-compiler.d.mts +7 -0
- package/dist/internal/oauth/query-compiler.mjs +9 -0
- package/dist/internal/oauth/query-compiler.mjs.map +1 -0
- package/dist/internal/oauth/registration.d.mts +7 -0
- package/dist/internal/oauth/registration.mjs +393 -0
- package/dist/internal/oauth/registration.mjs.map +1 -0
- package/dist/internal/oauth/state.mjs +107 -0
- package/dist/internal/oauth/state.mjs.map +1 -0
- package/dist/internal/oauth/target.d.mts +31 -0
- package/dist/internal/oauth/target.mjs +273 -0
- package/dist/internal/oauth/target.mjs.map +1 -0
- package/dist/internal/oauth-kernel.d.mts +1423 -0
- package/dist/internal/oauth-kernel.mjs +187 -0
- package/dist/internal/oauth-kernel.mjs.map +1 -0
- package/dist/internal/oauth-proxy.d.mts +31 -0
- package/dist/internal/oauth-proxy.mjs +129 -0
- package/dist/internal/oauth-proxy.mjs.map +1 -0
- package/dist/internal/passkey/target.d.mts +1 -1
- package/dist/internal/passkey-kernel.d.mts +5 -5
- package/dist/internal/passkeys.mjs +1 -1
- package/dist/internal/password-credentials.d.mts +11 -0
- package/dist/internal/password-credentials.mjs +121 -0
- package/dist/internal/password-credentials.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +15 -28
- package/dist/internal/password-kernel.mjs +35 -236
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +2 -0
- package/dist/internal/persistence.mjs +5 -5
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/query-operations.d.mts +2 -0
- package/dist/internal/session-kernel.d.mts +5 -3
- package/dist/internal/session-kernel.mjs +95 -29
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
- package/dist/internal/sql-oauth-connected-model.mjs +7 -0
- package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
- package/dist/internal/sql-oauth-model.d.mts +60 -0
- package/dist/internal/sql-oauth-model.mjs +10 -0
- package/dist/internal/sql-oauth-model.mjs.map +1 -0
- package/dist/internal/sql-persistence.mjs +3 -1
- package/dist/internal/sql-persistence.mjs.map +1 -1
- package/dist/internal/sql-query.d.mts +12 -0
- package/dist/internal/sql-query.mjs +15 -18
- package/dist/internal/sql-query.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +54 -0
- package/dist/internal/sql-storage-validation.mjs.map +1 -0
- package/dist/internal/storage-mapping.mjs +2 -67
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-plans.d.mts +7 -0
- package/dist/internal/storage-plans.mjs +189 -0
- package/dist/internal/storage-plans.mjs.map +1 -0
- package/dist/internal/storage-tables.d.mts +0 -99
- package/dist/internal/storage-tables.mjs +0 -43
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +6 -1
- package/dist/internal/storage-validation.mjs +42 -1
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-execution-kernel.d.mts +1 -1
- package/dist/internal/transaction-kernel.d.mts +1 -1
- package/dist/internal/transaction-kernel.mjs +3 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +97 -8
- package/src/OAuthPersistence.ts +427 -0
- package/src/index.ts +3 -0
- package/src/internal/authority-snapshot.ts +91 -0
- package/src/internal/configuration.ts +1 -8
- package/src/internal/models/oauth-connected-model.ts +396 -0
- package/src/internal/models/oauth-model.ts +708 -0
- package/src/internal/models/password-model.ts +0 -120
- package/src/internal/native-sql-table.ts +178 -0
- package/src/internal/oauth/accounts.ts +838 -0
- package/src/internal/oauth/connected-access.ts +388 -0
- package/src/internal/oauth/connected-collection.ts +345 -0
- package/src/internal/oauth/connected-custody.ts +308 -0
- package/src/internal/oauth/connected-flow.ts +562 -0
- package/src/internal/oauth/connected-input.ts +147 -0
- package/src/internal/oauth/connected-keys.ts +31 -0
- package/src/internal/oauth/connected-maintenance.ts +379 -0
- package/src/internal/oauth/connected-management.ts +391 -0
- package/src/internal/oauth/connected-reference.ts +135 -0
- package/src/internal/oauth/connected-settlement.ts +278 -0
- package/src/internal/oauth/connected-sign-in.ts +472 -0
- package/src/internal/oauth/connected-state.ts +924 -0
- package/src/internal/oauth/connected-target.ts +307 -0
- package/src/internal/oauth/flow.ts +505 -0
- package/src/internal/oauth/input.ts +201 -0
- package/src/internal/oauth/owner.ts +22 -0
- package/src/internal/oauth/query-compiler.ts +15 -0
- package/src/internal/oauth/registration.ts +676 -0
- package/src/internal/oauth/state.ts +218 -0
- package/src/internal/oauth/target.ts +803 -0
- package/src/internal/oauth-kernel.ts +294 -0
- package/src/internal/oauth-proxy.ts +176 -0
- package/src/internal/password-credentials.ts +245 -0
- package/src/internal/password-kernel.ts +98 -712
- package/src/internal/persistence.ts +5 -10
- package/src/internal/query-operations.ts +2 -0
- package/src/internal/session-kernel.ts +231 -81
- package/src/internal/sql-oauth-connected-model.ts +145 -0
- package/src/internal/sql-oauth-model.ts +263 -0
- package/src/internal/sql-persistence.ts +2 -0
- package/src/internal/sql-query.ts +16 -13
- package/src/internal/sql-storage-validation.ts +64 -0
- package/src/internal/storage-mapping.ts +0 -66
- package/src/internal/storage-plans.ts +218 -0
- package/src/internal/storage-tables.ts +0 -35
- package/src/internal/storage-validation.ts +58 -0
- package/src/internal/transaction-kernel.ts +6 -2
|
@@ -16,10 +16,7 @@ import {
|
|
|
16
16
|
snapshotPasswordRequirement,
|
|
17
17
|
snapshotPasswordRevision,
|
|
18
18
|
type PasswordAction,
|
|
19
|
-
type PasswordAttemptDecision,
|
|
20
|
-
type PasswordCredentialSnapshot,
|
|
21
19
|
type PasswordMutationDecision,
|
|
22
|
-
type PasswordAttemptPolicy,
|
|
23
20
|
} from "@yielded/auth/Password";
|
|
24
21
|
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
25
22
|
import {
|
|
@@ -34,16 +31,23 @@ import {
|
|
|
34
31
|
type SecurityRevision,
|
|
35
32
|
} from "@yielded/auth/Sessions";
|
|
36
33
|
import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
|
|
34
|
+
import type { SqlClient } from "effect/sql/SqlClient";
|
|
37
35
|
import type * as SqlError from "effect/sql/SqlError";
|
|
38
36
|
|
|
39
37
|
import { PersistenceMappingError } from "./mapping-error";
|
|
40
38
|
import {
|
|
41
39
|
type AnyPasswordPersistenceMapping,
|
|
42
|
-
type PasswordAttemptAction,
|
|
43
|
-
type PasswordRateScopeKind,
|
|
44
|
-
type PasswordScopeKeys,
|
|
45
40
|
requiredPasswordConstraints,
|
|
46
41
|
} from "./models/password-model";
|
|
42
|
+
import { makeNativeSqlTables, sqlMapping, type NativeSqlTables } from "./native-sql-table";
|
|
43
|
+
import {
|
|
44
|
+
makePasswordCredentials,
|
|
45
|
+
samePasswordCredentialSnapshot as sameCredentialSnapshot,
|
|
46
|
+
} from "./password-credentials";
|
|
47
|
+
import {
|
|
48
|
+
CurrentPasswordPreparedTransaction,
|
|
49
|
+
PasswordPreparedPostconditions,
|
|
50
|
+
} from "./PasswordPreparedPostconditions";
|
|
47
51
|
import {
|
|
48
52
|
CurrentProofSql,
|
|
49
53
|
type ProofSqlConfiguration,
|
|
@@ -53,7 +57,7 @@ import {
|
|
|
53
57
|
/* oxlint-disable no-explicit-any -- existing storage kernels erase foreign table shapes; domain errors remain typed. */
|
|
54
58
|
import type { QueryFailure } from "./query-operations";
|
|
55
59
|
import type { QueryOperations, SqlFragment, SqlColumn } from "./query-operations";
|
|
56
|
-
import
|
|
60
|
+
import { NativeDatabase } from "./transaction-kernel";
|
|
57
61
|
|
|
58
62
|
type AdapterFailure = QueryFailure | PersistenceMappingError | SqlError.SqlError;
|
|
59
63
|
|
|
@@ -104,23 +108,18 @@ export interface PasswordSqlConfiguration {
|
|
|
104
108
|
|
|
105
109
|
type Database = PasswordSqlDatabase;
|
|
106
110
|
|
|
107
|
-
interface ScopeEntry {
|
|
108
|
-
readonly kind: PasswordRateScopeKind;
|
|
109
|
-
readonly key: string;
|
|
110
|
-
readonly limit: number;
|
|
111
|
-
readonly windowMillis: number;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
111
|
export const makePasswordKernel = <
|
|
115
112
|
Fragment extends SqlFragment = SqlFragment,
|
|
116
113
|
Column extends SqlColumn = SqlColumn,
|
|
117
114
|
>(
|
|
118
115
|
operations: QueryOperations<Fragment, Column>,
|
|
119
116
|
proofs: Pick<ReturnType<typeof makeProofKernel>, "completeProofPlanIn">,
|
|
117
|
+
nativeTables: (client: SqlClient, database: object) => NativeSqlTables = (client) =>
|
|
118
|
+
makeNativeSqlTables(client),
|
|
120
119
|
) => {
|
|
121
120
|
type Mapping = AnyPasswordPersistenceMapping<Fragment>;
|
|
122
121
|
|
|
123
|
-
const { and, eq,
|
|
122
|
+
const { and, eq, inArray, column, updateValues } = operations;
|
|
124
123
|
const { completeProofPlanIn } = proofs;
|
|
125
124
|
const unavailable = () => PasswordUnavailable.make({});
|
|
126
125
|
|
|
@@ -179,44 +178,6 @@ export const makePasswordKernel = <
|
|
|
179
178
|
revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision),
|
|
180
179
|
});
|
|
181
180
|
|
|
182
|
-
const attemptColumns = (mapping: Mapping) => ({
|
|
183
|
-
moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
|
|
184
|
-
action: column(mapping.attempt.table, mapping.attempt.action),
|
|
185
|
-
attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
|
|
186
|
-
identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
|
|
187
|
-
identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
|
|
188
|
-
subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
|
|
189
|
-
credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
|
|
190
|
-
securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
|
|
191
|
-
credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
|
|
192
|
-
verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
|
|
193
|
-
identifierBindingRevision: column(
|
|
194
|
-
mapping.attempt.table,
|
|
195
|
-
mapping.attempt.identifierBindingRevision,
|
|
196
|
-
),
|
|
197
|
-
admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
|
|
198
|
-
deadline: column(mapping.attempt.table, mapping.attempt.deadline),
|
|
199
|
-
retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
|
|
200
|
-
state: column(mapping.attempt.table, mapping.attempt.state),
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
const scopeColumns = (mapping: Mapping) => ({
|
|
204
|
-
moduleId: column(mapping.rateScope.table, mapping.rateScope.moduleId),
|
|
205
|
-
action: column(mapping.rateScope.table, mapping.rateScope.action),
|
|
206
|
-
scopeKind: column(mapping.rateScope.table, mapping.rateScope.scopeKind),
|
|
207
|
-
scopeKey: column(mapping.rateScope.table, mapping.rateScope.scopeKey),
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const chargeColumns = (mapping: Mapping) => ({
|
|
211
|
-
moduleId: column(mapping.charge.table, mapping.charge.moduleId),
|
|
212
|
-
action: column(mapping.charge.table, mapping.charge.action),
|
|
213
|
-
scopeKind: column(mapping.charge.table, mapping.charge.scopeKind),
|
|
214
|
-
scopeKey: column(mapping.charge.table, mapping.charge.scopeKey),
|
|
215
|
-
attemptId: column(mapping.charge.table, mapping.charge.attemptId),
|
|
216
|
-
occurredAt: column(mapping.charge.table, mapping.charge.occurredAt),
|
|
217
|
-
retentionUntil: column(mapping.charge.table, mapping.charge.retentionUntil),
|
|
218
|
-
});
|
|
219
|
-
|
|
220
181
|
const commandColumns = (mapping: Mapping) => ({
|
|
221
182
|
moduleId: column(mapping.command.table, mapping.command.moduleId),
|
|
222
183
|
commandId: column(mapping.command.table, mapping.command.commandId),
|
|
@@ -270,28 +231,6 @@ export const makePasswordKernel = <
|
|
|
270
231
|
const sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>
|
|
271
232
|
left.namespace === right.namespace && left.value === right.value;
|
|
272
233
|
|
|
273
|
-
const sameCredentialSnapshot = (
|
|
274
|
-
left: PasswordCredentialSnapshot,
|
|
275
|
-
right: PasswordCredentialSnapshot,
|
|
276
|
-
) =>
|
|
277
|
-
left.moduleId === right.moduleId &&
|
|
278
|
-
left.revision.subjectId === right.revision.subjectId &&
|
|
279
|
-
left.revision.securityRevision === right.revision.securityRevision &&
|
|
280
|
-
left.credentialId === right.credentialId &&
|
|
281
|
-
left.credentialRevision === right.credentialRevision &&
|
|
282
|
-
left.verifierVersion === right.verifierVersion &&
|
|
283
|
-
left.normalization === right.normalization &&
|
|
284
|
-
left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis &&
|
|
285
|
-
left.revision.credentials.length === right.revision.credentials.length &&
|
|
286
|
-
left.revision.credentials.every((entry) =>
|
|
287
|
-
right.revision.credentials.some(
|
|
288
|
-
(other) => other.credentialId === entry.credentialId && other.revision === entry.revision,
|
|
289
|
-
),
|
|
290
|
-
) &&
|
|
291
|
-
left.identifierBindingRevision === right.identifierBindingRevision &&
|
|
292
|
-
sameIdentifier(left.identifier, right.identifier) &&
|
|
293
|
-
Redacted.value(left.verifier) === Redacted.value(right.verifier);
|
|
294
|
-
|
|
295
234
|
const evidenceSatisfiedAt = (
|
|
296
235
|
evidence: AuthenticationEvidence,
|
|
297
236
|
requirement: AuthenticationRequirement,
|
|
@@ -482,145 +421,6 @@ export const makePasswordKernel = <
|
|
|
482
421
|
};
|
|
483
422
|
});
|
|
484
423
|
|
|
485
|
-
const scopeEntries = (
|
|
486
|
-
keys: PasswordScopeKeys,
|
|
487
|
-
policy: PasswordAttemptPolicy,
|
|
488
|
-
): ReadonlyArray<ScopeEntry> => {
|
|
489
|
-
const values: ScopeEntry[] = [
|
|
490
|
-
{ kind: "action", key: keys.action, ...policy.action },
|
|
491
|
-
{ kind: "identifier", key: keys.identifier, ...policy.identifier },
|
|
492
|
-
];
|
|
493
|
-
|
|
494
|
-
if (keys.subject !== undefined)
|
|
495
|
-
values.push({ kind: "subject", key: keys.subject, ...policy.subject });
|
|
496
|
-
|
|
497
|
-
return values;
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const lockScope = Effect.fn("DrizzlePassword.lockScope")(function* (
|
|
501
|
-
mapping: Mapping,
|
|
502
|
-
configuration: PasswordSqlConfiguration,
|
|
503
|
-
moduleId: string,
|
|
504
|
-
action: PasswordAttemptAction,
|
|
505
|
-
entry: ScopeEntry,
|
|
506
|
-
) {
|
|
507
|
-
const database = yield* CurrentPasswordSql;
|
|
508
|
-
|
|
509
|
-
const query = database.insert(mapping.rateScope.table).values(
|
|
510
|
-
mapping.rateScope.encodeInsert({
|
|
511
|
-
moduleId,
|
|
512
|
-
action,
|
|
513
|
-
scopeKind: entry.kind,
|
|
514
|
-
scopeKey: entry.key,
|
|
515
|
-
}),
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
yield* configuration.insertIfAbsent(query, mapping.rateScope.scopeKey, entry.key);
|
|
519
|
-
const c = scopeColumns(mapping);
|
|
520
|
-
|
|
521
|
-
yield* selectRows(
|
|
522
|
-
database
|
|
523
|
-
.select()
|
|
524
|
-
.from(mapping.rateScope.table)
|
|
525
|
-
.where(
|
|
526
|
-
and(
|
|
527
|
-
eq(c.moduleId, moduleId),
|
|
528
|
-
eq(c.action, action),
|
|
529
|
-
eq(c.scopeKind, entry.kind),
|
|
530
|
-
eq(c.scopeKey, entry.key),
|
|
531
|
-
),
|
|
532
|
-
)
|
|
533
|
-
.limit(1),
|
|
534
|
-
configuration.locking,
|
|
535
|
-
);
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
const scopeAdmits = Effect.fn("DrizzlePassword.scopeAdmits")(function* (
|
|
539
|
-
mapping: Mapping,
|
|
540
|
-
moduleId: string,
|
|
541
|
-
action: PasswordAttemptAction,
|
|
542
|
-
entry: ScopeEntry,
|
|
543
|
-
policy: PasswordAttemptPolicy,
|
|
544
|
-
identifier: LoginIdentifier,
|
|
545
|
-
nativeSubjectId: unknown | undefined,
|
|
546
|
-
now: number,
|
|
547
|
-
) {
|
|
548
|
-
const database = yield* CurrentPasswordSql;
|
|
549
|
-
|
|
550
|
-
const c = chargeColumns(mapping);
|
|
551
|
-
|
|
552
|
-
const charges = yield* selectRows(
|
|
553
|
-
database
|
|
554
|
-
.select({ attemptId: c.attemptId })
|
|
555
|
-
.from(mapping.charge.table)
|
|
556
|
-
.where(
|
|
557
|
-
and(
|
|
558
|
-
eq(c.moduleId, moduleId),
|
|
559
|
-
eq(c.action, action),
|
|
560
|
-
eq(c.scopeKind, entry.kind),
|
|
561
|
-
eq(c.scopeKey, entry.key),
|
|
562
|
-
gte(c.occurredAt, mapping.encodeInstant(now - entry.windowMillis)),
|
|
563
|
-
),
|
|
564
|
-
)
|
|
565
|
-
.limit(entry.limit),
|
|
566
|
-
true,
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
if (charges.length >= entry.limit) return false;
|
|
570
|
-
const a = attemptColumns(mapping);
|
|
571
|
-
|
|
572
|
-
// Retention is the pending horizon. Expired rows stay until cleanup, but they
|
|
573
|
-
// no longer consume the admission budget after that horizon.
|
|
574
|
-
const pendingWhere = and(
|
|
575
|
-
entry.kind === "action"
|
|
576
|
-
? and(eq(a.moduleId, moduleId), eq(a.action, action))
|
|
577
|
-
: entry.kind === "identifier"
|
|
578
|
-
? and(
|
|
579
|
-
eq(a.moduleId, moduleId),
|
|
580
|
-
eq(a.action, action),
|
|
581
|
-
eq(a.identifierNamespace, identifier.namespace),
|
|
582
|
-
eq(a.identifierValue, identifier.value),
|
|
583
|
-
)
|
|
584
|
-
: and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.subjectId, nativeSubjectId)),
|
|
585
|
-
eq(a.state, "pending"),
|
|
586
|
-
gt(a.retentionUntil, mapping.encodeInstant(now)),
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
const pending = yield* selectRows(
|
|
590
|
-
database
|
|
591
|
-
.select({ attemptId: a.attemptId })
|
|
592
|
-
.from(mapping.attempt.table)
|
|
593
|
-
.where(pendingWhere)
|
|
594
|
-
.limit(policy.maximumPending),
|
|
595
|
-
true,
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
return pending.length < policy.maximumPending;
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const insertCharge = Effect.fn("Drizzle.insertCharge")(function* (
|
|
602
|
-
mapping: Mapping,
|
|
603
|
-
moduleId: string,
|
|
604
|
-
action: PasswordAttemptAction,
|
|
605
|
-
entry: ScopeEntry,
|
|
606
|
-
attemptId: any,
|
|
607
|
-
now: number,
|
|
608
|
-
) {
|
|
609
|
-
const database = yield* CurrentPasswordSql;
|
|
610
|
-
|
|
611
|
-
return yield* database.insert(mapping.charge.table).values(
|
|
612
|
-
mapping.charge.encodeInsert({
|
|
613
|
-
moduleId,
|
|
614
|
-
action,
|
|
615
|
-
scopeKind: entry.kind,
|
|
616
|
-
scopeKey: entry.key,
|
|
617
|
-
attemptId,
|
|
618
|
-
occurredAtMillis: now,
|
|
619
|
-
retentionUntilMillis: now + entry.windowMillis,
|
|
620
|
-
}),
|
|
621
|
-
);
|
|
622
|
-
});
|
|
623
|
-
|
|
624
424
|
const owned = <A, E, R>(
|
|
625
425
|
database: Database,
|
|
626
426
|
mapping: Mapping,
|
|
@@ -650,6 +450,25 @@ export const makePasswordKernel = <
|
|
|
650
450
|
: run;
|
|
651
451
|
};
|
|
652
452
|
|
|
453
|
+
// A coordinator that accepts postconditions re-runs this check against its
|
|
454
|
+
// current transaction after application work, before commit.
|
|
455
|
+
const registerFinal = Effect.fnUntraced(function* <E>(
|
|
456
|
+
check: Effect.Effect<boolean, E, CurrentPasswordSql>,
|
|
457
|
+
) {
|
|
458
|
+
const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);
|
|
459
|
+
|
|
460
|
+
if (Option.isNone(postconditions)) return;
|
|
461
|
+
|
|
462
|
+
const postcondition = Effect.gen(function* () {
|
|
463
|
+
const transaction = yield* CurrentPasswordPreparedTransaction;
|
|
464
|
+
|
|
465
|
+
if (!(yield* check.pipe(Effect.provideService(CurrentPasswordSql, transaction))))
|
|
466
|
+
return yield* unavailable();
|
|
467
|
+
}).pipe(translateFailure);
|
|
468
|
+
|
|
469
|
+
if (!postconditions.value.register(postcondition)) return yield* unavailable();
|
|
470
|
+
});
|
|
471
|
+
|
|
653
472
|
const safeRead = <A, E, R>(
|
|
654
473
|
database: Database,
|
|
655
474
|
configuration: PasswordSqlConfiguration,
|
|
@@ -1067,55 +886,16 @@ export const makePasswordKernel = <
|
|
|
1067
886
|
);
|
|
1068
887
|
yield* insertCommand(mapping, input, "add-password", commandNow ?? authority.now);
|
|
1069
888
|
|
|
1070
|
-
const
|
|
889
|
+
const applied = checkMutationApplied(
|
|
1071
890
|
mapping,
|
|
1072
|
-
input
|
|
891
|
+
input,
|
|
1073
892
|
authority.nativeSubjectId,
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const authorityInserted = (yield* database
|
|
1078
|
-
.select()
|
|
1079
|
-
.from(mapping.authorityCredential.table)
|
|
1080
|
-
.where(
|
|
1081
|
-
and(
|
|
1082
|
-
eq(authorityCredentialColumns(mapping).subjectId, authority.nativeSubjectId),
|
|
1083
|
-
eq(authorityCredentialColumns(mapping).credentialId, credentialId),
|
|
1084
|
-
eq(authorityCredentialColumns(mapping).revision, credentialRevision),
|
|
1085
|
-
),
|
|
1086
|
-
)
|
|
1087
|
-
.limit(1))[0];
|
|
1088
|
-
|
|
1089
|
-
const subjectUpdated = (yield* database
|
|
1090
|
-
.select()
|
|
1091
|
-
.from(mapping.subject.table)
|
|
1092
|
-
.where(
|
|
1093
|
-
and(
|
|
1094
|
-
eq(subjectColumns(mapping).id, authority.nativeSubjectId),
|
|
1095
|
-
eq(subjectColumns(mapping).securityRevision, nextSecurityRevision),
|
|
1096
|
-
),
|
|
1097
|
-
)
|
|
1098
|
-
.limit(1))[0];
|
|
893
|
+
revisions,
|
|
894
|
+
commandNow ?? authority.now,
|
|
895
|
+
);
|
|
1099
896
|
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
inserted[mapping.credential.credentialId] !== credentialId ||
|
|
1103
|
-
inserted[mapping.credential.credentialRevision] !== credentialRevision ||
|
|
1104
|
-
inserted[mapping.credential.verifierVersion] !== verifierVersion ||
|
|
1105
|
-
inserted[mapping.credential.verifier] !== Redacted.value(input.replacement.verifier) ||
|
|
1106
|
-
inserted[mapping.credential.normalization] !== input.replacement.normalization ||
|
|
1107
|
-
authorityInserted === undefined ||
|
|
1108
|
-
subjectUpdated === undefined ||
|
|
1109
|
-
!(yield* mutationPostconditions(
|
|
1110
|
-
mapping,
|
|
1111
|
-
input,
|
|
1112
|
-
authority.nativeSubjectId,
|
|
1113
|
-
subjectUpdated,
|
|
1114
|
-
authorityInserted,
|
|
1115
|
-
commandNow ?? authority.now,
|
|
1116
|
-
))
|
|
1117
|
-
)
|
|
1118
|
-
return yield* unavailable();
|
|
897
|
+
if (!(yield* applied)) return yield* unavailable();
|
|
898
|
+
yield* registerFinal(applied);
|
|
1119
899
|
|
|
1120
900
|
return true;
|
|
1121
901
|
});
|
|
@@ -1139,267 +919,19 @@ export const makePasswordKernel = <
|
|
|
1139
919
|
).pipe(Effect.mapError(unavailable));
|
|
1140
920
|
const hooks = yield* LifecycleHooks;
|
|
1141
921
|
|
|
1142
|
-
return
|
|
1143
|
-
|
|
1144
|
-
Effect.gen(function* () {
|
|
1145
|
-
const attemptId = yield* allocate(
|
|
1146
|
-
configuration.mode,
|
|
1147
|
-
mapping.allocateAttemptId,
|
|
1148
|
-
mapping.allocateAttemptIdSync,
|
|
1149
|
-
);
|
|
1150
|
-
|
|
1151
|
-
return yield* owned(
|
|
1152
|
-
database,
|
|
1153
|
-
mapping,
|
|
1154
|
-
configuration,
|
|
1155
|
-
Effect.gen(function* () {
|
|
1156
|
-
const transaction = yield* CurrentPasswordSql;
|
|
1157
|
-
const journal = yield* CurrentCommitJournal;
|
|
1158
|
-
|
|
1159
|
-
const resolved = yield* resolveCredential(
|
|
1160
|
-
mapping,
|
|
1161
|
-
input.moduleId,
|
|
1162
|
-
input.identifier,
|
|
1163
|
-
input.subjectId,
|
|
1164
|
-
configuration.locking,
|
|
1165
|
-
);
|
|
1166
|
-
|
|
1167
|
-
const keys = mapping.scopeKeys({
|
|
1168
|
-
moduleId: input.moduleId,
|
|
1169
|
-
action: input.action,
|
|
1170
|
-
identifier: input.identifier,
|
|
1171
|
-
...(resolved?.snapshot === undefined
|
|
1172
|
-
? {}
|
|
1173
|
-
: { subjectId: resolved.snapshot.revision.subjectId }),
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
const scopes = scopeEntries(keys, input.policy);
|
|
1177
|
-
const admitted: ScopeEntry[] = [];
|
|
1178
|
-
|
|
1179
|
-
yield* lockScope(mapping, configuration, input.moduleId, input.action, scopes[0]!);
|
|
1180
|
-
let currentNow = yield* nowMillis;
|
|
1181
|
-
|
|
1182
|
-
if (
|
|
1183
|
-
yield* scopeAdmits(
|
|
1184
|
-
mapping,
|
|
1185
|
-
input.moduleId,
|
|
1186
|
-
input.action,
|
|
1187
|
-
scopes[0]!,
|
|
1188
|
-
input.policy,
|
|
1189
|
-
input.identifier,
|
|
1190
|
-
resolved?.nativeSubjectId,
|
|
1191
|
-
currentNow,
|
|
1192
|
-
)
|
|
1193
|
-
)
|
|
1194
|
-
admitted.push(scopes[0]!);
|
|
1195
|
-
if (admitted.length === 1) {
|
|
1196
|
-
for (const entry of scopes.slice(1)) {
|
|
1197
|
-
yield* lockScope(mapping, configuration, input.moduleId, input.action, entry);
|
|
1198
|
-
currentNow = yield* nowMillis;
|
|
1199
|
-
if (
|
|
1200
|
-
yield* scopeAdmits(
|
|
1201
|
-
mapping,
|
|
1202
|
-
input.moduleId,
|
|
1203
|
-
input.action,
|
|
1204
|
-
entry,
|
|
1205
|
-
input.policy,
|
|
1206
|
-
input.identifier,
|
|
1207
|
-
resolved?.nativeSubjectId,
|
|
1208
|
-
currentNow,
|
|
1209
|
-
)
|
|
1210
|
-
)
|
|
1211
|
-
admitted.push(entry);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
const now = yield* nowMillis;
|
|
1215
|
-
|
|
1216
|
-
for (const entry of admitted)
|
|
1217
|
-
yield* insertCharge(mapping, input.moduleId, input.action, entry, attemptId, now);
|
|
1218
|
-
if (admitted.length !== scopes.length)
|
|
1219
|
-
return prepare({ _tag: "Denied" as const }, journal);
|
|
1220
|
-
const snapshot = resolved?.snapshot;
|
|
1221
|
-
const deadline = now + input.policy.attemptLifetimeMillis;
|
|
1222
|
-
|
|
1223
|
-
const retentionUntil = Math.max(
|
|
1224
|
-
deadline,
|
|
1225
|
-
...scopes.map((entry) => now + entry.windowMillis),
|
|
1226
|
-
);
|
|
1227
|
-
|
|
1228
|
-
yield* transaction.insert(mapping.attempt.table).values(
|
|
1229
|
-
mapping.attempt.encodeInsert(
|
|
1230
|
-
{
|
|
1231
|
-
moduleId: input.moduleId,
|
|
1232
|
-
action: input.action,
|
|
1233
|
-
attemptId,
|
|
1234
|
-
identifier: input.identifier,
|
|
1235
|
-
...(snapshot === undefined
|
|
1236
|
-
? {}
|
|
1237
|
-
: {
|
|
1238
|
-
subjectId: snapshot.revision.subjectId,
|
|
1239
|
-
credentialId: snapshot.credentialId,
|
|
1240
|
-
securityRevision: snapshot.revision.securityRevision,
|
|
1241
|
-
credentialRevision: snapshot.credentialRevision,
|
|
1242
|
-
verifierVersion: snapshot.verifierVersion,
|
|
1243
|
-
identifierBindingRevision: snapshot.identifierBindingRevision,
|
|
1244
|
-
}),
|
|
1245
|
-
admittedAtMillis: now,
|
|
1246
|
-
deadlineMillis: deadline,
|
|
1247
|
-
retentionUntilMillis: retentionUntil,
|
|
1248
|
-
},
|
|
1249
|
-
{
|
|
1250
|
-
...(resolved?.nativeSubjectId === undefined
|
|
1251
|
-
? {}
|
|
1252
|
-
: { nativeSubjectId: resolved.nativeSubjectId }),
|
|
1253
|
-
state: "pending",
|
|
1254
|
-
},
|
|
1255
|
-
),
|
|
1256
|
-
);
|
|
1257
|
-
|
|
1258
|
-
return prepare(
|
|
1259
|
-
{
|
|
1260
|
-
_tag: "Admitted" as const,
|
|
1261
|
-
attemptId,
|
|
1262
|
-
...(snapshot === undefined ? {} : { credential: snapshot }),
|
|
1263
|
-
},
|
|
1264
|
-
journal,
|
|
1265
|
-
);
|
|
1266
|
-
}),
|
|
1267
|
-
);
|
|
1268
|
-
}).pipe(
|
|
1269
|
-
Effect.provideService(CurrentPasswordSql, database),
|
|
1270
|
-
Effect.provideService(LifecycleHooks, hooks),
|
|
1271
|
-
translateFailure,
|
|
1272
|
-
),
|
|
1273
|
-
settleAttempt: (uncaptured, prepare) =>
|
|
1274
|
-
Effect.gen(function* () {
|
|
1275
|
-
const captured =
|
|
1276
|
-
uncaptured.captured === undefined
|
|
1277
|
-
? undefined
|
|
1278
|
-
: yield* snapshotPasswordCredential(uncaptured.captured);
|
|
1279
|
-
|
|
1280
|
-
const input = { ...uncaptured, ...(captured === undefined ? {} : { captured }) };
|
|
1281
|
-
|
|
1282
|
-
return yield* owned(
|
|
1283
|
-
database,
|
|
1284
|
-
mapping,
|
|
1285
|
-
configuration,
|
|
1286
|
-
Effect.gen(function* () {
|
|
1287
|
-
const transaction = yield* CurrentPasswordSql;
|
|
1288
|
-
const journal = yield* CurrentCommitJournal;
|
|
1289
|
-
|
|
1290
|
-
const c = attemptColumns(mapping);
|
|
1291
|
-
|
|
1292
|
-
const discovered = yield* selectRows(
|
|
1293
|
-
transaction
|
|
1294
|
-
.select()
|
|
1295
|
-
.from(mapping.attempt.table)
|
|
1296
|
-
.where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId)))
|
|
1297
|
-
.limit(1),
|
|
1298
|
-
false,
|
|
1299
|
-
);
|
|
1300
|
-
|
|
1301
|
-
let row = discovered[0];
|
|
1302
|
-
|
|
1303
|
-
if (row === undefined || row[mapping.attempt.state] !== "pending")
|
|
1304
|
-
return prepare("rejected", journal);
|
|
1305
|
-
let decision: PasswordAttemptDecision = "rejected";
|
|
1306
|
-
let current: any;
|
|
1307
|
-
let attemptMatches = false;
|
|
1308
|
-
|
|
1309
|
-
if (input.outcome === "verified" && input.captured !== undefined) {
|
|
1310
|
-
current = yield* resolveCredential(
|
|
1311
|
-
mapping,
|
|
1312
|
-
input.moduleId,
|
|
1313
|
-
input.captured.identifier,
|
|
1314
|
-
input.captured.revision.subjectId,
|
|
1315
|
-
configuration.locking,
|
|
1316
|
-
);
|
|
1317
|
-
attemptMatches =
|
|
1318
|
-
input.captured.moduleId === input.moduleId &&
|
|
1319
|
-
row[mapping.attempt.identifierNamespace] ===
|
|
1320
|
-
input.captured.identifier.namespace &&
|
|
1321
|
-
row[mapping.attempt.identifierValue] === input.captured.identifier.value &&
|
|
1322
|
-
mapping.subjectId.equals(
|
|
1323
|
-
row[mapping.attempt.subjectId],
|
|
1324
|
-
current?.nativeSubjectId,
|
|
1325
|
-
) &&
|
|
1326
|
-
row[mapping.attempt.credentialId] === input.captured.credentialId &&
|
|
1327
|
-
row[mapping.attempt.securityRevision] ===
|
|
1328
|
-
input.captured.revision.securityRevision &&
|
|
1329
|
-
row[mapping.attempt.credentialRevision] === input.captured.credentialRevision &&
|
|
1330
|
-
row[mapping.attempt.verifierVersion] === input.captured.verifierVersion &&
|
|
1331
|
-
row[mapping.attempt.identifierBindingRevision] ===
|
|
1332
|
-
input.captured.identifierBindingRevision;
|
|
1333
|
-
}
|
|
1334
|
-
row = (yield* selectRows(
|
|
1335
|
-
transaction
|
|
1336
|
-
.select()
|
|
1337
|
-
.from(mapping.attempt.table)
|
|
1338
|
-
.where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId)))
|
|
1339
|
-
.limit(1),
|
|
1340
|
-
configuration.locking,
|
|
1341
|
-
))[0];
|
|
1342
|
-
if (row === undefined || row[mapping.attempt.state] !== "pending")
|
|
1343
|
-
return prepare("rejected", journal);
|
|
1344
|
-
const now = yield* nowMillis;
|
|
1345
|
-
const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
|
|
922
|
+
if (!validConstraints(mapping)) return yield* unavailable();
|
|
923
|
+
const root = yield* NativeDatabase;
|
|
1346
924
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
decision = "verified";
|
|
1355
|
-
const prepared = prepare(decision, journal);
|
|
1356
|
-
|
|
1357
|
-
yield* transaction
|
|
1358
|
-
.update(mapping.attempt.table)
|
|
1359
|
-
.set(updateValues([[mapping.attempt.state, decision]]))
|
|
1360
|
-
.where(
|
|
1361
|
-
and(
|
|
1362
|
-
eq(c.moduleId, input.moduleId),
|
|
1363
|
-
eq(c.attemptId, input.attemptId),
|
|
1364
|
-
eq(c.state, "pending"),
|
|
1365
|
-
),
|
|
1366
|
-
);
|
|
1367
|
-
if (
|
|
1368
|
-
decision === "verified" &&
|
|
1369
|
-
input.rehash !== undefined &&
|
|
1370
|
-
current?.snapshot !== undefined
|
|
1371
|
-
) {
|
|
1372
|
-
const cc = credentialColumns(mapping);
|
|
1373
|
-
|
|
1374
|
-
const nextVersion = yield* allocate(
|
|
1375
|
-
configuration.mode,
|
|
1376
|
-
mapping.allocateRevision,
|
|
1377
|
-
mapping.allocateRevisionSync,
|
|
1378
|
-
);
|
|
1379
|
-
|
|
1380
|
-
yield* transaction
|
|
1381
|
-
.update(mapping.credential.table)
|
|
1382
|
-
.set(mapping.credential.encodeVerifier(input.rehash.nextVerifier, nextVersion))
|
|
1383
|
-
.where(
|
|
1384
|
-
and(
|
|
1385
|
-
eq(cc.moduleId, input.moduleId),
|
|
1386
|
-
eq(cc.subjectId, current.nativeSubjectId),
|
|
1387
|
-
eq(cc.credentialId, current.snapshot.credentialId),
|
|
1388
|
-
eq(cc.credentialRevision, current.snapshot.credentialRevision),
|
|
1389
|
-
eq(cc.verifierVersion, input.rehash.expectedVersion),
|
|
1390
|
-
eq(cc.verifier, Redacted.value(input.rehash.expectedVerifier)),
|
|
1391
|
-
),
|
|
1392
|
-
);
|
|
1393
|
-
}
|
|
925
|
+
const credentials = yield* makePasswordCredentials(
|
|
926
|
+
yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(
|
|
927
|
+
translateFailure,
|
|
928
|
+
),
|
|
929
|
+
mapping,
|
|
930
|
+
configuration,
|
|
931
|
+
);
|
|
1394
932
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
);
|
|
1398
|
-
}).pipe(
|
|
1399
|
-
Effect.provideService(CurrentPasswordSql, database),
|
|
1400
|
-
Effect.provideService(LifecycleHooks, hooks),
|
|
1401
|
-
translateFailure,
|
|
1402
|
-
),
|
|
933
|
+
return PasswordPersistence.of({
|
|
934
|
+
...credentials,
|
|
1403
935
|
readForSubject: (input) =>
|
|
1404
936
|
safeRead(
|
|
1405
937
|
database,
|
|
@@ -1691,13 +1223,30 @@ export const makePasswordKernel = <
|
|
|
1691
1223
|
configuration.proof!.configuration,
|
|
1692
1224
|
input.completion,
|
|
1693
1225
|
Effect.gen(function* () {
|
|
1694
|
-
|
|
1226
|
+
const revisions = {
|
|
1227
|
+
credentialRevision,
|
|
1228
|
+
verifierVersion,
|
|
1229
|
+
nextSecurityRevision,
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const applied = yield* writeReplacement(
|
|
1695
1233
|
mapping,
|
|
1696
1234
|
input,
|
|
1697
1235
|
authority.nativeSubjectId,
|
|
1698
|
-
|
|
1236
|
+
revisions,
|
|
1699
1237
|
authority.now,
|
|
1700
1238
|
);
|
|
1239
|
+
|
|
1240
|
+
if (applied)
|
|
1241
|
+
yield* registerReplacement(
|
|
1242
|
+
mapping,
|
|
1243
|
+
input,
|
|
1244
|
+
authority.nativeSubjectId,
|
|
1245
|
+
revisions,
|
|
1246
|
+
authority.now,
|
|
1247
|
+
);
|
|
1248
|
+
|
|
1249
|
+
return applied;
|
|
1701
1250
|
}),
|
|
1702
1251
|
(decision) => {
|
|
1703
1252
|
passwordReceipt = prepare(
|
|
@@ -1718,197 +1267,6 @@ export const makePasswordKernel = <
|
|
|
1718
1267
|
Effect.provideService(LifecycleHooks, hooks),
|
|
1719
1268
|
translateFailure,
|
|
1720
1269
|
),
|
|
1721
|
-
cleanupAttempts: (input, prepare) =>
|
|
1722
|
-
owned(
|
|
1723
|
-
database,
|
|
1724
|
-
mapping,
|
|
1725
|
-
configuration,
|
|
1726
|
-
Effect.gen(function* () {
|
|
1727
|
-
const transaction = yield* CurrentPasswordSql;
|
|
1728
|
-
const journal = yield* CurrentCommitJournal;
|
|
1729
|
-
|
|
1730
|
-
const now = yield* nowMillis;
|
|
1731
|
-
const nativeNow = mapping.encodeInstant(now);
|
|
1732
|
-
let remaining = input.limit;
|
|
1733
|
-
let hasMore = false;
|
|
1734
|
-
const a = attemptColumns(mapping);
|
|
1735
|
-
const c = chargeColumns(mapping);
|
|
1736
|
-
|
|
1737
|
-
const attempts = yield* selectRows(
|
|
1738
|
-
transaction
|
|
1739
|
-
.select({ attemptId: a.attemptId })
|
|
1740
|
-
.from(mapping.attempt.table)
|
|
1741
|
-
.where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow)))
|
|
1742
|
-
.limit(remaining + 1),
|
|
1743
|
-
false,
|
|
1744
|
-
);
|
|
1745
|
-
|
|
1746
|
-
if (attempts.length > remaining) hasMore = true;
|
|
1747
|
-
const attemptCandidates = attempts.slice(0, remaining);
|
|
1748
|
-
|
|
1749
|
-
remaining -= attemptCandidates.length;
|
|
1750
|
-
|
|
1751
|
-
const charges = yield* selectRows(
|
|
1752
|
-
transaction
|
|
1753
|
-
.select({
|
|
1754
|
-
action: c.action,
|
|
1755
|
-
scopeKind: c.scopeKind,
|
|
1756
|
-
scopeKey: c.scopeKey,
|
|
1757
|
-
attemptId: c.attemptId,
|
|
1758
|
-
})
|
|
1759
|
-
.from(mapping.charge.table)
|
|
1760
|
-
.where(and(eq(c.moduleId, input.moduleId), lte(c.retentionUntil, nativeNow)))
|
|
1761
|
-
.limit(remaining + 1),
|
|
1762
|
-
false,
|
|
1763
|
-
);
|
|
1764
|
-
|
|
1765
|
-
if (charges.length > remaining) hasMore = true;
|
|
1766
|
-
const chargeCandidates = charges.slice(0, remaining);
|
|
1767
|
-
|
|
1768
|
-
remaining -= chargeCandidates.length;
|
|
1769
|
-
const sc = scopeColumns(mapping);
|
|
1770
|
-
|
|
1771
|
-
const scopes = yield* selectRows(
|
|
1772
|
-
transaction
|
|
1773
|
-
.select({ action: sc.action, scopeKind: sc.scopeKind, scopeKey: sc.scopeKey })
|
|
1774
|
-
.from(mapping.rateScope.table)
|
|
1775
|
-
.where(
|
|
1776
|
-
and(
|
|
1777
|
-
eq(sc.moduleId, input.moduleId),
|
|
1778
|
-
notExists(
|
|
1779
|
-
transaction
|
|
1780
|
-
.select({ one: sql`1` })
|
|
1781
|
-
.from(mapping.charge.table)
|
|
1782
|
-
.where(
|
|
1783
|
-
and(
|
|
1784
|
-
eq(c.moduleId, sc.moduleId),
|
|
1785
|
-
eq(c.action, sc.action),
|
|
1786
|
-
eq(c.scopeKind, sc.scopeKind),
|
|
1787
|
-
eq(c.scopeKey, sc.scopeKey),
|
|
1788
|
-
),
|
|
1789
|
-
),
|
|
1790
|
-
),
|
|
1791
|
-
),
|
|
1792
|
-
)
|
|
1793
|
-
.limit(remaining + 1),
|
|
1794
|
-
remaining === 0 ? false : configuration.locking,
|
|
1795
|
-
);
|
|
1796
|
-
|
|
1797
|
-
if (scopes.length > remaining) hasMore = true;
|
|
1798
|
-
const selectedScopes = scopes.slice(0, remaining);
|
|
1799
|
-
|
|
1800
|
-
remaining -= selectedScopes.length;
|
|
1801
|
-
const selectedCharges: typeof chargeCandidates = [];
|
|
1802
|
-
|
|
1803
|
-
for (const candidate of chargeCandidates) {
|
|
1804
|
-
const locked = yield* selectRows(
|
|
1805
|
-
transaction
|
|
1806
|
-
.select({
|
|
1807
|
-
action: c.action,
|
|
1808
|
-
scopeKind: c.scopeKind,
|
|
1809
|
-
scopeKey: c.scopeKey,
|
|
1810
|
-
attemptId: c.attemptId,
|
|
1811
|
-
})
|
|
1812
|
-
.from(mapping.charge.table)
|
|
1813
|
-
.where(
|
|
1814
|
-
and(
|
|
1815
|
-
eq(c.moduleId, input.moduleId),
|
|
1816
|
-
eq(c.action, candidate.action),
|
|
1817
|
-
eq(c.scopeKind, candidate.scopeKind),
|
|
1818
|
-
eq(c.scopeKey, candidate.scopeKey),
|
|
1819
|
-
eq(c.attemptId, candidate.attemptId),
|
|
1820
|
-
lte(c.retentionUntil, nativeNow),
|
|
1821
|
-
),
|
|
1822
|
-
)
|
|
1823
|
-
.limit(1),
|
|
1824
|
-
configuration.locking,
|
|
1825
|
-
);
|
|
1826
|
-
|
|
1827
|
-
if (locked[0] !== undefined) selectedCharges.push(locked[0] as any);
|
|
1828
|
-
}
|
|
1829
|
-
const selectedAttempts: typeof attemptCandidates = [];
|
|
1830
|
-
|
|
1831
|
-
for (const candidate of attemptCandidates) {
|
|
1832
|
-
const locked = yield* selectRows(
|
|
1833
|
-
transaction
|
|
1834
|
-
.select({ attemptId: a.attemptId })
|
|
1835
|
-
.from(mapping.attempt.table)
|
|
1836
|
-
.where(
|
|
1837
|
-
and(
|
|
1838
|
-
eq(a.moduleId, input.moduleId),
|
|
1839
|
-
eq(a.attemptId, candidate.attemptId),
|
|
1840
|
-
lte(a.retentionUntil, nativeNow),
|
|
1841
|
-
),
|
|
1842
|
-
)
|
|
1843
|
-
.limit(1),
|
|
1844
|
-
configuration.locking,
|
|
1845
|
-
);
|
|
1846
|
-
|
|
1847
|
-
if (locked[0] !== undefined) selectedAttempts.push(locked[0] as any);
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
const prepared = prepare(
|
|
1851
|
-
{
|
|
1852
|
-
removed: selectedAttempts.length + selectedCharges.length + selectedScopes.length,
|
|
1853
|
-
hasMore,
|
|
1854
|
-
},
|
|
1855
|
-
journal,
|
|
1856
|
-
);
|
|
1857
|
-
|
|
1858
|
-
for (const row of selectedCharges)
|
|
1859
|
-
yield* transaction
|
|
1860
|
-
.delete(mapping.charge.table)
|
|
1861
|
-
.where(
|
|
1862
|
-
and(
|
|
1863
|
-
eq(c.moduleId, input.moduleId),
|
|
1864
|
-
eq(c.action, row.action),
|
|
1865
|
-
eq(c.scopeKind, row.scopeKind),
|
|
1866
|
-
eq(c.scopeKey, row.scopeKey),
|
|
1867
|
-
eq(c.attemptId, row.attemptId),
|
|
1868
|
-
lte(c.retentionUntil, nativeNow),
|
|
1869
|
-
),
|
|
1870
|
-
);
|
|
1871
|
-
if (selectedAttempts.length > 0)
|
|
1872
|
-
yield* transaction.delete(mapping.attempt.table).where(
|
|
1873
|
-
and(
|
|
1874
|
-
eq(a.moduleId, input.moduleId),
|
|
1875
|
-
inArray(
|
|
1876
|
-
a.attemptId,
|
|
1877
|
-
selectedAttempts.map((row: any) => row.attemptId),
|
|
1878
|
-
),
|
|
1879
|
-
lte(a.retentionUntil, nativeNow),
|
|
1880
|
-
),
|
|
1881
|
-
);
|
|
1882
|
-
for (const row of selectedScopes)
|
|
1883
|
-
yield* transaction.delete(mapping.rateScope.table).where(
|
|
1884
|
-
and(
|
|
1885
|
-
eq(sc.moduleId, input.moduleId),
|
|
1886
|
-
eq(sc.action, row.action),
|
|
1887
|
-
eq(sc.scopeKind, row.scopeKind),
|
|
1888
|
-
eq(sc.scopeKey, row.scopeKey),
|
|
1889
|
-
notExists(
|
|
1890
|
-
transaction
|
|
1891
|
-
.select({ one: sql`1` })
|
|
1892
|
-
.from(mapping.charge.table)
|
|
1893
|
-
.where(
|
|
1894
|
-
and(
|
|
1895
|
-
eq(c.moduleId, sc.moduleId),
|
|
1896
|
-
eq(c.action, sc.action),
|
|
1897
|
-
eq(c.scopeKind, sc.scopeKind),
|
|
1898
|
-
eq(c.scopeKey, sc.scopeKey),
|
|
1899
|
-
),
|
|
1900
|
-
),
|
|
1901
|
-
),
|
|
1902
|
-
),
|
|
1903
|
-
);
|
|
1904
|
-
|
|
1905
|
-
return prepared;
|
|
1906
|
-
}),
|
|
1907
|
-
).pipe(
|
|
1908
|
-
Effect.provideService(CurrentPasswordSql, database),
|
|
1909
|
-
Effect.provideService(LifecycleHooks, hooks),
|
|
1910
|
-
translateFailure,
|
|
1911
|
-
),
|
|
1912
1270
|
});
|
|
1913
1271
|
});
|
|
1914
1272
|
|
|
@@ -1987,8 +1345,14 @@ export const makePasswordKernel = <
|
|
|
1987
1345
|
)
|
|
1988
1346
|
.limit(1))[0];
|
|
1989
1347
|
|
|
1348
|
+
// SQL equality follows column collation; compare the persisted values exactly.
|
|
1990
1349
|
return (
|
|
1991
1350
|
credential !== undefined &&
|
|
1351
|
+
credential[mapping.credential.credentialId] === revisions.credentialId &&
|
|
1352
|
+
credential[mapping.credential.credentialRevision] === revisions.credentialRevision &&
|
|
1353
|
+
credential[mapping.credential.verifierVersion] === revisions.verifierVersion &&
|
|
1354
|
+
credential[mapping.credential.verifier] === Redacted.value(input.replacement.verifier) &&
|
|
1355
|
+
credential[mapping.credential.normalization] === input.replacement.normalization &&
|
|
1992
1356
|
(yield* mutationPostconditions(
|
|
1993
1357
|
mapping,
|
|
1994
1358
|
input,
|
|
@@ -2069,6 +1433,27 @@ export const makePasswordKernel = <
|
|
|
2069
1433
|
);
|
|
2070
1434
|
});
|
|
2071
1435
|
|
|
1436
|
+
const registerReplacement = (
|
|
1437
|
+
mapping: Mapping,
|
|
1438
|
+
input: PasswordMutationInput,
|
|
1439
|
+
nativeSubjectId: unknown,
|
|
1440
|
+
revisions: {
|
|
1441
|
+
readonly credentialRevision: SecurityRevision;
|
|
1442
|
+
readonly verifierVersion: SecurityRevision;
|
|
1443
|
+
readonly nextSecurityRevision: SecurityRevision;
|
|
1444
|
+
},
|
|
1445
|
+
now: number,
|
|
1446
|
+
) =>
|
|
1447
|
+
registerFinal(
|
|
1448
|
+
checkMutationApplied(
|
|
1449
|
+
mapping,
|
|
1450
|
+
input,
|
|
1451
|
+
nativeSubjectId,
|
|
1452
|
+
{ ...revisions, credentialId: input.credential!.credentialId },
|
|
1453
|
+
now,
|
|
1454
|
+
),
|
|
1455
|
+
);
|
|
1456
|
+
|
|
2072
1457
|
const replaceIn = Effect.fn("Drizzle.replaceIn")(function* <A>(
|
|
2073
1458
|
mapping: Mapping,
|
|
2074
1459
|
configuration: PasswordSqlConfiguration,
|
|
@@ -2102,6 +1487,7 @@ export const makePasswordKernel = <
|
|
|
2102
1487
|
))
|
|
2103
1488
|
)
|
|
2104
1489
|
return yield* unavailable();
|
|
1490
|
+
yield* registerReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now);
|
|
2105
1491
|
|
|
2106
1492
|
return prepared;
|
|
2107
1493
|
});
|