@yielded/auth-persistence 0.1.0-beta.11 → 0.1.0-beta.12
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 +6 -1
- package/dist/Adapter.d.mts +2 -2
- package/dist/Adapter.mjs +4 -4
- package/dist/internal/PasswordPreparedPostconditions.d.mts +1 -1
- package/dist/internal/configuration.d.mts +1 -1
- package/dist/internal/email-kernel.d.mts +5 -5
- package/dist/internal/email-kernel.mjs +8 -6
- package/dist/internal/email-kernel.mjs.map +1 -1
- package/dist/internal/models/email-model.d.mts +1 -1
- package/dist/internal/oauth-server.mjs +1 -1
- package/dist/internal/oauth-server.mjs.map +1 -1
- package/dist/internal/passkey/target.mjs +21 -12
- package/dist/internal/passkey/target.mjs.map +1 -1
- package/dist/internal/passkey/write-target.mjs +6 -6
- package/dist/internal/passkey/write-target.mjs.map +1 -1
- package/dist/internal/passkey-kernel.d.mts +9 -12
- package/dist/internal/passkeys.mjs +8 -7
- package/dist/internal/passkeys.mjs.map +1 -1
- package/dist/internal/password-kernel.d.mts +3 -3
- package/dist/internal/password-kernel.mjs +3 -2
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +3 -2
- package/dist/internal/persistence.mjs +22 -26
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/proof-kernel.d.mts +2 -2
- package/dist/internal/proof-kernel.mjs +2 -1
- package/dist/internal/proof-kernel.mjs.map +1 -1
- package/dist/internal/registration.mjs +6 -1
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-kernel.d.mts +19 -9
- package/dist/internal/session-kernel.mjs +155 -147
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-persistence.mjs +2 -3
- package/dist/internal/sql-persistence.mjs.map +1 -1
- package/dist/internal/sql-query.mjs +4 -2
- package/dist/internal/sql-query.mjs.map +1 -1
- package/dist/internal/standalone.d.mts +2 -4
- package/dist/internal/standalone.mjs +2 -2
- package/dist/internal/standalone.mjs.map +1 -1
- package/dist/internal/storage-mapping.mjs +2 -2
- package/dist/internal/storage-validation.mjs +4 -2
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-execution-kernel.d.mts +6 -7
- package/dist/internal/transaction-execution-kernel.mjs +9 -7
- package/dist/internal/transaction-execution-kernel.mjs.map +1 -1
- package/dist/internal/transaction-kernel.d.mts +7 -3
- package/dist/internal/transaction-kernel.mjs +5 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +6 -1
- package/src/internal/email-kernel.ts +30 -35
- package/src/internal/oauth-server.ts +1 -1
- package/src/internal/passkey/target.ts +36 -43
- package/src/internal/passkey/write-target.ts +4 -14
- package/src/internal/passkeys.ts +4 -9
- package/src/internal/password-kernel.ts +2 -2
- package/src/internal/persistence.ts +77 -68
- package/src/internal/proof-kernel.ts +2 -2
- package/src/internal/registration.ts +22 -7
- package/src/internal/session-kernel.ts +1122 -1139
- package/src/internal/sql-persistence.ts +1 -1
- package/src/internal/sql-query.ts +5 -3
- package/src/internal/standalone.ts +5 -7
- package/src/internal/storage-validation.ts +20 -16
- package/src/internal/transaction-execution-kernel.ts +17 -15
- package/src/internal/transaction-kernel.ts +7 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { QueryFailure, QueryOperations } from "./query-operations.mjs";
|
|
2
2
|
import { AuthenticationAuthorityMapping, PendingAuthenticationMapping, SignedSessionValidityMapping, StatefulSessionMapping } from "./models/session-model.mjs";
|
|
3
3
|
import { SessionStepUpMapping } from "./models/step-up-model.mjs";
|
|
4
|
-
import { Context, Effect } from "effect";
|
|
4
|
+
import { Context, DateTime, Effect } from "effect";
|
|
5
|
+
import { SubjectId } from "@yielded/auth/Schema";
|
|
5
6
|
import { LifecycleHooks } from "@yielded/auth/Hooks";
|
|
6
|
-
import {
|
|
7
|
+
import { AuthenticationEvidence, AuthenticationRevision, PendingAuthentication, PendingAuthenticationInvalid, PendingConsumption, PrepareSessionCommit, SessionRepository, SessionStepUpPersistence, SessionUnavailable, SignedSessionValidity, StaleAuthentication, StatefulSessionPersistence } from "@yielded/auth/Sessions";
|
|
7
8
|
import * as SqlError$1 from "effect/sql/SqlError";
|
|
8
9
|
//#region src/internal/session-kernel.d.ts
|
|
9
10
|
type CommitMode = "interactive" | "synchronous";
|
|
@@ -23,7 +24,6 @@ export interface SessionSqlDatabase {
|
|
|
23
24
|
readonly delete: (...args: ReadonlyArray<any>) => SessionSqlQuery;
|
|
24
25
|
readonly transaction: <A, E, R>(body: (transaction: SessionSqlDatabase) => Effect.Effect<A, E, R>) => Effect.Effect<A, E | SqlError$1.SqlError, R>;
|
|
25
26
|
}
|
|
26
|
-
type Database = SessionSqlDatabase;
|
|
27
27
|
declare const CurrentSessionSql_base: Context.ServiceClass<CurrentSessionSql, "effect-auth/drizzle/CurrentSessionSql", SessionSqlDatabase>;
|
|
28
28
|
/** Native SQL authority for the current transaction or savepoint. */
|
|
29
29
|
export declare class CurrentSessionSql extends CurrentSessionSql_base {}
|
|
@@ -35,14 +35,24 @@ export interface SessionSqlOptions {
|
|
|
35
35
|
readonly standaloneGuard: Guard;
|
|
36
36
|
}
|
|
37
37
|
export declare const makeSessionKernel: (operations: QueryOperations) => {
|
|
38
|
-
makeSqlAuthenticationAuthority: <Claims>(
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
makeSqlAuthenticationAuthority: <Claims>(mapping: AuthenticationAuthorityMapping<Claims, any, any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<{
|
|
39
|
+
readonly capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) => Effect.Effect<AuthenticationRevision, StaleAuthentication | SessionUnavailable>;
|
|
40
|
+
readonly requirements: (evidence: AuthenticationEvidence) => Effect.Effect<import("@yielded/auth/Sessions").AuthenticationRequirement, StaleAuthentication | SessionUnavailable>;
|
|
41
|
+
readonly approve: <A>(input: {
|
|
42
|
+
readonly evidence: AuthenticationEvidence;
|
|
43
|
+
readonly now: DateTime.Utc;
|
|
44
|
+
readonly expiresAt: DateTime.Utc;
|
|
45
|
+
readonly absoluteExpiresAt: DateTime.Utc;
|
|
46
|
+
readonly pending?: PendingConsumption;
|
|
47
|
+
}, prepare: PrepareSessionCommit<void, A>) => Effect.Effect<import("@yielded/auth/Hooks").PreparedCommit<A>, StaleAuthentication | PendingAuthenticationInvalid | SessionUnavailable>;
|
|
48
|
+
}, never, CurrentSessionSql | LifecycleHooks>;
|
|
49
|
+
makeSqlPendingAuthentication: <Claims>(mapping: PendingAuthenticationMapping<Claims, any, any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<PendingAuthentication<Claims>, never, CurrentSessionSql | LifecycleHooks>;
|
|
50
|
+
makeSqlStatefulSessions: <Claims>(mapping: StatefulSessionMapping<Claims, any, any, any, any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<{
|
|
41
51
|
readonly statefulSessionPersistence: StatefulSessionPersistence<Claims>;
|
|
42
52
|
readonly sessionRepository: SessionRepository;
|
|
43
|
-
}, never, LifecycleHooks>;
|
|
44
|
-
makeSqlSignedValidity: (
|
|
45
|
-
makeSqlSessionStepUp: <Claims>(
|
|
53
|
+
}, never, CurrentSessionSql | LifecycleHooks>;
|
|
54
|
+
makeSqlSignedValidity: (mapping: SignedSessionValidityMapping<any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<SignedSessionValidity, never, CurrentSessionSql | LifecycleHooks>;
|
|
55
|
+
makeSqlSessionStepUp: <Claims>(mapping: SessionStepUpMapping<Claims, any, any, any, any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<SessionStepUpPersistence<Claims>, never, CurrentSessionSql | LifecycleHooks>;
|
|
46
56
|
};
|
|
47
57
|
//#endregion
|
|
48
58
|
//# sourceMappingURL=session-kernel.d.mts.map
|
|
@@ -179,7 +179,9 @@ const makeSessionKernel = (operations) => {
|
|
|
179
179
|
})
|
|
180
180
|
});
|
|
181
181
|
};
|
|
182
|
-
const makeSqlAuthenticationAuthority = (
|
|
182
|
+
const makeSqlAuthenticationAuthority = Effect.fnUntraced(function* (mapping, options) {
|
|
183
|
+
const database = yield* CurrentSessionSql;
|
|
184
|
+
const hooks = yield* LifecycleHooks;
|
|
183
185
|
const service = {
|
|
184
186
|
capture: (subjectId, credentialIds) => safeTransaction(inTransaction(database, captureIn(mapping, subjectId, credentialIds, true))),
|
|
185
187
|
requirements: (evidence) => safeTransaction(inTransaction(database, validateEvidenceIn(mapping, evidence, true).pipe(Effect.map((result) => result.requirement)))),
|
|
@@ -233,7 +235,9 @@ const makeSessionKernel = (operations) => {
|
|
|
233
235
|
if (DateTime.toEpochMillis(yield* freshNow) >= DateTime.toEpochMillis(record.expiresAt)) return yield* invalidPending();
|
|
234
236
|
return record;
|
|
235
237
|
});
|
|
236
|
-
const makeSqlPendingAuthentication = (
|
|
238
|
+
const makeSqlPendingAuthentication = Effect.fnUntraced(function* (mapping, options) {
|
|
239
|
+
const database = yield* CurrentSessionSql;
|
|
240
|
+
const hooks = yield* LifecycleHooks;
|
|
237
241
|
const service = {
|
|
238
242
|
create: (input, _inputNow, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
239
243
|
const transaction = yield* CurrentSessionSql;
|
|
@@ -302,7 +306,9 @@ const makeSessionKernel = (operations) => {
|
|
|
302
306
|
expiresAt: column(mapping.session.table, mapping.session.expiresAt),
|
|
303
307
|
absoluteExpiresAt: column(mapping.session.table, mapping.session.absoluteExpiresAt)
|
|
304
308
|
});
|
|
305
|
-
const makeSqlStatefulSessions = (
|
|
309
|
+
const makeSqlStatefulSessions = Effect.fnUntraced(function* (mapping, options) {
|
|
310
|
+
const database = yield* CurrentSessionSql;
|
|
311
|
+
const hooks = yield* LifecycleHooks;
|
|
306
312
|
const c = sessionColumns(mapping);
|
|
307
313
|
const persistence = {
|
|
308
314
|
establish: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
@@ -449,7 +455,9 @@ const makeSessionKernel = (operations) => {
|
|
|
449
455
|
sessionRepository: { list: (input) => repository.list(input).pipe(Effect.provideService(LifecycleHooks, hooks)) }
|
|
450
456
|
};
|
|
451
457
|
});
|
|
452
|
-
const makeSqlSignedValidity = (
|
|
458
|
+
const makeSqlSignedValidity = Effect.fnUntraced(function* (mapping, options) {
|
|
459
|
+
const database = yield* CurrentSessionSql;
|
|
460
|
+
const hooks = yield* LifecycleHooks;
|
|
453
461
|
const subject = subjectColumns(mapping);
|
|
454
462
|
const tombstone = {
|
|
455
463
|
subjectId: column(mapping.tombstone.table, mapping.tombstone.subjectId),
|
|
@@ -580,155 +588,155 @@ const makeSessionKernel = (operations) => {
|
|
|
580
588
|
if (cause.reasons.length > 0 && cause.reasons.every((reason) => Cause.isFailReason(reason) && isSqlStepUpRejectAbsence(reason.error))) return Effect.succeed(rejected);
|
|
581
589
|
return Effect.failCause(Cause.map(cause, (error) => isSqlStepUpRejectAbsence(error) ? unavailable() : error));
|
|
582
590
|
}));
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
...m.encodeInsert(record, { subjectId: authority.nativeSubjectId }),
|
|
602
|
-
...updateValues([
|
|
603
|
-
[m.digest, record.digest],
|
|
604
|
-
[m.version, version],
|
|
605
|
-
[m.flowId, record.flowId],
|
|
606
|
-
[m.subjectId, authority.nativeSubjectId],
|
|
607
|
-
[m.bindingDigest, record.bindingDigest],
|
|
608
|
-
[m.snapshot, snapshot],
|
|
609
|
-
[m.expiresAt, m.encodeInstant(record.expiresAt)],
|
|
610
|
-
[m.attemptLimit, record.attemptLimit],
|
|
611
|
-
[m.failedAttempts, 0],
|
|
612
|
-
[m.consumed, false]
|
|
613
|
-
])
|
|
614
|
-
};
|
|
615
|
-
const receipt = prepare(record, journal);
|
|
616
|
-
yield* tx.insert(m.table).values(values).pipe(normalizeMutation(mapping));
|
|
617
|
-
if ((yield* tx.select().from(m.table).where(and(eq(c.digest, record.digest), eq(c.version, version), eq(c.flowId, record.flowId), eq(c.subjectId, authority.nativeSubjectId), eq(c.bindingDigest, record.bindingDigest), eq(c.snapshot, snapshot), eq(c.expiresAt, m.encodeInstant(record.expiresAt)), eq(c.attemptLimit, record.attemptLimit), eq(c.failedAttempts, 0), eq(c.consumed, false))).limit(1))[0] === void 0) return yield* unavailable();
|
|
618
|
-
return receipt;
|
|
619
|
-
})).pipe((effect) => mapFailureCause(effect, (error) => error._tag === "SessionStepUpInvalid" ? stale() : error)),
|
|
620
|
-
context: (input) => mapFailureCause(read(input.digest).pipe(Effect.flatMap(({ intent }) => snapshotPendingAuthenticationContext({
|
|
621
|
-
flowId: intent.flowId,
|
|
622
|
-
bindingDigest: intent.bindingDigest,
|
|
623
|
-
revision: intent.revision,
|
|
624
|
-
expiresAtMillis: DateTime.toEpochMillis(intent.expiresAt)
|
|
625
|
-
}))), (error) => error._tag === "PendingAuthenticationInvalid" ? unavailable() : error),
|
|
626
|
-
read: (input) => read(input.digest).pipe(Effect.flatMap((value) => value.intent.bindingDigest === input.bindingDigest ? Effect.succeed(value.intent) : Effect.fail(SessionStepUpInvalid.make({})))),
|
|
627
|
-
reject: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
628
|
-
const tx = yield* CurrentSessionSql;
|
|
629
|
-
const journal = yield* CurrentCommitJournal;
|
|
630
|
-
const selected = yield* recoverSqlStepUpRejectAbsence(readSqlStepUp(mapping, input.digest, options.locking).pipe(Effect.map((value) => ({
|
|
631
|
-
_tag: "Found",
|
|
632
|
-
value
|
|
633
|
-
}))), { _tag: "Rejected" });
|
|
634
|
-
if (selected._tag === "Rejected") return prepare({ _tag: "Rejected" }, journal);
|
|
635
|
-
const { row, intent } = selected.value;
|
|
636
|
-
const receipt = prepare({ _tag: "Rejected" }, journal);
|
|
637
|
-
yield* tx.update(m.table).set(updateValues([[m.failedAttempts, sql`${c.failedAttempts} + 1`]])).where(and(eq(c.digest, input.digest), eq(c.snapshot, row[m.snapshot]), eq(c.version, intent.version), eq(c.consumed, false), sql`${c.failedAttempts} < ${c.attemptLimit}`));
|
|
638
|
-
const written = yield* stepUpLocator(mapping, input.digest, false);
|
|
639
|
-
if (written === void 0 || written[m.version] !== intent.version || written[m.snapshot] !== row[m.snapshot] || written[m.failedAttempts] !== row[m.failedAttempts] + 1) return yield* unavailable();
|
|
640
|
-
return receipt;
|
|
641
|
-
})),
|
|
642
|
-
complete: (plan, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
643
|
-
const tx = yield* CurrentSessionSql;
|
|
644
|
-
const journal = yield* CurrentCommitJournal;
|
|
645
|
-
yield* validateStepUpPlan(plan);
|
|
646
|
-
const current = yield* stepUpAuthority(mapping, plan.evidence.revision, options.locking);
|
|
647
|
-
const stored = yield* readSqlStepUp(mapping, plan.intent.digest, options.locking);
|
|
648
|
-
if ((yield* encodeStepUpIntent(stored.intent)) !== (yield* encodeStepUpIntent(plan.intent))) return yield* SessionStepUpInvalid.make({});
|
|
649
|
-
const base = yield* assessAuthentication(plan.evidence, current.requirement).pipe(Effect.mapError(() => stale()));
|
|
650
|
-
const profile = yield* assessAuthentication(plan.evidence, stored.intent.requirement).pipe(Effect.mapError(() => stale()));
|
|
651
|
-
if (!base.satisfied || !profile.satisfied) return yield* stale();
|
|
652
|
-
const replacement = plan.replacement, now = yield* freshNow;
|
|
653
|
-
if (DateTime.toEpochMillis(replacement.inspection.session.expiresAt) > DateTime.toEpochMillis(stored.intent.sourceAbsoluteExpiresAt) || DateTime.toEpochMillis(now) < DateTime.toEpochMillis(replacement.inspection.session.issuedAt) || DateTime.toEpochMillis(now) >= Math.min(DateTime.toEpochMillis(replacement.inspection.session.expiresAt), DateTime.toEpochMillis(stored.intent.expiresAt))) return yield* SessionStepUpInvalid.make({});
|
|
654
|
-
let rotation;
|
|
655
|
-
if (replacement._tag === "Stateful") {
|
|
656
|
-
if (mapping.source.kind !== "Stateful") return yield* SessionStepUpInvalid.make({});
|
|
657
|
-
const s = mapping.source.session, row = stored.source.row;
|
|
658
|
-
if (row[s.digest] !== replacement.expectedDigest || row[s.version] !== replacement.expectedRowVersion) return yield* SessionConflict.make({});
|
|
659
|
-
const version = yield* allocate(options.mode, s.allocateVersion, s.allocateVersionSync);
|
|
660
|
-
const next = {
|
|
661
|
-
...replacement.inspection.session,
|
|
662
|
-
provenance: replacement.inspection.provenance,
|
|
663
|
-
credentialVersion: replacement.inspection.credentialVersion,
|
|
664
|
-
digest: replacement.nextDigest,
|
|
591
|
+
return {
|
|
592
|
+
makeSqlAuthenticationAuthority,
|
|
593
|
+
makeSqlPendingAuthentication,
|
|
594
|
+
makeSqlStatefulSessions,
|
|
595
|
+
makeSqlSignedValidity,
|
|
596
|
+
makeSqlSessionStepUp: Effect.fnUntraced(function* (mapping, options) {
|
|
597
|
+
const database = yield* CurrentSessionSql;
|
|
598
|
+
const hooks = yield* LifecycleHooks;
|
|
599
|
+
const m = mapping.intent, c = stepUpColumns(mapping);
|
|
600
|
+
const read = (digest) => mapFailureCause(safeTransaction(inTransaction(database, readSqlStepUp(mapping, digest, options.locking))), (error) => error._tag === "StaleAuthentication" ? SessionStepUpInvalid.make({}) : error);
|
|
601
|
+
const service = {
|
|
602
|
+
create: (input, _now, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
603
|
+
const tx = yield* CurrentSessionSql;
|
|
604
|
+
const journal = yield* CurrentCommitJournal;
|
|
605
|
+
const authority = yield* stepUpAuthority(mapping, input.revision, options.locking);
|
|
606
|
+
const version = yield* allocate(options.mode, m.allocateVersion, m.allocateVersionSync);
|
|
607
|
+
const record = {
|
|
608
|
+
...input,
|
|
665
609
|
version
|
|
666
610
|
};
|
|
667
|
-
|
|
668
|
-
|
|
611
|
+
yield* stepUpSource(mapping, record, authority.nativeSubjectId, yield* freshNow, options.locking);
|
|
612
|
+
if ((yield* selectRows(tx.select().from(m.table).where(or(eq(c.digest, input.digest), eq(c.flowId, input.flowId))).limit(1), options.locking)).length > 0) return yield* SessionConflict.make({});
|
|
613
|
+
const snapshot = yield* encodeStepUpIntent(record);
|
|
614
|
+
const values = {
|
|
615
|
+
...m.encodeInsert(record, { subjectId: authority.nativeSubjectId }),
|
|
669
616
|
...updateValues([
|
|
670
|
-
[
|
|
671
|
-
[
|
|
672
|
-
[
|
|
673
|
-
[
|
|
674
|
-
[
|
|
675
|
-
[
|
|
676
|
-
[
|
|
617
|
+
[m.digest, record.digest],
|
|
618
|
+
[m.version, version],
|
|
619
|
+
[m.flowId, record.flowId],
|
|
620
|
+
[m.subjectId, authority.nativeSubjectId],
|
|
621
|
+
[m.bindingDigest, record.bindingDigest],
|
|
622
|
+
[m.snapshot, snapshot],
|
|
623
|
+
[m.expiresAt, m.encodeInstant(record.expiresAt)],
|
|
624
|
+
[m.attemptLimit, record.attemptLimit],
|
|
625
|
+
[m.failedAttempts, 0],
|
|
626
|
+
[m.consumed, false]
|
|
677
627
|
])
|
|
678
628
|
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
629
|
+
const receipt = prepare(record, journal);
|
|
630
|
+
yield* tx.insert(m.table).values(values).pipe(normalizeMutation(mapping));
|
|
631
|
+
if ((yield* tx.select().from(m.table).where(and(eq(c.digest, record.digest), eq(c.version, version), eq(c.flowId, record.flowId), eq(c.subjectId, authority.nativeSubjectId), eq(c.bindingDigest, record.bindingDigest), eq(c.snapshot, snapshot), eq(c.expiresAt, m.encodeInstant(record.expiresAt)), eq(c.attemptLimit, record.attemptLimit), eq(c.failedAttempts, 0), eq(c.consumed, false))).limit(1))[0] === void 0) return yield* unavailable();
|
|
632
|
+
return receipt;
|
|
633
|
+
})).pipe((effect) => mapFailureCause(effect, (error) => error._tag === "SessionStepUpInvalid" ? stale() : error)),
|
|
634
|
+
context: (input) => mapFailureCause(read(input.digest).pipe(Effect.flatMap(({ intent }) => snapshotPendingAuthenticationContext({
|
|
635
|
+
flowId: intent.flowId,
|
|
636
|
+
bindingDigest: intent.bindingDigest,
|
|
637
|
+
revision: intent.revision,
|
|
638
|
+
expiresAtMillis: DateTime.toEpochMillis(intent.expiresAt)
|
|
639
|
+
}))), (error) => error._tag === "PendingAuthenticationInvalid" ? unavailable() : error),
|
|
640
|
+
read: (input) => read(input.digest).pipe(Effect.flatMap((value) => value.intent.bindingDigest === input.bindingDigest ? Effect.succeed(value.intent) : Effect.fail(SessionStepUpInvalid.make({})))),
|
|
641
|
+
reject: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
642
|
+
const tx = yield* CurrentSessionSql;
|
|
643
|
+
const journal = yield* CurrentCommitJournal;
|
|
644
|
+
const selected = yield* recoverSqlStepUpRejectAbsence(readSqlStepUp(mapping, input.digest, options.locking).pipe(Effect.map((value) => ({
|
|
645
|
+
_tag: "Found",
|
|
646
|
+
value
|
|
647
|
+
}))), { _tag: "Rejected" });
|
|
648
|
+
if (selected._tag === "Rejected") return prepare({ _tag: "Rejected" }, journal);
|
|
649
|
+
const { row, intent } = selected.value;
|
|
650
|
+
const receipt = prepare({ _tag: "Rejected" }, journal);
|
|
651
|
+
yield* tx.update(m.table).set(updateValues([[m.failedAttempts, sql`${c.failedAttempts} + 1`]])).where(and(eq(c.digest, input.digest), eq(c.snapshot, row[m.snapshot]), eq(c.version, intent.version), eq(c.consumed, false), sql`${c.failedAttempts} < ${c.attemptLimit}`));
|
|
652
|
+
const written = yield* stepUpLocator(mapping, input.digest, false);
|
|
653
|
+
if (written === void 0 || written[m.version] !== intent.version || written[m.snapshot] !== row[m.snapshot] || written[m.failedAttempts] !== row[m.failedAttempts] + 1) return yield* unavailable();
|
|
654
|
+
return receipt;
|
|
655
|
+
})),
|
|
656
|
+
complete: (plan, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
657
|
+
const tx = yield* CurrentSessionSql;
|
|
658
|
+
const journal = yield* CurrentCommitJournal;
|
|
659
|
+
yield* validateStepUpPlan(plan);
|
|
660
|
+
const current = yield* stepUpAuthority(mapping, plan.evidence.revision, options.locking);
|
|
661
|
+
const stored = yield* readSqlStepUp(mapping, plan.intent.digest, options.locking);
|
|
662
|
+
if ((yield* encodeStepUpIntent(stored.intent)) !== (yield* encodeStepUpIntent(plan.intent))) return yield* SessionStepUpInvalid.make({});
|
|
663
|
+
const base = yield* assessAuthentication(plan.evidence, current.requirement).pipe(Effect.mapError(() => stale()));
|
|
664
|
+
const profile = yield* assessAuthentication(plan.evidence, stored.intent.requirement).pipe(Effect.mapError(() => stale()));
|
|
665
|
+
if (!base.satisfied || !profile.satisfied) return yield* stale();
|
|
666
|
+
const replacement = plan.replacement, now = yield* freshNow;
|
|
667
|
+
if (DateTime.toEpochMillis(replacement.inspection.session.expiresAt) > DateTime.toEpochMillis(stored.intent.sourceAbsoluteExpiresAt) || DateTime.toEpochMillis(now) < DateTime.toEpochMillis(replacement.inspection.session.issuedAt) || DateTime.toEpochMillis(now) >= Math.min(DateTime.toEpochMillis(replacement.inspection.session.expiresAt), DateTime.toEpochMillis(stored.intent.expiresAt))) return yield* SessionStepUpInvalid.make({});
|
|
668
|
+
let rotation;
|
|
669
|
+
if (replacement._tag === "Stateful") {
|
|
670
|
+
if (mapping.source.kind !== "Stateful") return yield* SessionStepUpInvalid.make({});
|
|
671
|
+
const s = mapping.source.session, row = stored.source.row;
|
|
672
|
+
if (row[s.digest] !== replacement.expectedDigest || row[s.version] !== replacement.expectedRowVersion) return yield* SessionConflict.make({});
|
|
673
|
+
const version = yield* allocate(options.mode, s.allocateVersion, s.allocateVersionSync);
|
|
674
|
+
const next = {
|
|
675
|
+
...replacement.inspection.session,
|
|
676
|
+
provenance: replacement.inspection.provenance,
|
|
677
|
+
credentialVersion: replacement.inspection.credentialVersion,
|
|
678
|
+
digest: replacement.nextDigest,
|
|
679
|
+
version
|
|
680
|
+
};
|
|
681
|
+
rotation = {
|
|
682
|
+
...s.encodeRotation(next),
|
|
683
|
+
...updateValues([
|
|
684
|
+
[s.version, version],
|
|
685
|
+
[s.digest, next.digest],
|
|
686
|
+
[s.credentialVersion, next.credentialVersion],
|
|
687
|
+
[s.authenticatedAt, s.encodeInstant(next.assurance.authenticatedAt)],
|
|
688
|
+
[s.issuedAt, s.encodeInstant(next.issuedAt)],
|
|
689
|
+
[s.expiresAt, s.encodeInstant(next.expiresAt)],
|
|
690
|
+
[s.absoluteExpiresAt, s.encodeInstant(next.absoluteExpiresAt)]
|
|
691
|
+
])
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const completionVersion = yield* allocate(options.mode, m.allocateVersion, m.allocateVersionSync);
|
|
695
|
+
if (completionVersion === stored.intent.version) return yield* unavailable();
|
|
696
|
+
const completionSnapshot = yield* encodeStepUpIntent({
|
|
697
|
+
...stored.intent,
|
|
698
|
+
version: completionVersion
|
|
702
699
|
});
|
|
703
|
-
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
700
|
+
const receipt = prepare(replacement.inspection, journal);
|
|
701
|
+
if (replacement._tag === "Stateful" && mapping.source.kind === "Stateful") {
|
|
702
|
+
const s = mapping.source.session, sc = sessionColumns(mapping.source);
|
|
703
|
+
yield* tx.update(s.table).set(rotation).where(and(eq(sc.sessionId, stored.source.nativeSessionId), eq(sc.digest, replacement.expectedDigest), eq(sc.version, replacement.expectedRowVersion)));
|
|
704
|
+
const written = (yield* tx.select().from(s.table).where(eq(sc.sessionId, stored.source.nativeSessionId)).limit(1))[0];
|
|
705
|
+
if (written === void 0 || written[s.version] !== rotation[s.version]) return yield* unavailable();
|
|
706
|
+
const decoded = yield* s.decode(written);
|
|
707
|
+
if (decoded.version !== rotation[s.version] || !stepUpRotationMatches(plan, decoded)) return yield* unavailable();
|
|
708
|
+
} else if (replacement._tag === "StateAssistedSigned" && mapping.source.kind === "StateAssistedSigned") {
|
|
709
|
+
const t = mapping.source.tombstone;
|
|
710
|
+
const owner = column(t.table, t.subjectId), id = column(t.table, t.sessionId), expiry = column(t.table, t.absoluteExpiresAt);
|
|
711
|
+
yield* tx.delete(t.table).where(and(eq(owner, stored.nativeSubjectId), eq(id, stored.source.nativeSessionId), lte(expiry, t.encodeInstant(yield* freshNow))));
|
|
712
|
+
const values = t.encodeInsert({
|
|
713
|
+
subjectId: stored.nativeSubjectId,
|
|
714
|
+
sessionId: stored.source.nativeSessionId,
|
|
715
|
+
absoluteExpiresAt: replacement.tombstoneUntil
|
|
716
|
+
});
|
|
717
|
+
yield* tx.insert(t.table).values(values).pipe(normalizeMutation(mapping));
|
|
718
|
+
if ((yield* tx.select().from(t.table).where(and(eq(owner, stored.nativeSubjectId), eq(id, stored.source.nativeSessionId), eq(expiry, t.encodeInstant(replacement.tombstoneUntil)))).limit(1))[0] === void 0) return yield* unavailable();
|
|
719
|
+
}
|
|
720
|
+
yield* tx.update(m.table).set(updateValues([
|
|
721
|
+
[m.consumed, true],
|
|
722
|
+
[m.version, completionVersion],
|
|
723
|
+
[m.snapshot, completionSnapshot]
|
|
724
|
+
])).where(and(eq(c.digest, stored.intent.digest), eq(c.version, stored.intent.version), eq(c.snapshot, stored.row[m.snapshot]), eq(c.consumed, false), eq(c.failedAttempts, stored.row[m.failedAttempts])));
|
|
725
|
+
const completed = yield* stepUpLocator(mapping, stored.intent.digest, false);
|
|
726
|
+
if (completed === void 0 || !completed[m.consumed] || completed[m.version] !== completionVersion || completed[m.snapshot] !== completionSnapshot) return yield* unavailable();
|
|
727
|
+
const commitNow = yield* freshNow;
|
|
728
|
+
if (!stepUpIntentLive(stored.intent, mapping.source.kind, commitNow) || DateTime.toEpochMillis(commitNow) >= DateTime.toEpochMillis(replacement.inspection.session.expiresAt) || !(yield* assessAuthentication(plan.evidence, current.requirement).pipe(Effect.mapError(() => stale()))).satisfied || !(yield* assessAuthentication(plan.evidence, stored.intent.requirement).pipe(Effect.mapError(() => stale()))).satisfied) return yield* stale();
|
|
729
|
+
return receipt;
|
|
730
|
+
}))
|
|
731
|
+
};
|
|
732
|
+
return {
|
|
733
|
+
create: (input, now, prepare) => service.create(input, now, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
734
|
+
context: (input) => service.context(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
735
|
+
read: (input) => service.read(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
736
|
+
reject: (input, prepare) => service.reject(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
737
|
+
complete: (plan, prepare) => service.complete(plan, prepare).pipe(Effect.provideService(LifecycleHooks, hooks))
|
|
738
|
+
};
|
|
739
|
+
})
|
|
732
740
|
};
|
|
733
741
|
};
|
|
734
742
|
//#endregion
|