@yielded/auth-persistence 0.1.0-beta.17 → 0.1.0-beta.19
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 +5 -2
- package/dist/Adapter.d.mts +2 -2
- package/dist/Adapter.mjs +2 -2
- package/dist/internal/configuration.d.mts +5 -4
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/crypto.mjs +12 -0
- package/dist/internal/crypto.mjs.map +1 -0
- package/dist/internal/email-kernel.d.mts +7 -7
- package/dist/internal/passkey/admission.mjs +19 -15
- package/dist/internal/passkey/admission.mjs.map +1 -1
- package/dist/internal/passkey/credentials.mjs +4 -4
- package/dist/internal/passkey/credentials.mjs.map +1 -1
- package/dist/internal/passkey/enrollment.mjs +6 -6
- package/dist/internal/passkey/enrollment.mjs.map +1 -1
- package/dist/internal/passkey/flow.mjs +7 -7
- package/dist/internal/passkey/flow.mjs.map +1 -1
- package/dist/internal/passkey/management.mjs +2 -2
- package/dist/internal/passkey/management.mjs.map +1 -1
- package/dist/internal/passkey/registration-ceremony.mjs +2 -2
- package/dist/internal/passkey/registration-ceremony.mjs.map +1 -1
- package/dist/internal/passkey/registration-custody.mjs +1 -1
- package/dist/internal/passkey/registration-custody.mjs.map +1 -1
- package/dist/internal/passkey/registration-write.mjs +12 -12
- package/dist/internal/passkey/registration-write.mjs.map +1 -1
- package/dist/internal/passkey/state.d.mts +1 -1
- package/dist/internal/passkey/state.mjs +8 -9
- package/dist/internal/passkey/state.mjs.map +1 -1
- package/dist/internal/passkey/target.d.mts +1 -1
- package/dist/internal/passkey/target.mjs.map +1 -1
- package/dist/internal/passkey/write-state.mjs +13 -13
- package/dist/internal/passkey/write-state.mjs.map +1 -1
- package/dist/internal/passkey/write-target.mjs.map +1 -1
- package/dist/internal/passkey-kernel.d.mts +705 -249
- package/dist/internal/passkeys.mjs +32 -14
- package/dist/internal/passkeys.mjs.map +1 -1
- package/dist/internal/password-kernel.d.mts +6 -6
- package/dist/internal/password-kernel.mjs +1 -1
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +2 -2
- package/dist/internal/persistence.mjs +18 -9
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/phone-kernel.d.mts +7 -7
- package/dist/internal/phone-kernel.mjs +14 -14
- package/dist/internal/phone-kernel.mjs.map +1 -1
- package/dist/internal/registration.mjs +8 -7
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-kernel.mjs +2 -1
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-query.mjs +1 -1
- package/dist/internal/sql-query.mjs.map +1 -1
- package/dist/internal/storage-mapping.d.mts +5 -4
- package/dist/internal/storage-mapping.mjs +16 -13
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +13 -3
- package/dist/internal/storage-validation.mjs +128 -68
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-execution-kernel.d.mts +4 -4
- package/dist/internal/transaction-execution-kernel.mjs +7 -5
- package/dist/internal/transaction-execution-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +7 -1
- package/src/internal/configuration.ts +5 -3
- package/src/internal/crypto.ts +15 -0
- package/src/internal/passkey/admission.ts +22 -15
- package/src/internal/passkey/credentials.ts +4 -4
- package/src/internal/passkey/enrollment.ts +7 -7
- package/src/internal/passkey/flow.ts +8 -8
- package/src/internal/passkey/management.ts +7 -2
- package/src/internal/passkey/registration-ceremony.ts +2 -2
- package/src/internal/passkey/registration-custody.ts +1 -1
- package/src/internal/passkey/registration-write.ts +12 -12
- package/src/internal/passkey/state.ts +10 -13
- package/src/internal/passkey/target.ts +16 -8
- package/src/internal/passkey/write-state.ts +10 -14
- package/src/internal/passkey/write-target.ts +11 -3
- package/src/internal/passkeys.ts +121 -70
- package/src/internal/password-kernel.ts +2 -2
- package/src/internal/persistence.ts +30 -13
- package/src/internal/phone-kernel.ts +26 -17
- package/src/internal/registration.ts +10 -8
- package/src/internal/session-kernel.ts +3 -2
- package/src/internal/sql-query.ts +3 -2
- package/src/internal/storage-mapping.ts +21 -13
- package/src/internal/storage-validation.ts +219 -139
- package/src/internal/transaction-execution-kernel.ts +30 -11
|
@@ -31,18 +31,19 @@ export const makePasskeyAdmissionKernel = (
|
|
|
31
31
|
|
|
32
32
|
const chargeRetentionMillis = 86_400_000;
|
|
33
33
|
|
|
34
|
-
const chargeScopes = (ceremony: PasskeyCeremony, subjectId?: string)
|
|
34
|
+
const chargeScopes = Effect.fnUntraced(function* (ceremony: PasskeyCeremony, subjectId?: string) {
|
|
35
35
|
const scopes: { kind: PasskeyChargeKind; scope: string }[] = [
|
|
36
|
-
{ kind: "global", scope: key("global", [ceremony.moduleId]) },
|
|
36
|
+
{ kind: "global", scope: yield* key("global", [ceremony.moduleId]) },
|
|
37
37
|
];
|
|
38
38
|
|
|
39
|
-
if (subjectId !== undefined)
|
|
40
|
-
|
|
39
|
+
if (subjectId !== undefined)
|
|
40
|
+
scopes.push({ kind: "subject", scope: yield* subjectScope(subjectId) });
|
|
41
|
+
const target = yield* targetScope(ceremony);
|
|
41
42
|
|
|
42
43
|
if (target !== null) scopes.push({ kind: "target", scope: target });
|
|
43
44
|
|
|
44
45
|
return scopes;
|
|
45
|
-
};
|
|
46
|
+
});
|
|
46
47
|
|
|
47
48
|
const lockAdmission = Effect.fn("passkey.lockAdmission")(function* (mapping: any) {
|
|
48
49
|
const owner = yield* CurrentPasskeyTransaction;
|
|
@@ -107,7 +108,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
107
108
|
)})`;
|
|
108
109
|
};
|
|
109
110
|
|
|
110
|
-
const admissionCondition = (
|
|
111
|
+
const admissionCondition = Effect.fnUntraced(function* (
|
|
111
112
|
mapping: any,
|
|
112
113
|
policies: ReadonlyArray<PasskeyMethodPolicy>,
|
|
113
114
|
ceremony: PasskeyCeremony,
|
|
@@ -115,19 +116,23 @@ export const makePasskeyAdmissionKernel = (
|
|
|
115
116
|
added: ReadonlyArray<PasskeyChargeKind>,
|
|
116
117
|
newFlow: boolean,
|
|
117
118
|
resolvedSubject: boolean,
|
|
118
|
-
)
|
|
119
|
-
|
|
119
|
+
) {
|
|
120
|
+
const subject = subjectId === undefined ? undefined : yield* subjectScope(subjectId);
|
|
121
|
+
const scopes = yield* chargeScopes(ceremony, subjectId);
|
|
122
|
+
|
|
123
|
+
return both(
|
|
120
124
|
...policies.flatMap((policy) => [
|
|
121
125
|
sql`${pendingCount(mapping)} + ${newFlow ? 1 : 0} <= ${policy.maximumPending}`,
|
|
122
126
|
subjectId === undefined
|
|
123
127
|
? undefined
|
|
124
|
-
: sql`${pendingCount(mapping,
|
|
125
|
-
...
|
|
128
|
+
: sql`${pendingCount(mapping, subject)} + ${newFlow || resolvedSubject ? 1 : 0} <= ${policy.maximumPendingPerSubject}`,
|
|
129
|
+
...scopes.map(
|
|
126
130
|
({ kind, scope }) =>
|
|
127
131
|
sql`${chargedCount(mapping, kind, scope, policy.admission[kind].windowMillis)} + ${added.includes(kind) ? 1 : 0} <= ${policy.admission[kind].limit}`,
|
|
128
132
|
),
|
|
129
133
|
]),
|
|
130
134
|
);
|
|
135
|
+
});
|
|
131
136
|
|
|
132
137
|
const guardAdmission = (
|
|
133
138
|
mapping: any,
|
|
@@ -135,9 +140,11 @@ export const makePasskeyAdmissionKernel = (
|
|
|
135
140
|
ceremony: PasskeyCeremony,
|
|
136
141
|
subjectId?: string,
|
|
137
142
|
) =>
|
|
138
|
-
Effect.
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const owner = yield* CurrentPasskeyTransaction;
|
|
145
|
+
|
|
139
146
|
owner.postconditions.push(
|
|
140
|
-
admissionCondition(mapping, policies, ceremony, subjectId, [], false, false),
|
|
147
|
+
yield* admissionCondition(mapping, policies, ceremony, subjectId, [], false, false),
|
|
141
148
|
);
|
|
142
149
|
});
|
|
143
150
|
|
|
@@ -161,7 +168,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
161
168
|
columns: mappedColumns(table),
|
|
162
169
|
});
|
|
163
170
|
|
|
164
|
-
const scopes = chargeScopes(ceremony, subjectId);
|
|
171
|
+
const scopes = yield* chargeScopes(ceremony, subjectId);
|
|
165
172
|
|
|
166
173
|
invariant(found.rows.length === scopes.length);
|
|
167
174
|
for (const { kind, scope } of scopes) {
|
|
@@ -202,7 +209,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
202
209
|
Effect.flatMap(CurrentPasskeyTransaction, (owner) =>
|
|
203
210
|
Effect.gen(function* () {
|
|
204
211
|
const table = mapping.charge;
|
|
205
|
-
const scopes = chargeScopes(ceremony, subjectId);
|
|
212
|
+
const scopes = yield* chargeScopes(ceremony, subjectId);
|
|
206
213
|
|
|
207
214
|
const where = equal(table.table, {
|
|
208
215
|
[table.moduleId]: ceremony.moduleId,
|
|
@@ -241,7 +248,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
241
248
|
const earliest = yield* owner.now(clock);
|
|
242
249
|
const identities: Record<string, unknown>[] = [];
|
|
243
250
|
|
|
244
|
-
for (const { kind, scope } of chargeScopes(ceremony, subjectId).filter((item) =>
|
|
251
|
+
for (const { kind, scope } of (yield* chargeScopes(ceremony, subjectId)).filter((item) =>
|
|
245
252
|
kinds.includes(item.kind),
|
|
246
253
|
)) {
|
|
247
254
|
const identity = {
|
|
@@ -172,7 +172,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
172
172
|
const found = yield* owner.read(
|
|
173
173
|
descriptor.table,
|
|
174
174
|
equal(descriptor.table, {
|
|
175
|
-
[descriptor.credentialKey]: credentialKey(rpId, protocolCredentialId),
|
|
175
|
+
[descriptor.credentialKey]: yield* credentialKey(rpId, protocolCredentialId),
|
|
176
176
|
}),
|
|
177
177
|
{ lock: false, observe: false, limit: 1, columns: mappedColumns(descriptor) },
|
|
178
178
|
);
|
|
@@ -257,7 +257,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
257
257
|
) {
|
|
258
258
|
const owner = yield* CurrentPasskeyTransaction;
|
|
259
259
|
const ownership = mapping.credentialOwnership;
|
|
260
|
-
const tupleKey = credentialKey(rpId, protocolCredentialId);
|
|
260
|
+
const tupleKey = yield* credentialKey(rpId, protocolCredentialId);
|
|
261
261
|
|
|
262
262
|
const tuple = (yield* owner.read(
|
|
263
263
|
ownership.table,
|
|
@@ -298,7 +298,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
298
298
|
return undefined;
|
|
299
299
|
let decoded = descriptor.decode(copiedRow(row));
|
|
300
300
|
const handle = mapping.handleOwnership;
|
|
301
|
-
const expectedHandleKey = handleKey(rpId, decoded.userHandle);
|
|
301
|
+
const expectedHandleKey = yield* handleKey(rpId, decoded.userHandle);
|
|
302
302
|
|
|
303
303
|
const handleRow = (yield* owner.read(
|
|
304
304
|
handle.table,
|
|
@@ -452,7 +452,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
452
452
|
);
|
|
453
453
|
// oxlint-disable-next-line no-restricted-properties -- native SQL column values enter through this untyped driver boundary.
|
|
454
454
|
userHandle = Schema.decodeUnknownSync(PasskeyUserHandle)(handleRow[handle.userHandle]);
|
|
455
|
-
invariant(handleRow[handle.handleKey] === handleKey(input.rpId, userHandle));
|
|
455
|
+
invariant(handleRow[handle.handleKey] === (yield* handleKey(input.rpId, userHandle)));
|
|
456
456
|
}
|
|
457
457
|
const credential = mapping.read.credential;
|
|
458
458
|
|
|
@@ -97,7 +97,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
97
97
|
|
|
98
98
|
const found = yield* owner.read(table.table, where, { limit: 1 });
|
|
99
99
|
const row = found.rows[0];
|
|
100
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
100
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
101
101
|
|
|
102
102
|
if (row !== undefined)
|
|
103
103
|
return (
|
|
@@ -193,7 +193,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
193
193
|
action: "enroll-begin",
|
|
194
194
|
commandId: ceremony.commandId,
|
|
195
195
|
flowId: ceremony.flowId,
|
|
196
|
-
bindingDigest: enrollmentDigest(ceremony),
|
|
196
|
+
bindingDigest: yield* enrollmentDigest(ceremony),
|
|
197
197
|
revision: ceremony.context.revision,
|
|
198
198
|
},
|
|
199
199
|
{
|
|
@@ -207,11 +207,11 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
207
207
|
)
|
|
208
208
|
return { _tag: "Rejected" } as const;
|
|
209
209
|
invariant(
|
|
210
|
-
digest(Ceremony.fields.context, ceremony.context) ===
|
|
211
|
-
digest(Ceremony.fields.context, {
|
|
210
|
+
(yield* digest(Ceremony.fields.context, ceremony.context)) ===
|
|
211
|
+
(yield* digest(Ceremony.fields.context, {
|
|
212
212
|
...ceremony.context,
|
|
213
213
|
authorization: input.authorization,
|
|
214
|
-
}),
|
|
214
|
+
})),
|
|
215
215
|
);
|
|
216
216
|
const issued = yield* issueAssertion(mapping, ceremony, input.policy);
|
|
217
217
|
|
|
@@ -277,7 +277,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
277
277
|
action: "enroll-complete",
|
|
278
278
|
commandId: ceremony.commandId,
|
|
279
279
|
flowId: ceremony.flowId,
|
|
280
|
-
bindingDigest: digest(Ceremony, ceremony),
|
|
280
|
+
bindingDigest: yield* digest(Ceremony, ceremony),
|
|
281
281
|
revision: ceremony.context.revision,
|
|
282
282
|
},
|
|
283
283
|
{
|
|
@@ -293,7 +293,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
293
293
|
return yield* reject;
|
|
294
294
|
const tuple = mapping.read.credentialOwnership;
|
|
295
295
|
// Credential ownership is RP-global, including retained registration custody.
|
|
296
|
-
const key = credentialKeyFor(ceremony.profile.rpId, input.verified.protocolCredentialId);
|
|
296
|
+
const key = yield* credentialKeyFor(ceremony.profile.rpId, input.verified.protocolCredentialId);
|
|
297
297
|
|
|
298
298
|
if (
|
|
299
299
|
(yield* owner.read(tuple.table, equal(tuple.table, { [tuple.credentialKey]: key }), {
|
|
@@ -233,8 +233,8 @@ export const makePasskeyFlowKernel = (
|
|
|
233
233
|
const subject = credential?.revision.subjectId ?? knownSubject(ceremony);
|
|
234
234
|
|
|
235
235
|
invariant(
|
|
236
|
-
row[table.subjectScope] === (subject === undefined ? null : subjectScope(subject)) &&
|
|
237
|
-
row[table.targetScope] === targetScope(ceremony),
|
|
236
|
+
row[table.subjectScope] === (subject === undefined ? null : yield* subjectScope(subject)) &&
|
|
237
|
+
row[table.targetScope] === (yield* targetScope(ceremony)),
|
|
238
238
|
);
|
|
239
239
|
if (state === "Pending") {
|
|
240
240
|
invariant(
|
|
@@ -378,12 +378,12 @@ export const makePasskeyFlowKernel = (
|
|
|
378
378
|
|
|
379
379
|
if (sameCommand.rows.length !== 0 || sameFlow !== undefined)
|
|
380
380
|
return { _tag: "Rejected" } as const;
|
|
381
|
-
const kinds = chargeScopes(ceremony, subjectId).map((item) => item.kind);
|
|
381
|
+
const kinds = (yield* chargeScopes(ceremony, subjectId)).map((item) => item.kind);
|
|
382
382
|
|
|
383
383
|
if (
|
|
384
384
|
!(yield* owner.check(liveCondition(mapping, ceremony))) ||
|
|
385
385
|
!(yield* owner.check(
|
|
386
|
-
admissionCondition(mapping, [current], ceremony, subjectId, kinds, true, false),
|
|
386
|
+
yield* admissionCondition(mapping, [current], ceremony, subjectId, kinds, true, false),
|
|
387
387
|
))
|
|
388
388
|
)
|
|
389
389
|
return { _tag: "Rejected" } as const;
|
|
@@ -410,8 +410,8 @@ export const makePasskeyFlowKernel = (
|
|
|
410
410
|
[table.claimedAt]: null,
|
|
411
411
|
[table.claimExpiresAt]: null,
|
|
412
412
|
[table.credentialSnapshot]: null,
|
|
413
|
-
[table.subjectScope]: subjectId === undefined ? null : subjectScope(subjectId),
|
|
414
|
-
[table.targetScope]: targetScope(ceremony),
|
|
413
|
+
[table.subjectScope]: subjectId === undefined ? null : yield* subjectScope(subjectId),
|
|
414
|
+
[table.targetScope]: yield* targetScope(ceremony),
|
|
415
415
|
};
|
|
416
416
|
|
|
417
417
|
// Empty-key observations now expect this exact inserted row as well.
|
|
@@ -554,7 +554,7 @@ export const makePasskeyFlowKernel = (
|
|
|
554
554
|
|
|
555
555
|
if (
|
|
556
556
|
!(yield* owner.check(
|
|
557
|
-
admissionCondition(
|
|
557
|
+
yield* admissionCondition(
|
|
558
558
|
mapping,
|
|
559
559
|
[read.policy, current],
|
|
560
560
|
read.ceremony,
|
|
@@ -591,7 +591,7 @@ export const makePasskeyFlowKernel = (
|
|
|
591
591
|
[table.claimExpiresAt]: mapping.clock.encodeInstant(claim.claimExpiresAtMillis),
|
|
592
592
|
[table.credentialSnapshot]:
|
|
593
593
|
input.credential === undefined ? null : credentialStorage.encode(input.credential),
|
|
594
|
-
[table.subjectScope]: subjectId === undefined ? null : subjectScope(subjectId),
|
|
594
|
+
[table.subjectScope]: subjectId === undefined ? null : yield* subjectScope(subjectId),
|
|
595
595
|
},
|
|
596
596
|
);
|
|
597
597
|
if (resolved) yield* insertCharges(mapping, read.ceremony, read.policy, ["subject"], subjectId);
|
|
@@ -335,7 +335,7 @@ export const makePasskeyManagementKernel = (
|
|
|
335
335
|
action: "remove",
|
|
336
336
|
commandId: input.commandId,
|
|
337
337
|
flowId: input.commandId,
|
|
338
|
-
bindingDigest: removeDigest(mapping.moduleId, input.commandId, input.credential),
|
|
338
|
+
bindingDigest: yield* removeDigest(mapping.moduleId, input.commandId, input.credential),
|
|
339
339
|
revision: input.credential.revision,
|
|
340
340
|
},
|
|
341
341
|
{
|
|
@@ -384,7 +384,12 @@ export const makePasskeyManagementKernel = (
|
|
|
384
384
|
|
|
385
385
|
yield* owner.update(
|
|
386
386
|
tuple.table,
|
|
387
|
-
{
|
|
387
|
+
{
|
|
388
|
+
[tuple.credentialKey]: yield* credentialKey(
|
|
389
|
+
credential.rpId,
|
|
390
|
+
credential.protocolCredentialId,
|
|
391
|
+
),
|
|
392
|
+
},
|
|
388
393
|
{
|
|
389
394
|
[tuple.state]: mapping.write.credentialOwnership.removedState,
|
|
390
395
|
[tuple.version]: owner.marker,
|
|
@@ -78,7 +78,7 @@ export const makePasskeyRegistrationCeremonyKernel = (
|
|
|
78
78
|
})).rows[0];
|
|
79
79
|
|
|
80
80
|
if (advisory === undefined) return false;
|
|
81
|
-
const hashedHandle = handleKey(ceremony.profile.rpId, context.userHandle);
|
|
81
|
+
const hashedHandle = yield* handleKey(ceremony.profile.rpId, context.userHandle);
|
|
82
82
|
|
|
83
83
|
const held = (yield* owner.read(
|
|
84
84
|
handle.table,
|
|
@@ -204,7 +204,7 @@ export const makePasskeyRegistrationCeremonyKernel = (
|
|
|
204
204
|
policyStorage.encode(current) !== policyStorage.encode(input.policy) ||
|
|
205
205
|
!(yield* owner.check(liveCondition(mapping, read.ceremony))) ||
|
|
206
206
|
!(yield* owner.check(
|
|
207
|
-
admissionCondition(
|
|
207
|
+
yield* admissionCondition(
|
|
208
208
|
mapping,
|
|
209
209
|
[read.policy, current],
|
|
210
210
|
read.ceremony,
|
|
@@ -26,7 +26,7 @@ export const makePasskeyRegistrationCustodyKernel = (
|
|
|
26
26
|
const owner = yield* CurrentPasskeyTransaction;
|
|
27
27
|
const handle = mapping.handle;
|
|
28
28
|
const intent = mapping.intent;
|
|
29
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
29
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
30
30
|
|
|
31
31
|
const held = (yield* owner.read(
|
|
32
32
|
handle.table,
|
|
@@ -67,7 +67,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
67
67
|
const { digest, insertCredential, jsonStorage, validRegistration } = writeState;
|
|
68
68
|
const { both } = transactions;
|
|
69
69
|
|
|
70
|
-
const registrationData = (mapping: any, registration: any)
|
|
70
|
+
const registrationData = Effect.fnUntraced(function* (mapping: any, registration: any) {
|
|
71
71
|
const codec = jsonStorage(mapping.registration.schema);
|
|
72
72
|
const encoded = codec.encode(registration);
|
|
73
73
|
|
|
@@ -76,26 +76,26 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
76
76
|
return {
|
|
77
77
|
encoded,
|
|
78
78
|
registration: codec.decode(encoded),
|
|
79
|
-
fingerprint: digest(mapping.registration.schema, registration),
|
|
79
|
+
fingerprint: yield* digest(mapping.registration.schema, registration),
|
|
80
80
|
};
|
|
81
|
-
};
|
|
81
|
+
});
|
|
82
82
|
|
|
83
|
-
const describe = (mapping: any, registration: any)
|
|
84
|
-
const data = registrationData(mapping, registration);
|
|
83
|
+
const describe = Effect.fnUntraced(function* (mapping: any, registration: any) {
|
|
84
|
+
const data = yield* registrationData(mapping, registration);
|
|
85
85
|
|
|
86
86
|
const labels = Schema.decodeSync(
|
|
87
87
|
Schema.Struct({ name: PasskeyLabel, displayName: PasskeyLabel }),
|
|
88
88
|
)(mapping.registration.describe(data.registration));
|
|
89
89
|
|
|
90
90
|
return { ...data, ...labels };
|
|
91
|
-
};
|
|
91
|
+
});
|
|
92
92
|
|
|
93
93
|
const inspectRegistration = Effect.fn("passkey.inspectRegistration")(function* (
|
|
94
94
|
mapping: any,
|
|
95
95
|
registration: any,
|
|
96
96
|
) {
|
|
97
97
|
const owner = yield* CurrentPasskeyTransaction;
|
|
98
|
-
const value = describe(mapping, registration);
|
|
98
|
+
const value = yield* describe(mapping, registration);
|
|
99
99
|
const current = yield* readModule(mapping);
|
|
100
100
|
|
|
101
101
|
yield* readPolicyGuards(mapping);
|
|
@@ -133,7 +133,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
133
133
|
ceremony.allowedCredentials.length !== 0
|
|
134
134
|
)
|
|
135
135
|
return { _tag: "Rejected" } as const;
|
|
136
|
-
const value = describe(mapping, input.registration);
|
|
136
|
+
const value = yield* describe(mapping, input.registration);
|
|
137
137
|
|
|
138
138
|
if (
|
|
139
139
|
value.fingerprint !== ceremony.context.fingerprint ||
|
|
@@ -148,7 +148,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
148
148
|
|
|
149
149
|
if (!(yield* owner.check(eligible))) return { _tag: "Rejected" } as const;
|
|
150
150
|
const handle = mapping.handle;
|
|
151
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
151
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
152
152
|
|
|
153
153
|
const absentHandle = yield* owner.read(
|
|
154
154
|
handle.table,
|
|
@@ -259,7 +259,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
259
259
|
const absentCredential = yield* owner.read(
|
|
260
260
|
tuple.table,
|
|
261
261
|
equal(tuple.table, {
|
|
262
|
-
[tuple.credentialKey]: credentialKey(
|
|
262
|
+
[tuple.credentialKey]: yield* credentialKey(
|
|
263
263
|
ceremony.profile.rpId,
|
|
264
264
|
input.verified.protocolCredentialId,
|
|
265
265
|
),
|
|
@@ -268,7 +268,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
268
268
|
);
|
|
269
269
|
|
|
270
270
|
const handle = mapping.handle;
|
|
271
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
271
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
272
272
|
|
|
273
273
|
const held = (yield* owner.read(
|
|
274
274
|
handle.table,
|
|
@@ -319,7 +319,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
319
319
|
)
|
|
320
320
|
return yield* reject;
|
|
321
321
|
|
|
322
|
-
const data = describe(
|
|
322
|
+
const data = yield* describe(
|
|
323
323
|
mapping,
|
|
324
324
|
jsonStorage(mapping.registration.schema).decode(row[intent.applicationSnapshot]),
|
|
325
325
|
);
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any -- existing mapped-row bridge; public adapters preserve table, ID, and Effect types. */
|
|
2
2
|
|
|
3
|
-
// oxlint-disable-next-line import/extensions -- Noble exposes its explicit .js subpaths.
|
|
4
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
|
-
// oxlint-disable-next-line import/extensions -- Noble exposes its explicit .js subpaths.
|
|
6
|
-
import { randomBytes } from "@noble/hashes/utils.js";
|
|
7
3
|
import {
|
|
8
4
|
PasskeyConfigurationError,
|
|
9
5
|
PasskeyUnavailable,
|
|
@@ -15,9 +11,9 @@ import {
|
|
|
15
11
|
PasskeyMethodPolicy,
|
|
16
12
|
snapshotPasskeySync,
|
|
17
13
|
} from "@yielded/auth/Passkey";
|
|
18
|
-
import { Context, Effect, Schema } from "effect";
|
|
19
|
-
import { Base64Url } from "effect/encoding";
|
|
14
|
+
import { Context, Crypto, Effect, Schema } from "effect";
|
|
20
15
|
|
|
16
|
+
import { randomId } from "../crypto";
|
|
21
17
|
import {
|
|
22
18
|
requiredPasskeyCredentialConstraints,
|
|
23
19
|
requiredPasskeyPersistenceConstraints,
|
|
@@ -48,14 +44,14 @@ export const makePasskeyStateKernel = (
|
|
|
48
44
|
|
|
49
45
|
const { col, equal, copiedRow, matchesNativeRow } = makeTransactionRows(unavailable);
|
|
50
46
|
|
|
51
|
-
const nonce =
|
|
47
|
+
const nonce = randomId;
|
|
52
48
|
|
|
53
49
|
const encoder = new TextEncoder();
|
|
54
50
|
|
|
55
51
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
56
52
|
|
|
57
53
|
/** RP tuples and admission scopes have no collation-dependent string identity. */
|
|
58
|
-
const key = (kind: string, fields: ReadonlyArray<string>)
|
|
54
|
+
const key = Effect.fnUntraced(function* (kind: string, fields: ReadonlyArray<string>) {
|
|
59
55
|
const values = ["effect-auth/passkey/" + kind + "/v1", ...fields].map((field) => {
|
|
60
56
|
const bytes = encoder.encode(field);
|
|
61
57
|
|
|
@@ -74,10 +70,11 @@ export const makePasskeyStateKernel = (
|
|
|
74
70
|
offset += 4 + bytes.length;
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
const crypto = yield* Crypto.Crypto;
|
|
74
|
+
const hashed = yield* crypto.digest("SHA-256", packed);
|
|
75
|
+
|
|
76
|
+
return "v1:" + Array.from(hashed, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
77
|
+
});
|
|
81
78
|
|
|
82
79
|
const credentialKey = (rpId: string, id: string) => key("credential", [rpId, id]);
|
|
83
80
|
|
|
@@ -88,7 +85,7 @@ export const makePasskeyStateKernel = (
|
|
|
88
85
|
const targetScope = (ceremony: PasskeyCeremony) => {
|
|
89
86
|
const context = ceremony.context;
|
|
90
87
|
|
|
91
|
-
if (!("target" in context)) return null;
|
|
88
|
+
if (!("target" in context)) return Effect.succeed(null);
|
|
92
89
|
const target = context.target;
|
|
93
90
|
|
|
94
91
|
return key("target", [
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
snapshotPasskeySync,
|
|
27
27
|
} from "@yielded/auth/Passkey";
|
|
28
28
|
import { SubjectId } from "@yielded/auth/Schema";
|
|
29
|
-
import { Effect, Schema } from "effect";
|
|
29
|
+
import { type Crypto, Effect, Schema } from "effect";
|
|
30
30
|
import type { Statement } from "effect/sql/Statement";
|
|
31
31
|
|
|
32
32
|
import type { PersistenceMappingError } from "../mapping-error";
|
|
@@ -130,7 +130,7 @@ export const makePasskeyTargetKernel = (
|
|
|
130
130
|
): Effect.fn.Return<
|
|
131
131
|
PasskeyExecution,
|
|
132
132
|
PasskeyConfigurationError,
|
|
133
|
-
LifecycleHooks | NativeDatabase
|
|
133
|
+
LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
134
134
|
> {
|
|
135
135
|
const hooks = yield* LifecycleHooks;
|
|
136
136
|
|
|
@@ -437,7 +437,7 @@ export const makePasskeyTargetKernel = (
|
|
|
437
437
|
): Effect.Effect<
|
|
438
438
|
PasskeyCredentialServices,
|
|
439
439
|
PasskeyConfigurationError | PersistenceMappingError,
|
|
440
|
-
RSetup | NativeDatabase
|
|
440
|
+
RSetup | NativeDatabase | Crypto.Crypto
|
|
441
441
|
> =>
|
|
442
442
|
Effect.gen(function* () {
|
|
443
443
|
const mapping = yield* capturedMapping(source, "read", configuration);
|
|
@@ -455,7 +455,7 @@ export const makePasskeyTargetKernel = (
|
|
|
455
455
|
): Effect.Effect<
|
|
456
456
|
PasskeyEnrollmentContextServices,
|
|
457
457
|
PasskeyConfigurationError | PersistenceMappingError,
|
|
458
|
-
RSetup | NativeDatabase
|
|
458
|
+
RSetup | NativeDatabase | Crypto.Crypto
|
|
459
459
|
> =>
|
|
460
460
|
Effect.gen(function* () {
|
|
461
461
|
const mapping = yield* capturedMapping(source, "context", configuration);
|
|
@@ -473,7 +473,7 @@ export const makePasskeyTargetKernel = (
|
|
|
473
473
|
): Effect.Effect<
|
|
474
474
|
PasskeyPersistenceServices,
|
|
475
475
|
PasskeyConfigurationError | PersistenceMappingError,
|
|
476
|
-
RSetup | LifecycleHooks | NativeDatabase
|
|
476
|
+
RSetup | LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
477
477
|
> =>
|
|
478
478
|
Effect.gen(function* () {
|
|
479
479
|
const mapping = yield* capturedMapping(source, "assertion", configuration);
|
|
@@ -490,7 +490,7 @@ export const makePasskeyTargetKernel = (
|
|
|
490
490
|
): Effect.Effect<
|
|
491
491
|
PasskeyRegistrationCeremonyServices,
|
|
492
492
|
PasskeyConfigurationError | PersistenceMappingError,
|
|
493
|
-
RSetup | LifecycleHooks | NativeDatabase
|
|
493
|
+
RSetup | LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
494
494
|
> =>
|
|
495
495
|
Effect.gen(function* () {
|
|
496
496
|
const mapping = yield* capturedMapping(source, "registration", configuration);
|
|
@@ -508,7 +508,11 @@ export const makePasskeyTargetKernel = (
|
|
|
508
508
|
services: PasskeyPersistenceServices,
|
|
509
509
|
append: (statement: Statement<unknown>) => void,
|
|
510
510
|
) => Effect.Effect<A, E, R>,
|
|
511
|
-
): Effect.Effect<
|
|
511
|
+
): Effect.Effect<
|
|
512
|
+
A,
|
|
513
|
+
PasskeyCoordinatorError<E>,
|
|
514
|
+
R | RSetup | LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
515
|
+
> =>
|
|
512
516
|
Effect.flatMap(capturedMapping(source, "assertion", configuration), (mapping) =>
|
|
513
517
|
Effect.flatMap(LifecycleHooks, (hooks) =>
|
|
514
518
|
coordinateTransactionOwner(
|
|
@@ -545,7 +549,11 @@ export const makePasskeyTargetKernel = (
|
|
|
545
549
|
services: PasskeyRegistrationCeremonyServices,
|
|
546
550
|
append: (statement: Statement<unknown>) => void,
|
|
547
551
|
) => Effect.Effect<A, E, R>,
|
|
548
|
-
): Effect.Effect<
|
|
552
|
+
): Effect.Effect<
|
|
553
|
+
A,
|
|
554
|
+
PasskeyCoordinatorError<E>,
|
|
555
|
+
R | RSetup | LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
556
|
+
> =>
|
|
549
557
|
Effect.flatMap(capturedMapping(source, "registration", configuration), (mapping) =>
|
|
550
558
|
Effect.flatMap(LifecycleHooks, (hooks) =>
|
|
551
559
|
coordinateTransactionOwner(
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any -- existing mapped-row bridge; public adapters preserve table, ID, and Effect types. */
|
|
2
2
|
|
|
3
|
-
// oxlint-disable-next-line import/extensions -- Noble exposes explicit .js subpaths.
|
|
4
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
3
|
import {
|
|
6
4
|
type PasskeyActionAuthorization,
|
|
7
5
|
type PasskeyCeremony,
|
|
@@ -18,11 +16,11 @@ import {
|
|
|
18
16
|
PasskeyManagementPolicy,
|
|
19
17
|
snapshotPasskeySync,
|
|
20
18
|
} from "@yielded/auth/Passkey";
|
|
21
|
-
import {
|
|
19
|
+
import { type SubjectId } from "@yielded/auth/Schema";
|
|
22
20
|
import { SessionInvalidationWindow } from "@yielded/auth/Sessions";
|
|
23
21
|
import { DateTime, Effect, Schema } from "effect";
|
|
24
|
-
import { Base64Url } from "effect/encoding";
|
|
25
22
|
|
|
23
|
+
import { digest as digestText } from "../crypto";
|
|
26
24
|
import type { PersistenceMappingError } from "../mapping-error";
|
|
27
25
|
import type { QueryOperations } from "../query-operations";
|
|
28
26
|
import type { makeTransactionKernel } from "../transaction-kernel";
|
|
@@ -85,10 +83,7 @@ export const makePasskeyWriteStateKernel = (
|
|
|
85
83
|
const digest = <S extends Schema.Codec<unknown, unknown, never, never>>(
|
|
86
84
|
schema: S,
|
|
87
85
|
value: S["Type"],
|
|
88
|
-
) =>
|
|
89
|
-
TokenDigest.make(
|
|
90
|
-
Base64Url.encode(sha256(new TextEncoder().encode(jsonStorage(schema).encode(value)))),
|
|
91
|
-
);
|
|
86
|
+
) => digestText(jsonStorage(schema).encode(value));
|
|
92
87
|
|
|
93
88
|
const currentSubject = Effect.fn("passkey.currentWriteSubject")(function* (
|
|
94
89
|
mapping: any,
|
|
@@ -205,7 +200,7 @@ export const makePasskeyWriteStateKernel = (
|
|
|
205
200
|
read.subjectIds.equals(table.decodeSubjectId(copiedRow(row)), subject.nativeId),
|
|
206
201
|
);
|
|
207
202
|
const ownership = read.credentialOwnership;
|
|
208
|
-
const key = credentialKey(decoded.rpId, decoded.protocolCredentialId);
|
|
203
|
+
const key = yield* credentialKey(decoded.rpId, decoded.protocolCredentialId);
|
|
209
204
|
|
|
210
205
|
const tuple = (yield* owner.read(
|
|
211
206
|
ownership.table,
|
|
@@ -226,7 +221,7 @@ export const makePasskeyWriteStateKernel = (
|
|
|
226
221
|
row[table.credentialKey] === key,
|
|
227
222
|
);
|
|
228
223
|
const handle = read.handleOwnership;
|
|
229
|
-
const hashedHandle = handleKey(decoded.rpId, decoded.userHandle);
|
|
224
|
+
const hashedHandle = yield* handleKey(decoded.rpId, decoded.userHandle);
|
|
230
225
|
|
|
231
226
|
const held = (yield* owner.read(
|
|
232
227
|
handle.table,
|
|
@@ -470,9 +465,10 @@ export const makePasskeyWriteStateKernel = (
|
|
|
470
465
|
}
|
|
471
466
|
owner.postconditions.push(mapping.invalidation.postcondition(input));
|
|
472
467
|
const flow = mapping.flow;
|
|
468
|
+
const scope = yield* subjectScope(subject.subjectId);
|
|
473
469
|
|
|
474
470
|
const where = both(
|
|
475
|
-
equal(flow.table, { [flow.subjectScope]:
|
|
471
|
+
equal(flow.table, { [flow.subjectScope]: scope }),
|
|
476
472
|
sql`${col(flow.table, flow.state)} in (${flow.states.Pending}, ${flow.states.Claimed})`,
|
|
477
473
|
);
|
|
478
474
|
|
|
@@ -485,7 +481,7 @@ export const makePasskeyWriteStateKernel = (
|
|
|
485
481
|
for (const observation of owner.observations)
|
|
486
482
|
if (observation.table === flow.table)
|
|
487
483
|
observation.rows = observation.rows.map((row) =>
|
|
488
|
-
row[flow.subjectScope] ===
|
|
484
|
+
row[flow.subjectScope] === scope &&
|
|
489
485
|
[flow.states.Pending, flow.states.Claimed].includes(row[flow.state])
|
|
490
486
|
? { ...row, [flow.state]: flow.states.Rejected, [flow.version]: owner.marker }
|
|
491
487
|
: row,
|
|
@@ -559,8 +555,8 @@ export const makePasskeyWriteStateKernel = (
|
|
|
559
555
|
});
|
|
560
556
|
|
|
561
557
|
const input = { subjectId: subject.nativeId, credential, summary: safe, marker: owner.marker };
|
|
562
|
-
const tupleKey = credentialKey(credential.rpId, credential.protocolCredentialId);
|
|
563
|
-
const hashedHandle = handleKey(credential.rpId, credential.userHandle);
|
|
558
|
+
const tupleKey = yield* credentialKey(credential.rpId, credential.protocolCredentialId);
|
|
559
|
+
const hashedHandle = yield* handleKey(credential.rpId, credential.userHandle);
|
|
564
560
|
const tuple = read.credentialOwnership;
|
|
565
561
|
|
|
566
562
|
const absent = yield* owner.read(
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import * as M from "@yielded/auth/Passkey";
|
|
11
11
|
import { SubjectId } from "@yielded/auth/Schema";
|
|
12
12
|
import { SessionInvalidationWindow } from "@yielded/auth/Sessions";
|
|
13
|
-
import { Effect, Layer, Schema } from "effect";
|
|
13
|
+
import { type Crypto, Effect, Layer, Schema } from "effect";
|
|
14
14
|
import type { Statement } from "effect/sql/Statement";
|
|
15
15
|
|
|
16
16
|
import type { PasskeyMappingSource } from "../models/passkey-model";
|
|
@@ -371,7 +371,11 @@ export const makePasskeyWriteTargetKernel = (
|
|
|
371
371
|
services: PasskeyManagementServices,
|
|
372
372
|
append: (statement: Statement<unknown>) => void,
|
|
373
373
|
) => Effect.Effect<A, E, R>,
|
|
374
|
-
): Effect.Effect<
|
|
374
|
+
): Effect.Effect<
|
|
375
|
+
A,
|
|
376
|
+
PasskeyCoordinatorError<E>,
|
|
377
|
+
R | RSetup | LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
378
|
+
> =>
|
|
375
379
|
Effect.gen(function* () {
|
|
376
380
|
const mapping = yield* writeMapping(source, configuration, false);
|
|
377
381
|
const hooks = yield* LifecycleHooks;
|
|
@@ -406,7 +410,11 @@ export const makePasskeyWriteTargetKernel = (
|
|
|
406
410
|
services: PasskeyRegistrationServices<Value>,
|
|
407
411
|
append: (statement: Statement<unknown>) => void,
|
|
408
412
|
) => Effect.Effect<A, E, R>,
|
|
409
|
-
): Effect.Effect<
|
|
413
|
+
): Effect.Effect<
|
|
414
|
+
A,
|
|
415
|
+
PasskeyCoordinatorError<E>,
|
|
416
|
+
R | RSetup | LifecycleHooks | NativeDatabase | Crypto.Crypto
|
|
417
|
+
> =>
|
|
410
418
|
Effect.gen(function* () {
|
|
411
419
|
const mapping = yield* writeMapping(source, configuration, true);
|
|
412
420
|
const hooks = yield* LifecycleHooks;
|