@yielded/auth-persistence 0.1.0-beta.23 → 0.1.0-beta.25
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 +12 -6
- package/dist/Adapter.mjs +8 -3
- 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/configuration.d.mts +1 -1
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-kernel.d.mts +1 -1
- package/dist/internal/email-kernel.mjs +1 -1
- package/dist/internal/email-kernel.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 +6 -48
- package/dist/internal/models/password-model.mjs +0 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +4 -0
- package/dist/internal/native-sql-table.mjs +7 -0
- package/dist/internal/native-sql-table.mjs.map +1 -1
- 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/write-state.mjs +9 -1
- package/dist/internal/passkey/write-state.mjs.map +1 -1
- package/dist/internal/passkey-kernel.d.mts +9 -9
- package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
- package/dist/internal/password-credentials.mjs +121 -0
- package/dist/internal/password-credentials.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +8 -24
- package/dist/internal/password-kernel.mjs +5 -43
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +1 -1
- package/dist/internal/persistence.mjs +2 -2
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/query-operations.d.mts +2 -0
- package/dist/internal/session-kernel.d.mts +2 -2
- package/dist/internal/session-kernel.mjs +74 -18
- 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-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 +1 -37
- 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 -56
- package/dist/internal/storage-tables.mjs +0 -17
- 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-kernel.mjs +3 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +89 -5
- package/src/OAuthPersistence.ts +427 -0
- package/src/index.ts +3 -0
- package/src/internal/configuration.ts +1 -3
- package/src/internal/email-kernel.ts +2 -1
- 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 +2 -57
- package/src/internal/native-sql-table.ts +14 -0
- package/src/internal/oauth/accounts.ts +839 -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/passkey/write-state.ts +18 -4
- package/src/internal/password-credentials.ts +245 -0
- package/src/internal/password-kernel.ts +7 -99
- package/src/internal/persistence.ts +1 -1
- package/src/internal/query-operations.ts +2 -0
- package/src/internal/session-kernel.ts +143 -55
- package/src/internal/sql-oauth-connected-model.ts +145 -0
- package/src/internal/sql-oauth-model.ts +263 -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 -37
- package/src/internal/storage-plans.ts +218 -0
- package/src/internal/storage-tables.ts +0 -20
- package/src/internal/storage-validation.ts +58 -0
- package/src/internal/transaction-kernel.ts +6 -2
- package/dist/internal/password-attempts.mjs +0 -197
- package/dist/internal/password-attempts.mjs.map +0 -1
- package/src/internal/password-attempts.ts +0 -466
|
@@ -4374,8 +4374,8 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
4374
4374
|
readonly ceremony: import("@yielded/auth/Passkey").PasskeyCeremony;
|
|
4375
4375
|
readonly claimId: string;
|
|
4376
4376
|
}) => import("effect/Effect").Effect<{
|
|
4377
|
-
readonly claim?: never;
|
|
4378
4377
|
readonly _tag: "Rejected";
|
|
4378
|
+
readonly claim?: never;
|
|
4379
4379
|
} | {
|
|
4380
4380
|
readonly _tag: "Claimed";
|
|
4381
4381
|
readonly claim: {
|
|
@@ -4979,7 +4979,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
4979
4979
|
invalidationMatches: (mapping: any, supplied: import("@yielded/auth/Sessions").SessionInvalidationWindow, policy: import("@yielded/auth/Passkey").PasskeyManagementPolicy) => boolean;
|
|
4980
4980
|
invalidate: (mapping: any, subject: WriteSubject, invalidation: {
|
|
4981
4981
|
readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
|
|
4982
|
-
readonly existingSessions: "immediate" | "original-absolute-expiry";
|
|
4982
|
+
readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
|
|
4983
4983
|
readonly maximumExposureMillis: number;
|
|
4984
4984
|
readonly oldAuthenticationEvidence: "rejected";
|
|
4985
4985
|
}) => import("effect/Effect").Effect<string & import("effect/Brand").Brand<"effect-auth/SecurityRevision">, import("@yielded/auth/Passkey").PasskeyUnavailable | import("effect/PlatformError").PlatformError, import("effect/Crypto").Crypto | CurrentPasskeyTransaction>;
|
|
@@ -5635,7 +5635,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
5635
5635
|
readonly nowMillis: number;
|
|
5636
5636
|
}) => import("effect/Effect").Effect<{
|
|
5637
5637
|
readonly _tag: "Rejected";
|
|
5638
|
-
|
|
5638
|
+
credential?: never;
|
|
5639
5639
|
} | {
|
|
5640
5640
|
readonly _tag: "Enrolled";
|
|
5641
5641
|
readonly credential: {
|
|
@@ -5706,20 +5706,19 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
5706
5706
|
readonly _tag: "Rejected";
|
|
5707
5707
|
readonly decision?: never;
|
|
5708
5708
|
} | {
|
|
5709
|
-
readonly credential?: never;
|
|
5710
5709
|
readonly _tag: "Replay";
|
|
5711
5710
|
readonly result: {
|
|
5712
5711
|
readonly credentialId: string;
|
|
5713
5712
|
readonly invalidation: {
|
|
5714
5713
|
readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
|
|
5715
|
-
readonly existingSessions: "immediate" | "original-absolute-expiry";
|
|
5714
|
+
readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
|
|
5716
5715
|
readonly maximumExposureMillis: number;
|
|
5717
5716
|
readonly oldAuthenticationEvidence: "rejected";
|
|
5718
5717
|
};
|
|
5719
5718
|
readonly replayed: true;
|
|
5720
5719
|
};
|
|
5720
|
+
credential?: never;
|
|
5721
5721
|
} | {
|
|
5722
|
-
readonly result?: never;
|
|
5723
5722
|
readonly _tag: "Target";
|
|
5724
5723
|
readonly credential: {
|
|
5725
5724
|
readonly credentialId: string;
|
|
@@ -5757,6 +5756,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
5757
5756
|
readonly counter: number;
|
|
5758
5757
|
readonly maximumCounter: number;
|
|
5759
5758
|
};
|
|
5759
|
+
result?: never;
|
|
5760
5760
|
}, import("@yielded/auth/Passkey").PasskeyUnavailable | import("effect/PlatformError").PlatformError, import("effect/Crypto").Crypto | CurrentPasskeyTransaction>;
|
|
5761
5761
|
removeCredential: (mapping: any, input: {
|
|
5762
5762
|
readonly moduleId: string;
|
|
@@ -5777,15 +5777,15 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
5777
5777
|
readonly credentialId: string;
|
|
5778
5778
|
readonly invalidation: {
|
|
5779
5779
|
readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
|
|
5780
|
-
readonly existingSessions: "immediate" | "original-absolute-expiry";
|
|
5780
|
+
readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
|
|
5781
5781
|
readonly maximumExposureMillis: number;
|
|
5782
5782
|
readonly oldAuthenticationEvidence: "rejected";
|
|
5783
5783
|
};
|
|
5784
5784
|
readonly replayed: true;
|
|
5785
5785
|
};
|
|
5786
5786
|
} | {
|
|
5787
|
-
readonly result?: never;
|
|
5788
5787
|
readonly _tag: "LastSignInMethod";
|
|
5788
|
+
result?: never;
|
|
5789
5789
|
} | {
|
|
5790
5790
|
readonly _tag: "Removed";
|
|
5791
5791
|
readonly result: {
|
|
@@ -5793,7 +5793,7 @@ export declare const makePasskeyKernel: (operations: QueryOperations) => {
|
|
|
5793
5793
|
replayed: boolean;
|
|
5794
5794
|
invalidation: {
|
|
5795
5795
|
readonly trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
|
|
5796
|
-
readonly existingSessions: "immediate" | "original-absolute-expiry";
|
|
5796
|
+
readonly existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
|
|
5797
5797
|
readonly maximumExposureMillis: number;
|
|
5798
5798
|
readonly oldAuthenticationEvidence: "rejected";
|
|
5799
5799
|
};
|
|
@@ -3,10 +3,9 @@ import "./models/password-model.mjs";
|
|
|
3
3
|
import "./native-sql-table.mjs";
|
|
4
4
|
import "./password-kernel.mjs";
|
|
5
5
|
import { Effect, Option, Redacted } from "effect";
|
|
6
|
-
import {
|
|
7
|
-
import { PasswordCredentialSnapshot, PasswordUnavailable, PreparePasswordCommit } from "@yielded/auth/Password";
|
|
6
|
+
import { EncodedPasswordHash, PasswordCredentialSnapshot, PasswordUnavailable } from "@yielded/auth/Password";
|
|
8
7
|
import { LoginIdentifier } from "@yielded/auth/Identity";
|
|
9
|
-
//#region src/internal/password-
|
|
8
|
+
//#region src/internal/password-credentials.d.ts
|
|
10
9
|
export declare const samePasswordCredentialSnapshot: (a: PasswordCredentialSnapshot, b: PasswordCredentialSnapshot) => boolean;
|
|
11
10
|
//#endregion
|
|
12
|
-
//# sourceMappingURL=password-
|
|
11
|
+
//# sourceMappingURL=password-credentials.d.mts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { sqlMapping } from "./native-sql-table.mjs";
|
|
2
|
+
import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
3
|
+
import { Effect, Option, Redacted, Schema } from "effect";
|
|
4
|
+
import { CurrentCommitJournal, hasCommitScope } from "@yielded/auth/Hooks";
|
|
5
|
+
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
6
|
+
import { EncodedPasswordHash, PasswordUnavailable, snapshotPasswordCredential } from "@yielded/auth/Password";
|
|
7
|
+
//#region src/internal/password-credentials.ts
|
|
8
|
+
const unavailable = () => PasswordUnavailable.make({});
|
|
9
|
+
const failure = (effect) => reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(Effect.mapError(unavailable));
|
|
10
|
+
const samePasswordCredentialSnapshot = (a, b) => a.moduleId === b.moduleId && a.revision.subjectId === b.revision.subjectId && a.revision.securityRevision === b.revision.securityRevision && a.revision.credentials.length === b.revision.credentials.length && new Set(a.revision.credentials.map((entry) => entry.credentialId)).size === a.revision.credentials.length && new Set(b.revision.credentials.map((entry) => entry.credentialId)).size === b.revision.credentials.length && a.revision.credentials.every((entry) => b.revision.credentials.some((other) => entry.credentialId === other.credentialId && entry.revision === other.revision)) && a.credentialId === b.credentialId && a.credentialRevision === b.credentialRevision && a.verifierVersion === b.verifierVersion && Redacted.value(a.verifier) === Redacted.value(b.verifier) && a.normalization === b.normalization && a.identifier.namespace === b.identifier.namespace && a.identifier.value === b.identifier.value && a.identifierBindingRevision === b.identifierBindingRevision && a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;
|
|
11
|
+
/** Lookup is read-only; rehash is one conditional maintenance write, never authentication. */
|
|
12
|
+
const makePasswordCredentials = Effect.fnUntraced(function* (table, mapping, options) {
|
|
13
|
+
const sql = (yield* NativeDatabase).$client.withoutTransforms();
|
|
14
|
+
const parent = yield* Effect.serviceOption(CurrentCommitJournal);
|
|
15
|
+
const [s, i, c] = yield* sqlMapping(() => [
|
|
16
|
+
table(mapping.subject.table),
|
|
17
|
+
table(mapping.identifier.table),
|
|
18
|
+
table(mapping.credential.table)
|
|
19
|
+
]).pipe(failure);
|
|
20
|
+
const standalone = Effect.gen(function* () {
|
|
21
|
+
if (options.coordinated) {
|
|
22
|
+
const current = yield* Effect.serviceOption(CurrentCommitJournal);
|
|
23
|
+
if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value) return yield* unavailable();
|
|
24
|
+
} else {
|
|
25
|
+
if (yield* hasCommitScope) return yield* unavailable();
|
|
26
|
+
yield* options.standaloneGuard;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const allocate = (asynchronous, sync) => {
|
|
30
|
+
if (options.mode !== "synchronous" && asynchronous !== void 0) return asynchronous;
|
|
31
|
+
return sync === void 0 ? Effect.fail(unavailable()) : Effect.try({
|
|
32
|
+
try: sync,
|
|
33
|
+
catch: unavailable
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const snapshot = Effect.fnUntraced(function* (nativeId, moduleId, identifier) {
|
|
37
|
+
const subjectTable = s.as("password_subject");
|
|
38
|
+
const identifierTable = i.as("password_identifier");
|
|
39
|
+
const credentialTable = c.as("password_credential");
|
|
40
|
+
const rows = yield* sqlMapping(() => sql`select ${subjectTable.fields("subject_")}, ${identifierTable.fields("identifier_")}, ${credentialTable.fields("credential_")}
|
|
41
|
+
from ${subjectTable.name}
|
|
42
|
+
inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}
|
|
43
|
+
and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}
|
|
44
|
+
and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}
|
|
45
|
+
inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}
|
|
46
|
+
and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}
|
|
47
|
+
where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}
|
|
48
|
+
`).pipe(Effect.flatten);
|
|
49
|
+
if (rows.length !== 1) return void 0;
|
|
50
|
+
const [subject, identifierRow, credential] = yield* sqlMapping(() => [
|
|
51
|
+
s.decode(rows[0], "subject_"),
|
|
52
|
+
i.decode(rows[0], "identifier_"),
|
|
53
|
+
c.decode(rows[0], "credential_")
|
|
54
|
+
]);
|
|
55
|
+
if (yield* sqlMapping(() => !mapping.subject.isActiveStatus(subject[mapping.subject.status]) || !mapping.identifier.isCurrent(identifierRow) || !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) || !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) || !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]))) return void 0;
|
|
56
|
+
return yield* sqlMapping(() => mapping.credential.decode({
|
|
57
|
+
moduleId,
|
|
58
|
+
subject,
|
|
59
|
+
identifier: identifierRow,
|
|
60
|
+
credential
|
|
61
|
+
})).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
findCredential: (uncaptured) => Effect.gen(function* () {
|
|
65
|
+
const input = {
|
|
66
|
+
...uncaptured,
|
|
67
|
+
identifier: { ...uncaptured.identifier }
|
|
68
|
+
};
|
|
69
|
+
yield* standalone;
|
|
70
|
+
const identifiers = yield* sqlMapping(() => sql`select ${i.fields("i_")} from ${i.name}
|
|
71
|
+
where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}
|
|
72
|
+
and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`).pipe(Effect.flatten);
|
|
73
|
+
const identifier = identifiers.length === 1 ? yield* sqlMapping(() => i.decode(identifiers[0], "i_")) : void 0;
|
|
74
|
+
if (identifier === void 0 || !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))) return Option.none();
|
|
75
|
+
const credential = yield* snapshot(identifier[mapping.identifier.subjectId], input.moduleId, input.identifier);
|
|
76
|
+
return credential === void 0 || input.subjectId !== void 0 && credential.revision.subjectId !== input.subjectId ? Option.none() : Option.some(credential);
|
|
77
|
+
}).pipe(failure),
|
|
78
|
+
rehashIfCurrent: (input) => Effect.gen(function* () {
|
|
79
|
+
const nextVerifier = Redacted.make(yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)));
|
|
80
|
+
const credential = yield* snapshotPasswordCredential(input.credential);
|
|
81
|
+
yield* standalone;
|
|
82
|
+
const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);
|
|
83
|
+
const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
|
|
84
|
+
if (version === credential.verifierVersion) return yield* unavailable();
|
|
85
|
+
yield* sqlMapping(() => {
|
|
86
|
+
const encoded = mapping.credential.encodeVerifier(nextVerifier, version);
|
|
87
|
+
const values = {
|
|
88
|
+
[mapping.credential.verifier]: encoded[mapping.credential.verifier],
|
|
89
|
+
[mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion]
|
|
90
|
+
};
|
|
91
|
+
if (Object.values(values).some((value) => value === void 0)) throw unavailable();
|
|
92
|
+
const exact = sql.and(Object.entries({
|
|
93
|
+
[mapping.credential.moduleId]: credential.moduleId,
|
|
94
|
+
[mapping.credential.subjectId]: nativeId,
|
|
95
|
+
[mapping.credential.credentialId]: credential.credentialId,
|
|
96
|
+
[mapping.credential.credentialRevision]: credential.credentialRevision,
|
|
97
|
+
[mapping.credential.verifierVersion]: credential.verifierVersion,
|
|
98
|
+
[mapping.credential.verifier]: Redacted.value(credential.verifier),
|
|
99
|
+
[mapping.credential.normalization]: credential.normalization
|
|
100
|
+
}).map(([key, value]) => {
|
|
101
|
+
const column = c.column(key), bound = c.value(key, value);
|
|
102
|
+
if (typeof value !== "string") return sql`${column} = ${bound}`;
|
|
103
|
+
return sql.onDialectOrElse({
|
|
104
|
+
mysql: () => sql`binary ${column} = binary ${bound}`,
|
|
105
|
+
pg: () => sql`convert_to(cast(${column} as text), 'UTF8') = convert_to(cast(${bound} as text), 'UTF8')`,
|
|
106
|
+
orElse: () => sql`cast(${column} as blob) = cast(${bound} as blob)`
|
|
107
|
+
});
|
|
108
|
+
}));
|
|
109
|
+
return sql`${c.update(values)}
|
|
110
|
+
where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, credential.moduleId)}
|
|
111
|
+
and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, nativeId)}
|
|
112
|
+
and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, credential.credentialId)}
|
|
113
|
+
and ${exact}`;
|
|
114
|
+
}).pipe(Effect.flatten);
|
|
115
|
+
}).pipe(failure)
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
//#endregion
|
|
119
|
+
export { makePasswordCredentials, samePasswordCredentialSnapshot };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=password-credentials.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-credentials.mjs","names":[],"sources":["../../src/internal/password-credentials.ts"],"sourcesContent":["import { CurrentCommitJournal, hasCommitScope } from \"@yielded/auth/Hooks\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n EncodedPasswordHash,\n type PasswordPersistence,\n PasswordUnavailable,\n snapshotPasswordCredential,\n type PasswordCredentialSnapshot,\n} from \"@yielded/auth/Password\";\nimport { reportPersistenceFailure } from \"@yielded/auth/Persistence\";\nimport { Effect, Option, Redacted, Schema } from \"effect\";\n\nimport type { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport { sqlMapping, type SqlTable } from \"./native-sql-table\";\nimport type { PasswordSqlConfiguration } from \"./password-kernel\";\nimport { NativeDatabase } from \"./transaction-kernel\";\n\ntype Row = Readonly<Record<string, unknown>>;\nconst unavailable = () => PasswordUnavailable.make({});\n\nconst failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(\n Effect.mapError(unavailable),\n );\n\nexport const samePasswordCredentialSnapshot = (\n a: PasswordCredentialSnapshot,\n b: PasswordCredentialSnapshot,\n) =>\n a.moduleId === b.moduleId &&\n a.revision.subjectId === b.revision.subjectId &&\n a.revision.securityRevision === b.revision.securityRevision &&\n a.revision.credentials.length === b.revision.credentials.length &&\n new Set(a.revision.credentials.map((entry) => entry.credentialId)).size ===\n a.revision.credentials.length &&\n new Set(b.revision.credentials.map((entry) => entry.credentialId)).size ===\n b.revision.credentials.length &&\n a.revision.credentials.every((entry) =>\n b.revision.credentials.some(\n (other) => entry.credentialId === other.credentialId && entry.revision === other.revision,\n ),\n ) &&\n a.credentialId === b.credentialId &&\n a.credentialRevision === b.credentialRevision &&\n a.verifierVersion === b.verifierVersion &&\n Redacted.value(a.verifier) === Redacted.value(b.verifier) &&\n a.normalization === b.normalization &&\n a.identifier.namespace === b.identifier.namespace &&\n a.identifier.value === b.identifier.value &&\n a.identifierBindingRevision === b.identifierBindingRevision &&\n a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;\n\n/** Lookup is read-only; rehash is one conditional maintenance write, never authentication. */\nexport const makePasswordCredentials = Effect.fnUntraced(function* (\n table: (table: object) => SqlTable,\n mapping: AnyPasswordPersistenceMapping,\n options: PasswordSqlConfiguration,\n): Effect.fn.Return<\n Pick<PasswordPersistence[\"Service\"], \"findCredential\" | \"rehashIfCurrent\">,\n PasswordUnavailable,\n NativeDatabase\n> {\n const sql = (yield* NativeDatabase).$client.withoutTransforms();\n const parent = yield* Effect.serviceOption(CurrentCommitJournal);\n\n const [s, i, c] = yield* sqlMapping(\n () =>\n [\n table(mapping.subject.table),\n table(mapping.identifier.table),\n table(mapping.credential.table),\n ] as const,\n ).pipe(failure);\n\n const standalone = Effect.gen(function* () {\n if (options.coordinated) {\n const current = yield* Effect.serviceOption(CurrentCommitJournal);\n\n if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value)\n return yield* unavailable();\n } else {\n if (yield* hasCommitScope) return yield* unavailable();\n yield* options.standaloneGuard;\n }\n });\n\n const allocate = <A>(\n asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,\n sync: (() => A) | undefined,\n ) => {\n if (options.mode !== \"synchronous\" && asynchronous !== undefined) return asynchronous;\n\n return sync === undefined\n ? Effect.fail(unavailable())\n : Effect.try({ try: sync, catch: unavailable });\n };\n\n // Bind each native ID through its own column codec. Physical encodings of the\n // same application ID need not match across subject, identifier and password.\n const snapshot = Effect.fnUntraced(function* (\n nativeId: unknown,\n moduleId: string,\n identifier: LoginIdentifier,\n ) {\n const subjectTable = s.as(\"password_subject\");\n const identifierTable = i.as(\"password_identifier\");\n const credentialTable = c.as(\"password_credential\");\n\n const rows = yield* sqlMapping(\n () =>\n sql<Row>`select ${subjectTable.fields(\"subject_\")}, ${identifierTable.fields(\"identifier_\")}, ${credentialTable.fields(\"credential_\")}\n from ${subjectTable.name}\n inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}\n and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}\n and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}\n inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}\n and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}\n where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}\n `,\n ).pipe(Effect.flatten);\n\n if (rows.length !== 1) return undefined;\n\n const [subject, identifierRow, credential] = yield* sqlMapping(\n () =>\n [\n s.decode(rows[0]!, \"subject_\"),\n i.decode(rows[0]!, \"identifier_\"),\n c.decode(rows[0]!, \"credential_\"),\n ] as const,\n );\n\n if (\n yield* sqlMapping(\n () =>\n !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||\n !mapping.identifier.isCurrent(identifierRow) ||\n !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) ||\n !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) ||\n !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]),\n )\n )\n return undefined;\n\n const captured = yield* sqlMapping(() =>\n mapping.credential.decode({ moduleId, subject, identifier: identifierRow, credential }),\n ).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));\n\n return captured;\n });\n\n return {\n findCredential: (uncaptured) =>\n Effect.gen(function* () {\n const input = { ...uncaptured, identifier: { ...uncaptured.identifier } };\n\n yield* standalone;\n\n const identifiers = yield* sqlMapping(\n () => sql<Row>`select ${i.fields(\"i_\")} from ${i.name}\n where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}\n and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`,\n ).pipe(Effect.flatten);\n\n const identifier =\n identifiers.length === 1\n ? yield* sqlMapping(() => i.decode(identifiers[0]!, \"i_\"))\n : undefined;\n\n if (\n identifier === undefined ||\n !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))\n )\n return Option.none();\n\n const credential = yield* snapshot(\n identifier[mapping.identifier.subjectId],\n input.moduleId,\n input.identifier,\n );\n\n return credential === undefined ||\n (input.subjectId !== undefined && credential.revision.subjectId !== input.subjectId)\n ? Option.none()\n : Option.some(credential);\n }).pipe(failure),\n rehashIfCurrent: (input) =>\n Effect.gen(function* () {\n const nextVerifier = Redacted.make(\n yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)),\n );\n\n const credential = yield* snapshotPasswordCredential(input.credential);\n\n yield* standalone;\n const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);\n const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);\n\n if (version === credential.verifierVersion) return yield* unavailable();\n yield* sqlMapping(() => {\n const encoded = mapping.credential.encodeVerifier(nextVerifier, version);\n\n // A custom encoder cannot turn maintenance into a semantic credential change.\n const values = {\n [mapping.credential.verifier]: encoded[mapping.credential.verifier],\n [mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion],\n };\n\n if (Object.values(values).some((value) => value === undefined)) throw unavailable();\n\n // Ordinary identity predicates retain index use; the full CAS ignores text collation.\n const exact = sql.and(\n Object.entries({\n [mapping.credential.moduleId]: credential.moduleId,\n [mapping.credential.subjectId]: nativeId,\n [mapping.credential.credentialId]: credential.credentialId,\n [mapping.credential.credentialRevision]: credential.credentialRevision,\n [mapping.credential.verifierVersion]: credential.verifierVersion,\n [mapping.credential.verifier]: Redacted.value(credential.verifier),\n [mapping.credential.normalization]: credential.normalization,\n }).map(([key, value]) => {\n const column = c.column(key),\n bound = c.value(key, value);\n\n if (typeof value !== \"string\") return sql`${column} = ${bound}`;\n\n return sql.onDialectOrElse({\n mysql: () => sql`binary ${column} = binary ${bound}`,\n pg: () =>\n sql`convert_to(cast(${column} as text), 'UTF8') = convert_to(cast(${bound} as text), 'UTF8')`,\n orElse: () => sql`cast(${column} as blob) = cast(${bound} as blob)`,\n });\n }),\n );\n\n return sql`${c.update(values)}\n where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, credential.moduleId)}\n and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, nativeId)}\n and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, credential.credentialId)}\n and ${exact}`;\n }).pipe(Effect.flatten);\n }).pipe(failure),\n } satisfies Pick<PasswordPersistence[\"Service\"], \"findCredential\" | \"rehashIfCurrent\">;\n});\n"],"mappings":";;;;;;;AAmBA,MAAM,oBAAoB,oBAAoB,KAAK,CAAC,CAAC;AAErD,MAAM,WAAoB,WACxB,yBAAyB,QAAQ,OAAO,GAAG,mBAAmB,CAAC,CAAC,CAAC,KAC/D,OAAO,SAAS,WAAW,CAC7B;AAEF,MAAa,kCACX,GACA,MAEA,EAAE,aAAa,EAAE,YACjB,EAAE,SAAS,cAAc,EAAE,SAAS,aACpC,EAAE,SAAS,qBAAqB,EAAE,SAAS,oBAC3C,EAAE,SAAS,YAAY,WAAW,EAAE,SAAS,YAAY,UACzD,IAAI,IAAI,EAAE,SAAS,YAAY,KAAK,UAAU,MAAM,YAAY,CAAC,CAAC,CAAC,SACjE,EAAE,SAAS,YAAY,UACzB,IAAI,IAAI,EAAE,SAAS,YAAY,KAAK,UAAU,MAAM,YAAY,CAAC,CAAC,CAAC,SACjE,EAAE,SAAS,YAAY,UACzB,EAAE,SAAS,YAAY,OAAO,UAC5B,EAAE,SAAS,YAAY,MACpB,UAAU,MAAM,iBAAiB,MAAM,gBAAgB,MAAM,aAAa,MAAM,QACnF,CACF,KACA,EAAE,iBAAiB,EAAE,gBACrB,EAAE,uBAAuB,EAAE,sBAC3B,EAAE,oBAAoB,EAAE,mBACxB,SAAS,MAAM,EAAE,QAAQ,MAAM,SAAS,MAAM,EAAE,QAAQ,KACxD,EAAE,kBAAkB,EAAE,iBACtB,EAAE,WAAW,cAAc,EAAE,WAAW,aACxC,EAAE,WAAW,UAAU,EAAE,WAAW,SACpC,EAAE,8BAA8B,EAAE,6BAClC,EAAE,+BAA+B,EAAE;;AAGrC,MAAa,0BAA0B,OAAO,WAAW,WACvD,OACA,SACA,SAKA;CACA,MAAM,OAAO,OAAO,eAAA,CAAgB,QAAQ,kBAAkB;CAC9D,MAAM,SAAS,OAAO,OAAO,cAAc,oBAAoB;CAE/D,MAAM,CAAC,GAAG,GAAG,KAAK,OAAO,iBAErB;EACE,MAAM,QAAQ,QAAQ,KAAK;EAC3B,MAAM,QAAQ,WAAW,KAAK;EAC9B,MAAM,QAAQ,WAAW,KAAK;CAChC,CACJ,CAAC,CAAC,KAAK,OAAO;CAEd,MAAM,aAAa,OAAO,IAAI,aAAa;EACzC,IAAI,QAAQ,aAAa;GACvB,MAAM,UAAU,OAAO,OAAO,cAAc,oBAAoB;GAEhE,IAAI,OAAO,OAAO,MAAM,KAAK,OAAO,OAAO,OAAO,KAAK,OAAO,UAAU,QAAQ,OAC9E,OAAO,OAAO,YAAY;EAC9B,OAAO;GACL,IAAI,OAAO,gBAAgB,OAAO,OAAO,YAAY;GACrD,OAAO,QAAQ;EACjB;CACF,CAAC;CAED,MAAM,YACJ,cACA,SACG;EACH,IAAI,QAAQ,SAAS,iBAAiB,iBAAiB,KAAA,GAAW,OAAO;EAEzE,OAAO,SAAS,KAAA,IACZ,OAAO,KAAK,YAAY,CAAC,IACzB,OAAO,IAAI;GAAE,KAAK;GAAM,OAAO;EAAY,CAAC;CAClD;CAIA,MAAM,WAAW,OAAO,WAAW,WACjC,UACA,UACA,YACA;EACA,MAAM,eAAe,EAAE,GAAG,kBAAkB;EAC5C,MAAM,kBAAkB,EAAE,GAAG,qBAAqB;EAClD,MAAM,kBAAkB,EAAE,GAAG,qBAAqB;EAElD,MAAM,OAAO,OAAO,iBAEhB,GAAQ,UAAU,aAAa,OAAO,UAAU,EAAE,IAAI,gBAAgB,OAAO,aAAa,EAAE,IAAI,gBAAgB,OAAO,aAAa,EAAE;aACjI,aAAa,KAAK;mBACZ,gBAAgB,KAAK,MAAM,gBAAgB,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,WAAW,WAAW,SAAS,EAAE;cACpK,gBAAgB,OAAO,QAAQ,WAAW,KAAK,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,OAAO,WAAW,KAAK,EAAE;cACxH,gBAAgB,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,WAAW,QAAQ,EAAE;mBACnH,gBAAgB,KAAK,MAAM,gBAAgB,OAAO,QAAQ,WAAW,QAAQ,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,UAAU,QAAQ,EAAE;cACtJ,gBAAgB,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,gBAAgB,MAAM,QAAQ,WAAW,WAAW,QAAQ,EAAE;cACxH,aAAa,OAAO,QAAQ,QAAQ,EAAE,EAAE,KAAK,aAAa,MAAM,QAAQ,QAAQ,IAAI,QAAQ,EAAE;OAExG,CAAC,CAAC,KAAK,OAAO,OAAO;EAErB,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;EAE9B,MAAM,CAAC,SAAS,eAAe,cAAc,OAAO,iBAEhD;GACE,EAAE,OAAO,KAAK,IAAK,UAAU;GAC7B,EAAE,OAAO,KAAK,IAAK,aAAa;GAChC,EAAE,OAAO,KAAK,IAAK,aAAa;EAClC,CACJ;EAEA,IACE,OAAO,iBAEH,CAAC,QAAQ,QAAQ,eAAe,QAAQ,QAAQ,QAAQ,OAAO,KAC/D,CAAC,QAAQ,WAAW,UAAU,aAAa,KAC3C,CAAC,QAAQ,UAAU,OAAO,UAAU,cAAc,QAAQ,WAAW,UAAU,KAC/E,CAAC,QAAQ,UAAU,OAAO,UAAU,QAAQ,QAAQ,QAAQ,GAAG,KAC/D,CAAC,QAAQ,UAAU,OAAO,UAAU,WAAW,QAAQ,WAAW,UAAU,CAChF,GAEA,OAAO,KAAA;EAMT,OAAO,OAJiB,iBACtB,QAAQ,WAAW,OAAO;GAAE;GAAU;GAAS,YAAY;GAAe;EAAW,CAAC,CACxF,CAAC,CAAC,KAAK,OAAO,SAAS,OAAO,QAAQ,0BAA0B,CAAC;CAGnE,CAAC;CAED,OAAO;EACL,iBAAiB,eACf,OAAO,IAAI,aAAa;GACtB,MAAM,QAAQ;IAAE,GAAG;IAAY,YAAY,EAAE,GAAG,WAAW,WAAW;GAAE;GAExE,OAAO;GAEP,MAAM,cAAc,OAAO,iBACnB,GAAQ,UAAU,EAAE,OAAO,IAAI,EAAE,QAAQ,EAAE,KAAK;kBAC9C,EAAE,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,WAAW,MAAM,WAAW,SAAS,EAAE;kBAC9G,EAAE,OAAO,QAAQ,WAAW,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,OAAO,MAAM,WAAW,KAAK,GAC1G,CAAC,CAAC,KAAK,OAAO,OAAO;GAErB,MAAM,aACJ,YAAY,WAAW,IACnB,OAAO,iBAAiB,EAAE,OAAO,YAAY,IAAK,IAAI,CAAC,IACvD,KAAA;GAEN,IACE,eAAe,KAAA,KACf,EAAE,OAAO,iBAAiB,QAAQ,WAAW,UAAU,UAAU,CAAC,IAElE,OAAO,OAAO,KAAK;GAErB,MAAM,aAAa,OAAO,SACxB,WAAW,QAAQ,WAAW,YAC9B,MAAM,UACN,MAAM,UACR;GAEA,OAAO,eAAe,KAAA,KACnB,MAAM,cAAc,KAAA,KAAa,WAAW,SAAS,cAAc,MAAM,YACxE,OAAO,KAAK,IACZ,OAAO,KAAK,UAAU;EAC5B,CAAC,CAAC,CAAC,KAAK,OAAO;EACjB,kBAAkB,UAChB,OAAO,IAAI,aAAa;GACtB,MAAM,eAAe,SAAS,KAC5B,OAAO,OAAO,aAAa,mBAAmB,CAAC,CAAC,SAAS,MAAM,MAAM,YAAY,CAAC,CACpF;GAEA,MAAM,aAAa,OAAO,2BAA2B,MAAM,UAAU;GAErE,OAAO;GACP,MAAM,WAAW,OAAO,QAAQ,UAAU,SAAS,WAAW,SAAS,SAAS;GAChF,MAAM,UAAU,OAAO,SAAS,QAAQ,kBAAkB,QAAQ,oBAAoB;GAEtF,IAAI,YAAY,WAAW,iBAAiB,OAAO,OAAO,YAAY;GACtE,OAAO,iBAAiB;IACtB,MAAM,UAAU,QAAQ,WAAW,eAAe,cAAc,OAAO;IAGvE,MAAM,SAAS;MACZ,QAAQ,WAAW,WAAW,QAAQ,QAAQ,WAAW;MACzD,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,WAAW;IACnE;IAEA,IAAI,OAAO,OAAO,MAAM,CAAC,CAAC,MAAM,UAAU,UAAU,KAAA,CAAS,GAAG,MAAM,YAAY;IAGlF,MAAM,QAAQ,IAAI,IAChB,OAAO,QAAQ;MACZ,QAAQ,WAAW,WAAW,WAAW;MACzC,QAAQ,WAAW,YAAY;MAC/B,QAAQ,WAAW,eAAe,WAAW;MAC7C,QAAQ,WAAW,qBAAqB,WAAW;MACnD,QAAQ,WAAW,kBAAkB,WAAW;MAChD,QAAQ,WAAW,WAAW,SAAS,MAAM,WAAW,QAAQ;MAChE,QAAQ,WAAW,gBAAgB,WAAW;IACjD,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW;KACvB,MAAM,SAAS,EAAE,OAAO,GAAG,GACzB,QAAQ,EAAE,MAAM,KAAK,KAAK;KAE5B,IAAI,OAAO,UAAU,UAAU,OAAO,GAAG,GAAG,OAAO,KAAK;KAExD,OAAO,IAAI,gBAAgB;MACzB,aAAa,GAAG,UAAU,OAAO,YAAY;MAC7C,UACE,GAAG,mBAAmB,OAAO,uCAAuC,MAAM;MAC5E,cAAc,GAAG,QAAQ,OAAO,mBAAmB,MAAM;KAC3D,CAAC;IACH,CAAC,CACH;IAEA,OAAO,GAAG,GAAG,EAAE,OAAO,MAAM,EAAE;kBACtB,EAAE,OAAO,QAAQ,WAAW,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,UAAU,WAAW,QAAQ,EAAE;kBACrG,EAAE,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,WAAW,QAAQ,EAAE;kBAC5F,EAAE,OAAO,QAAQ,WAAW,YAAY,EAAE,KAAK,EAAE,MAAM,QAAQ,WAAW,cAAc,WAAW,YAAY,EAAE;kBACjH;GACV,CAAC,CAAC,CAAC,KAAK,OAAO,OAAO;EACxB,CAAC,CAAC,CAAC,KAAK,OAAO;CACnB;AACF,CAAC"}
|
|
@@ -4,7 +4,7 @@ import { QueryFailure, QueryOperations, SqlColumn, SqlFragment } from "./query-o
|
|
|
4
4
|
import { AnyPasswordPersistenceMapping } from "./models/password-model.mjs";
|
|
5
5
|
import { CurrentProofSql, ProofSqlConfiguration, makeProofKernel } from "./proof-kernel.mjs";
|
|
6
6
|
import { NativeSqlTables } from "./native-sql-table.mjs";
|
|
7
|
-
import { samePasswordCredentialSnapshot } from "./password-
|
|
7
|
+
import { samePasswordCredentialSnapshot } from "./password-credentials.mjs";
|
|
8
8
|
import { Context, DateTime, Effect, Option, Redacted } from "effect";
|
|
9
9
|
import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, PreparedCommit } from "@yielded/auth/Hooks";
|
|
10
10
|
import { AuthenticationEvidence, AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
|
|
@@ -52,24 +52,15 @@ export interface PasswordSqlConfiguration {
|
|
|
52
52
|
type Database = PasswordSqlDatabase;
|
|
53
53
|
export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFragment, Column extends SqlColumn = SqlColumn>(operations: QueryOperations<Fragment, Column>, proofs: Pick<ReturnType<typeof makeProofKernel>, "completeProofPlanIn">, nativeTables?: (client: SqlClient, database: object) => NativeSqlTables) => {
|
|
54
54
|
makeSqlPasswordPersistence: (mapping: AnyPasswordPersistenceMapping<Fragment>, configuration: PasswordSqlConfiguration) => Effect.Effect<{
|
|
55
|
-
readonly
|
|
55
|
+
readonly findCredential: (input: {
|
|
56
56
|
readonly moduleId: string;
|
|
57
|
-
readonly action: "sign-in" | "change";
|
|
58
57
|
readonly identifier: LoginIdentifier;
|
|
59
58
|
readonly subjectId?: import("@yielded/auth/Schema").SubjectId;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
readonly
|
|
64
|
-
|
|
65
|
-
readonly captured?: import("@yielded/auth/Password").PasswordCredentialSnapshot;
|
|
66
|
-
readonly outcome: import("@yielded/auth/Password").PasswordAttemptDecision;
|
|
67
|
-
readonly rehash?: {
|
|
68
|
-
readonly expectedVersion: SecurityRevision;
|
|
69
|
-
readonly expectedVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
|
|
70
|
-
readonly nextVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
|
|
71
|
-
};
|
|
72
|
-
}, prepare: import("@yielded/auth/Password").PreparePasswordCommit<import("@yielded/auth/Password").PasswordAttemptDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
|
|
59
|
+
}) => Effect.Effect<Option.Option<import("@yielded/auth/Password").PasswordCredentialSnapshot>, PasswordUnavailable>;
|
|
60
|
+
readonly rehashIfCurrent: (input: {
|
|
61
|
+
readonly credential: import("@yielded/auth/Password").PasswordCredentialSnapshot;
|
|
62
|
+
readonly nextVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
|
|
63
|
+
}) => Effect.Effect<void, PasswordUnavailable>;
|
|
73
64
|
readonly readForSubject: (input: {
|
|
74
65
|
readonly moduleId: string;
|
|
75
66
|
readonly subjectId: import("@yielded/auth/Schema").SubjectId;
|
|
@@ -84,13 +75,6 @@ export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFrag
|
|
|
84
75
|
readonly resetWithProof: <A>(input: PasswordMutationInput & {
|
|
85
76
|
readonly completion: ProofCompletionPlan;
|
|
86
77
|
}, prepare: import("@yielded/auth/Password").PreparePasswordCommit<PasswordMutationDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
|
|
87
|
-
readonly cleanupAttempts: <A>(input: {
|
|
88
|
-
readonly moduleId: string;
|
|
89
|
-
readonly limit: number;
|
|
90
|
-
}, prepare: import("@yielded/auth/Password").PreparePasswordCommit<{
|
|
91
|
-
readonly removed: number;
|
|
92
|
-
readonly hasMore: boolean;
|
|
93
|
-
}, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
|
|
94
78
|
}, PasswordUnavailable, CurrentPasswordSql | LifecycleHooks | NativeDatabase>;
|
|
95
79
|
passwordSqlKernel: {
|
|
96
80
|
allocate: <A>(mode: PasswordSqlConfiguration["mode"], asyncValue: Effect.Effect<A, PersistenceMappingError> | undefined, syncValue: (() => A) | undefined) => Effect.Effect<A, PasswordUnavailable | PersistenceMappingError>;
|
|
@@ -133,7 +117,7 @@ export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFrag
|
|
|
133
117
|
}>;
|
|
134
118
|
invalidation: Readonly<{
|
|
135
119
|
trigger: "all-session-revocation" | "credential-change" | "factor-reset" | "identifier-change" | "password-reset" | "subject-delete" | "subject-disable";
|
|
136
|
-
existingSessions: "immediate" | "original-absolute-expiry";
|
|
120
|
+
existingSessions: "cache-expiry" | "immediate" | "original-absolute-expiry";
|
|
137
121
|
maximumExposureMillis: number;
|
|
138
122
|
oldAuthenticationEvidence: "rejected";
|
|
139
123
|
}>;
|
|
@@ -4,7 +4,7 @@ import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
|
4
4
|
import { CurrentPasswordPreparedTransaction, PasswordPreparedPostconditions } from "./PasswordPreparedPostconditions.mjs";
|
|
5
5
|
import { CurrentProofSql } from "./proof-kernel.mjs";
|
|
6
6
|
import { requiredPasswordConstraints } from "./models/password-model.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { makePasswordCredentials, samePasswordCredentialSnapshot } from "./password-credentials.mjs";
|
|
8
8
|
import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
|
|
9
9
|
import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
|
|
10
10
|
import { snapshotAuthenticationEvidence } from "@yielded/auth/Sessions";
|
|
@@ -14,7 +14,7 @@ import { PasswordPersistence, PasswordUnavailable, snapshotPasswordCredential, s
|
|
|
14
14
|
//#region src/internal/password-kernel.ts
|
|
15
15
|
var CurrentPasswordSql = class extends Context.Service()("effect-auth/CurrentPasswordSql") {};
|
|
16
16
|
const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeNativeSqlTables(client)) => {
|
|
17
|
-
const { and, eq, inArray,
|
|
17
|
+
const { and, eq, inArray, column, updateValues } = operations;
|
|
18
18
|
const { completeProofPlanIn } = proofs;
|
|
19
19
|
const unavailable = () => PasswordUnavailable.make({});
|
|
20
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()))));
|
|
@@ -47,23 +47,6 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
|
|
|
47
47
|
credentialId: column(mapping.authorityCredential.table, mapping.authorityCredential.credentialId),
|
|
48
48
|
revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision)
|
|
49
49
|
});
|
|
50
|
-
const attemptColumns = (mapping) => ({
|
|
51
|
-
moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
|
|
52
|
-
action: column(mapping.attempt.table, mapping.attempt.action),
|
|
53
|
-
attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
|
|
54
|
-
identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
|
|
55
|
-
identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
|
|
56
|
-
subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
|
|
57
|
-
credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
|
|
58
|
-
securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
|
|
59
|
-
credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
|
|
60
|
-
verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
|
|
61
|
-
identifierBindingRevision: column(mapping.attempt.table, mapping.attempt.identifierBindingRevision),
|
|
62
|
-
admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
|
|
63
|
-
deadline: column(mapping.attempt.table, mapping.attempt.deadline),
|
|
64
|
-
retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
|
|
65
|
-
state: column(mapping.attempt.table, mapping.attempt.state)
|
|
66
|
-
});
|
|
67
50
|
const commandColumns = (mapping) => ({
|
|
68
51
|
moduleId: column(mapping.command.table, mapping.command.moduleId),
|
|
69
52
|
commandId: column(mapping.command.table, mapping.command.commandId),
|
|
@@ -271,7 +254,7 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
|
|
|
271
254
|
const currentRequirement = yield* mapping.subject.decodeActionRequirement(row, action);
|
|
272
255
|
const now = yield* nowMillis;
|
|
273
256
|
if (!evidenceSatisfiedAt(input.authorization.evidence, input.authorization.requirement, now) || !evidenceSatisfiedAt(input.authorization.evidence, currentRequirement, now)) return void 0;
|
|
274
|
-
if (input.invalidation.existingSessions === "immediate" && mapping.sessionInvalidation !== "same-authority-immediate") return void 0;
|
|
257
|
+
if ((input.invalidation.existingSessions === "immediate" || input.invalidation.existingSessions === "cache-expiry") && mapping.sessionInvalidation !== "same-authority-immediate") return void 0;
|
|
275
258
|
return {
|
|
276
259
|
...locked,
|
|
277
260
|
now
|
|
@@ -346,9 +329,9 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
|
|
|
346
329
|
const hooks = yield* LifecycleHooks;
|
|
347
330
|
if (!validConstraints(mapping)) return yield* unavailable();
|
|
348
331
|
const root = yield* NativeDatabase;
|
|
349
|
-
const
|
|
332
|
+
const credentials = yield* makePasswordCredentials(yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(translateFailure), mapping, configuration);
|
|
350
333
|
return PasswordPersistence.of({
|
|
351
|
-
...
|
|
334
|
+
...credentials,
|
|
352
335
|
readForSubject: (input) => safeRead(database, configuration, Effect.gen(function* () {
|
|
353
336
|
const transaction = yield* CurrentPasswordSql;
|
|
354
337
|
const locked = yield* lockSubject(mapping, input.subjectId, false);
|
|
@@ -442,27 +425,6 @@ const makePasswordKernel = (operations, proofs, nativeTables = (client) => makeN
|
|
|
442
425
|
});
|
|
443
426
|
return passwordReceipt ?? prepare("rejected", journal);
|
|
444
427
|
}));
|
|
445
|
-
})).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure),
|
|
446
|
-
cleanupAttempts: (input, prepare) => owned(database, mapping, configuration, Effect.gen(function* () {
|
|
447
|
-
const transaction = yield* CurrentPasswordSql;
|
|
448
|
-
const journal = yield* CurrentCommitJournal;
|
|
449
|
-
const now = yield* nowMillis;
|
|
450
|
-
const nativeNow = mapping.encodeInstant(now);
|
|
451
|
-
const a = attemptColumns(mapping);
|
|
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);
|
|
455
|
-
const selectedAttempts = [];
|
|
456
|
-
for (const candidate of attemptCandidates) {
|
|
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);
|
|
458
|
-
if (locked[0] !== void 0) selectedAttempts.push(locked[0]);
|
|
459
|
-
}
|
|
460
|
-
const prepared = prepare({
|
|
461
|
-
removed: selectedAttempts.length,
|
|
462
|
-
hasMore
|
|
463
|
-
}, journal);
|
|
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)));
|
|
465
|
-
return prepared;
|
|
466
428
|
})).pipe(Effect.provideService(CurrentPasswordSql, database), Effect.provideService(LifecycleHooks, hooks), translateFailure)
|
|
467
429
|
});
|
|
468
430
|
});
|