@yielded/auth-persistence 0.1.0-beta.23 → 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 +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/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 -47
- 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-kernel.d.mts +5 -5
- 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 +4 -42
- 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/models/oauth-connected-model.ts +396 -0
- package/src/internal/models/oauth-model.ts +708 -0
- package/src/internal/models/password-model.ts +0 -56
- package/src/internal/native-sql-table.ts +14 -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 +5 -98
- 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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { CurrentCommitJournal, hasCommitScope } from "@yielded/auth/Hooks";
|
|
2
|
+
import type { LoginIdentifier } from "@yielded/auth/Identity";
|
|
3
|
+
import {
|
|
4
|
+
EncodedPasswordHash,
|
|
5
|
+
type PasswordPersistence,
|
|
6
|
+
PasswordUnavailable,
|
|
7
|
+
snapshotPasswordCredential,
|
|
8
|
+
type PasswordCredentialSnapshot,
|
|
9
|
+
} from "@yielded/auth/Password";
|
|
10
|
+
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
11
|
+
import { Effect, Option, Redacted, Schema } from "effect";
|
|
12
|
+
|
|
13
|
+
import type { PersistenceMappingError } from "./mapping-error";
|
|
14
|
+
import type { AnyPasswordPersistenceMapping } from "./models/password-model";
|
|
15
|
+
import { sqlMapping, type SqlTable } from "./native-sql-table";
|
|
16
|
+
import type { PasswordSqlConfiguration } from "./password-kernel";
|
|
17
|
+
import { NativeDatabase } from "./transaction-kernel";
|
|
18
|
+
|
|
19
|
+
type Row = Readonly<Record<string, unknown>>;
|
|
20
|
+
const unavailable = () => PasswordUnavailable.make({});
|
|
21
|
+
|
|
22
|
+
const failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
23
|
+
reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(
|
|
24
|
+
Effect.mapError(unavailable),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const samePasswordCredentialSnapshot = (
|
|
28
|
+
a: PasswordCredentialSnapshot,
|
|
29
|
+
b: PasswordCredentialSnapshot,
|
|
30
|
+
) =>
|
|
31
|
+
a.moduleId === b.moduleId &&
|
|
32
|
+
a.revision.subjectId === b.revision.subjectId &&
|
|
33
|
+
a.revision.securityRevision === b.revision.securityRevision &&
|
|
34
|
+
a.revision.credentials.length === b.revision.credentials.length &&
|
|
35
|
+
new Set(a.revision.credentials.map((entry) => entry.credentialId)).size ===
|
|
36
|
+
a.revision.credentials.length &&
|
|
37
|
+
new Set(b.revision.credentials.map((entry) => entry.credentialId)).size ===
|
|
38
|
+
b.revision.credentials.length &&
|
|
39
|
+
a.revision.credentials.every((entry) =>
|
|
40
|
+
b.revision.credentials.some(
|
|
41
|
+
(other) => entry.credentialId === other.credentialId && entry.revision === other.revision,
|
|
42
|
+
),
|
|
43
|
+
) &&
|
|
44
|
+
a.credentialId === b.credentialId &&
|
|
45
|
+
a.credentialRevision === b.credentialRevision &&
|
|
46
|
+
a.verifierVersion === b.verifierVersion &&
|
|
47
|
+
Redacted.value(a.verifier) === Redacted.value(b.verifier) &&
|
|
48
|
+
a.normalization === b.normalization &&
|
|
49
|
+
a.identifier.namespace === b.identifier.namespace &&
|
|
50
|
+
a.identifier.value === b.identifier.value &&
|
|
51
|
+
a.identifierBindingRevision === b.identifierBindingRevision &&
|
|
52
|
+
a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;
|
|
53
|
+
|
|
54
|
+
/** Lookup is read-only; rehash is one conditional maintenance write, never authentication. */
|
|
55
|
+
export const makePasswordCredentials = Effect.fnUntraced(function* (
|
|
56
|
+
table: (table: object) => SqlTable,
|
|
57
|
+
mapping: AnyPasswordPersistenceMapping,
|
|
58
|
+
options: PasswordSqlConfiguration,
|
|
59
|
+
): Effect.fn.Return<
|
|
60
|
+
Pick<PasswordPersistence["Service"], "findCredential" | "rehashIfCurrent">,
|
|
61
|
+
PasswordUnavailable,
|
|
62
|
+
NativeDatabase
|
|
63
|
+
> {
|
|
64
|
+
const sql = (yield* NativeDatabase).$client.withoutTransforms();
|
|
65
|
+
const parent = yield* Effect.serviceOption(CurrentCommitJournal);
|
|
66
|
+
|
|
67
|
+
const [s, i, c] = yield* sqlMapping(
|
|
68
|
+
() =>
|
|
69
|
+
[
|
|
70
|
+
table(mapping.subject.table),
|
|
71
|
+
table(mapping.identifier.table),
|
|
72
|
+
table(mapping.credential.table),
|
|
73
|
+
] as const,
|
|
74
|
+
).pipe(failure);
|
|
75
|
+
|
|
76
|
+
const standalone = Effect.gen(function* () {
|
|
77
|
+
if (options.coordinated) {
|
|
78
|
+
const current = yield* Effect.serviceOption(CurrentCommitJournal);
|
|
79
|
+
|
|
80
|
+
if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value)
|
|
81
|
+
return yield* unavailable();
|
|
82
|
+
} else {
|
|
83
|
+
if (yield* hasCommitScope) return yield* unavailable();
|
|
84
|
+
yield* options.standaloneGuard;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const allocate = <A>(
|
|
89
|
+
asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,
|
|
90
|
+
sync: (() => A) | undefined,
|
|
91
|
+
) => {
|
|
92
|
+
if (options.mode !== "synchronous" && asynchronous !== undefined) return asynchronous;
|
|
93
|
+
|
|
94
|
+
return sync === undefined
|
|
95
|
+
? Effect.fail(unavailable())
|
|
96
|
+
: Effect.try({ try: sync, catch: unavailable });
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Bind each native ID through its own column codec. Physical encodings of the
|
|
100
|
+
// same application ID need not match across subject, identifier and password.
|
|
101
|
+
const snapshot = Effect.fnUntraced(function* (
|
|
102
|
+
nativeId: unknown,
|
|
103
|
+
moduleId: string,
|
|
104
|
+
identifier: LoginIdentifier,
|
|
105
|
+
) {
|
|
106
|
+
const subjectTable = s.as("password_subject");
|
|
107
|
+
const identifierTable = i.as("password_identifier");
|
|
108
|
+
const credentialTable = c.as("password_credential");
|
|
109
|
+
|
|
110
|
+
const rows = yield* sqlMapping(
|
|
111
|
+
() =>
|
|
112
|
+
sql<Row>`select ${subjectTable.fields("subject_")}, ${identifierTable.fields("identifier_")}, ${credentialTable.fields("credential_")}
|
|
113
|
+
from ${subjectTable.name}
|
|
114
|
+
inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}
|
|
115
|
+
and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}
|
|
116
|
+
and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}
|
|
117
|
+
inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}
|
|
118
|
+
and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}
|
|
119
|
+
where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}
|
|
120
|
+
`,
|
|
121
|
+
).pipe(Effect.flatten);
|
|
122
|
+
|
|
123
|
+
if (rows.length !== 1) return undefined;
|
|
124
|
+
|
|
125
|
+
const [subject, identifierRow, credential] = yield* sqlMapping(
|
|
126
|
+
() =>
|
|
127
|
+
[
|
|
128
|
+
s.decode(rows[0]!, "subject_"),
|
|
129
|
+
i.decode(rows[0]!, "identifier_"),
|
|
130
|
+
c.decode(rows[0]!, "credential_"),
|
|
131
|
+
] as const,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
yield* sqlMapping(
|
|
136
|
+
() =>
|
|
137
|
+
!mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
|
|
138
|
+
!mapping.identifier.isCurrent(identifierRow) ||
|
|
139
|
+
!mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) ||
|
|
140
|
+
!mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) ||
|
|
141
|
+
!mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]),
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
return undefined;
|
|
145
|
+
|
|
146
|
+
const captured = yield* sqlMapping(() =>
|
|
147
|
+
mapping.credential.decode({ moduleId, subject, identifier: identifierRow, credential }),
|
|
148
|
+
).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));
|
|
149
|
+
|
|
150
|
+
return captured;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
findCredential: (uncaptured) =>
|
|
155
|
+
Effect.gen(function* () {
|
|
156
|
+
const input = { ...uncaptured, identifier: { ...uncaptured.identifier } };
|
|
157
|
+
|
|
158
|
+
yield* standalone;
|
|
159
|
+
|
|
160
|
+
const identifiers = yield* sqlMapping(
|
|
161
|
+
() => sql<Row>`select ${i.fields("i_")} from ${i.name}
|
|
162
|
+
where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}
|
|
163
|
+
and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`,
|
|
164
|
+
).pipe(Effect.flatten);
|
|
165
|
+
|
|
166
|
+
const identifier =
|
|
167
|
+
identifiers.length === 1
|
|
168
|
+
? yield* sqlMapping(() => i.decode(identifiers[0]!, "i_"))
|
|
169
|
+
: undefined;
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
identifier === undefined ||
|
|
173
|
+
!(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))
|
|
174
|
+
)
|
|
175
|
+
return Option.none();
|
|
176
|
+
|
|
177
|
+
const credential = yield* snapshot(
|
|
178
|
+
identifier[mapping.identifier.subjectId],
|
|
179
|
+
input.moduleId,
|
|
180
|
+
input.identifier,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return credential === undefined ||
|
|
184
|
+
(input.subjectId !== undefined && credential.revision.subjectId !== input.subjectId)
|
|
185
|
+
? Option.none()
|
|
186
|
+
: Option.some(credential);
|
|
187
|
+
}).pipe(failure),
|
|
188
|
+
rehashIfCurrent: (input) =>
|
|
189
|
+
Effect.gen(function* () {
|
|
190
|
+
const nextVerifier = Redacted.make(
|
|
191
|
+
yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const credential = yield* snapshotPasswordCredential(input.credential);
|
|
195
|
+
|
|
196
|
+
yield* standalone;
|
|
197
|
+
const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);
|
|
198
|
+
const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
|
|
199
|
+
|
|
200
|
+
if (version === credential.verifierVersion) return yield* unavailable();
|
|
201
|
+
yield* sqlMapping(() => {
|
|
202
|
+
const encoded = mapping.credential.encodeVerifier(nextVerifier, version);
|
|
203
|
+
|
|
204
|
+
// A custom encoder cannot turn maintenance into a semantic credential change.
|
|
205
|
+
const values = {
|
|
206
|
+
[mapping.credential.verifier]: encoded[mapping.credential.verifier],
|
|
207
|
+
[mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (Object.values(values).some((value) => value === undefined)) throw unavailable();
|
|
211
|
+
|
|
212
|
+
// Ordinary identity predicates retain index use; the full CAS ignores text collation.
|
|
213
|
+
const exact = sql.and(
|
|
214
|
+
Object.entries({
|
|
215
|
+
[mapping.credential.moduleId]: credential.moduleId,
|
|
216
|
+
[mapping.credential.subjectId]: nativeId,
|
|
217
|
+
[mapping.credential.credentialId]: credential.credentialId,
|
|
218
|
+
[mapping.credential.credentialRevision]: credential.credentialRevision,
|
|
219
|
+
[mapping.credential.verifierVersion]: credential.verifierVersion,
|
|
220
|
+
[mapping.credential.verifier]: Redacted.value(credential.verifier),
|
|
221
|
+
[mapping.credential.normalization]: credential.normalization,
|
|
222
|
+
}).map(([key, value]) => {
|
|
223
|
+
const column = c.column(key),
|
|
224
|
+
bound = c.value(key, value);
|
|
225
|
+
|
|
226
|
+
if (typeof value !== "string") return sql`${column} = ${bound}`;
|
|
227
|
+
|
|
228
|
+
return sql.onDialectOrElse({
|
|
229
|
+
mysql: () => sql`binary ${column} = binary ${bound}`,
|
|
230
|
+
pg: () =>
|
|
231
|
+
sql`convert_to(cast(${column} as text), 'UTF8') = convert_to(cast(${bound} as text), 'UTF8')`,
|
|
232
|
+
orElse: () => sql`cast(${column} as blob) = cast(${bound} as blob)`,
|
|
233
|
+
});
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return sql`${c.update(values)}
|
|
238
|
+
where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, credential.moduleId)}
|
|
239
|
+
and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, nativeId)}
|
|
240
|
+
and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, credential.credentialId)}
|
|
241
|
+
and ${exact}`;
|
|
242
|
+
}).pipe(Effect.flatten);
|
|
243
|
+
}).pipe(failure),
|
|
244
|
+
} satisfies Pick<PasswordPersistence["Service"], "findCredential" | "rehashIfCurrent">;
|
|
245
|
+
});
|
|
@@ -41,9 +41,9 @@ import {
|
|
|
41
41
|
} from "./models/password-model";
|
|
42
42
|
import { makeNativeSqlTables, sqlMapping, type NativeSqlTables } from "./native-sql-table";
|
|
43
43
|
import {
|
|
44
|
-
|
|
44
|
+
makePasswordCredentials,
|
|
45
45
|
samePasswordCredentialSnapshot as sameCredentialSnapshot,
|
|
46
|
-
} from "./password-
|
|
46
|
+
} from "./password-credentials";
|
|
47
47
|
import {
|
|
48
48
|
CurrentPasswordPreparedTransaction,
|
|
49
49
|
PasswordPreparedPostconditions,
|
|
@@ -119,7 +119,7 @@ export const makePasswordKernel = <
|
|
|
119
119
|
) => {
|
|
120
120
|
type Mapping = AnyPasswordPersistenceMapping<Fragment>;
|
|
121
121
|
|
|
122
|
-
const { and, eq, inArray,
|
|
122
|
+
const { and, eq, inArray, column, updateValues } = operations;
|
|
123
123
|
const { completeProofPlanIn } = proofs;
|
|
124
124
|
const unavailable = () => PasswordUnavailable.make({});
|
|
125
125
|
|
|
@@ -178,27 +178,6 @@ export const makePasswordKernel = <
|
|
|
178
178
|
revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision),
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
const attemptColumns = (mapping: Mapping) => ({
|
|
182
|
-
moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
|
|
183
|
-
action: column(mapping.attempt.table, mapping.attempt.action),
|
|
184
|
-
attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
|
|
185
|
-
identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
|
|
186
|
-
identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
|
|
187
|
-
subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
|
|
188
|
-
credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
|
|
189
|
-
securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
|
|
190
|
-
credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
|
|
191
|
-
verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
|
|
192
|
-
identifierBindingRevision: column(
|
|
193
|
-
mapping.attempt.table,
|
|
194
|
-
mapping.attempt.identifierBindingRevision,
|
|
195
|
-
),
|
|
196
|
-
admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
|
|
197
|
-
deadline: column(mapping.attempt.table, mapping.attempt.deadline),
|
|
198
|
-
retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
|
|
199
|
-
state: column(mapping.attempt.table, mapping.attempt.state),
|
|
200
|
-
});
|
|
201
|
-
|
|
202
181
|
const commandColumns = (mapping: Mapping) => ({
|
|
203
182
|
moduleId: column(mapping.command.table, mapping.command.moduleId),
|
|
204
183
|
commandId: column(mapping.command.table, mapping.command.commandId),
|
|
@@ -943,7 +922,7 @@ export const makePasswordKernel = <
|
|
|
943
922
|
if (!validConstraints(mapping)) return yield* unavailable();
|
|
944
923
|
const root = yield* NativeDatabase;
|
|
945
924
|
|
|
946
|
-
const
|
|
925
|
+
const credentials = yield* makePasswordCredentials(
|
|
947
926
|
yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(
|
|
948
927
|
translateFailure,
|
|
949
928
|
),
|
|
@@ -952,7 +931,7 @@ export const makePasswordKernel = <
|
|
|
952
931
|
);
|
|
953
932
|
|
|
954
933
|
return PasswordPersistence.of({
|
|
955
|
-
...
|
|
934
|
+
...credentials,
|
|
956
935
|
readForSubject: (input) =>
|
|
957
936
|
safeRead(
|
|
958
937
|
database,
|
|
@@ -1288,78 +1267,6 @@ export const makePasswordKernel = <
|
|
|
1288
1267
|
Effect.provideService(LifecycleHooks, hooks),
|
|
1289
1268
|
translateFailure,
|
|
1290
1269
|
),
|
|
1291
|
-
cleanupAttempts: (input, prepare) =>
|
|
1292
|
-
owned(
|
|
1293
|
-
database,
|
|
1294
|
-
mapping,
|
|
1295
|
-
configuration,
|
|
1296
|
-
Effect.gen(function* () {
|
|
1297
|
-
const transaction = yield* CurrentPasswordSql;
|
|
1298
|
-
const journal = yield* CurrentCommitJournal;
|
|
1299
|
-
|
|
1300
|
-
const now = yield* nowMillis;
|
|
1301
|
-
const nativeNow = mapping.encodeInstant(now);
|
|
1302
|
-
const a = attemptColumns(mapping);
|
|
1303
|
-
|
|
1304
|
-
const attempts = yield* selectRows(
|
|
1305
|
-
transaction
|
|
1306
|
-
.select({ attemptId: a.attemptId })
|
|
1307
|
-
.from(mapping.attempt.table)
|
|
1308
|
-
.where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow)))
|
|
1309
|
-
.limit(input.limit + 1),
|
|
1310
|
-
false,
|
|
1311
|
-
);
|
|
1312
|
-
|
|
1313
|
-
const hasMore = attempts.length > input.limit;
|
|
1314
|
-
const attemptCandidates = attempts.slice(0, input.limit);
|
|
1315
|
-
const selectedAttempts: typeof attemptCandidates = [];
|
|
1316
|
-
|
|
1317
|
-
for (const candidate of attemptCandidates) {
|
|
1318
|
-
const locked = yield* selectRows(
|
|
1319
|
-
transaction
|
|
1320
|
-
.select({ attemptId: a.attemptId })
|
|
1321
|
-
.from(mapping.attempt.table)
|
|
1322
|
-
.where(
|
|
1323
|
-
and(
|
|
1324
|
-
eq(a.moduleId, input.moduleId),
|
|
1325
|
-
eq(a.attemptId, candidate.attemptId),
|
|
1326
|
-
lte(a.retentionUntil, nativeNow),
|
|
1327
|
-
),
|
|
1328
|
-
)
|
|
1329
|
-
.limit(1),
|
|
1330
|
-
configuration.locking,
|
|
1331
|
-
);
|
|
1332
|
-
|
|
1333
|
-
if (locked[0] !== undefined) selectedAttempts.push(locked[0] as any);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
const prepared = prepare(
|
|
1337
|
-
{
|
|
1338
|
-
removed: selectedAttempts.length,
|
|
1339
|
-
hasMore,
|
|
1340
|
-
},
|
|
1341
|
-
journal,
|
|
1342
|
-
);
|
|
1343
|
-
|
|
1344
|
-
if (selectedAttempts.length > 0)
|
|
1345
|
-
yield* transaction.delete(mapping.attempt.table).where(
|
|
1346
|
-
and(
|
|
1347
|
-
eq(a.moduleId, input.moduleId),
|
|
1348
|
-
inArray(
|
|
1349
|
-
a.attemptId,
|
|
1350
|
-
selectedAttempts.map((row: any) => row.attemptId),
|
|
1351
|
-
),
|
|
1352
|
-
lte(a.retentionUntil, nativeNow),
|
|
1353
|
-
),
|
|
1354
|
-
);
|
|
1355
|
-
|
|
1356
|
-
return prepared;
|
|
1357
|
-
}),
|
|
1358
|
-
).pipe(
|
|
1359
|
-
Effect.provideService(CurrentPasswordSql, database),
|
|
1360
|
-
Effect.provideService(LifecycleHooks, hooks),
|
|
1361
|
-
translateFailure,
|
|
1362
|
-
),
|
|
1363
1270
|
});
|
|
1364
1271
|
});
|
|
1365
1272
|
|
|
@@ -141,7 +141,7 @@ export const createPersistence = <T extends object, R>(
|
|
|
141
141
|
|
|
142
142
|
const roles: StorageRole[] = ["identifiers", "credentials", "sessions", "sessionFlows"];
|
|
143
143
|
|
|
144
|
-
if (password) roles.push("passwords", "
|
|
144
|
+
if (password) roles.push("passwords", "passwordCommands");
|
|
145
145
|
if (management) roles.push("passwordRegistrations");
|
|
146
146
|
if (email) roles.push("emailCredentials", "emailCommands");
|
|
147
147
|
if (phone) roles.push("phoneState");
|
|
@@ -60,10 +60,12 @@ export interface QueryOperations<
|
|
|
60
60
|
readonly balancedD1And: Predicate<Fragment>;
|
|
61
61
|
readonly asc: (value: unknown) => Fragment;
|
|
62
62
|
readonly eq: Comparison<Fragment>;
|
|
63
|
+
readonly ne: Comparison<Fragment>;
|
|
63
64
|
readonly gt: Comparison<Fragment>;
|
|
64
65
|
readonly gte: Comparison<Fragment>;
|
|
65
66
|
readonly lte: Comparison<Fragment>;
|
|
66
67
|
readonly inArray: (value: unknown, values: ReadonlyArray<unknown>) => Fragment;
|
|
68
|
+
readonly isNotNull: (value: unknown) => Fragment;
|
|
67
69
|
readonly isNull: (value: unknown) => Fragment;
|
|
68
70
|
readonly notExists: (value: SqlExpression) => Fragment;
|
|
69
71
|
readonly sql: {
|