@yielded/auth-persistence 0.1.0-beta.30 → 0.1.0-beta.31
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 +3 -6
- package/dist/Adapter.d.mts +1 -1
- package/dist/internal/configuration.d.mts +18 -8
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/models/password-model.d.mts +2 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/passkey-managed.mjs +4 -2
- package/dist/internal/passkey-managed.mjs.map +1 -1
- package/dist/internal/password-native.mjs +14 -0
- package/dist/internal/password-native.mjs.map +1 -1
- package/dist/internal/password-registration-native.d.mts +1 -1
- package/dist/internal/persistence.d.mts +3 -0
- package/dist/internal/persistence.mjs +4 -6
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/registration.mjs +17 -8
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +2 -1
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.mjs +2 -1
- package/dist/internal/session-native-login.mjs.map +1 -1
- package/dist/internal/session-native-pending-read.mjs +1 -1
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +21 -8
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.mjs +2 -1
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.mjs +4 -2
- package/dist/internal/session-native-step-up.mjs.map +1 -1
- package/dist/internal/sqlite-policy-guard.mjs +23 -0
- package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
- package/dist/internal/storage-mapping.d.mts +9 -5
- package/dist/internal/storage-mapping.mjs +16 -4
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/package.json +1 -1
- package/src/internal/configuration.ts +26 -8
- package/src/internal/models/password-model.ts +2 -1
- package/src/internal/passkey-managed.ts +8 -2
- package/src/internal/password-native.ts +28 -2
- package/src/internal/persistence.ts +174 -164
- package/src/internal/registration.ts +53 -40
- package/src/internal/session-native-authority.ts +3 -1
- package/src/internal/session-native-login.ts +3 -1
- package/src/internal/session-native-pending-read.ts +1 -1
- package/src/internal/session-native-state.ts +29 -10
- package/src/internal/session-native-stateful.ts +3 -1
- package/src/internal/session-native-step-up.ts +5 -2
- package/src/internal/sqlite-policy-guard.ts +34 -0
- package/src/internal/storage-mapping.ts +40 -4
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
2
|
+
import { nativeSqlAlias } from "./native-sql-table.mjs";
|
|
2
3
|
import { anySqlCondition, exactSqlText } from "./sql-change.mjs";
|
|
4
|
+
import { guardSqlitePolicy } from "./sqlite-policy-guard.mjs";
|
|
3
5
|
import { Cause, DateTime, Effect, Schema } from "effect";
|
|
6
|
+
import { fragment, parameter } from "effect/sql/Statement";
|
|
4
7
|
import { reportAuthFailure } from "@yielded/auth/Persistence";
|
|
5
8
|
import { AuthenticationRevision, PendingAuthenticationInvalid, SessionConflict, SessionInvalid, SessionStepUpInvalid, SessionUnavailable, StaleAuthentication, assessAuthentication } from "@yielded/auth/Sessions";
|
|
6
9
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
@@ -44,7 +47,8 @@ const makeNativeSessionAuthorityState = Effect.fnUntraced(function* (tables, map
|
|
|
44
47
|
return id(table, c.status, c.activeStatusValue);
|
|
45
48
|
};
|
|
46
49
|
const requested = (table, ids) => anySqlCondition(ids.map((value) => exact(table, c.credentialId, value)));
|
|
47
|
-
const decode = Effect.fnUntraced(function* (subjectId, native,
|
|
50
|
+
const decode = Effect.fnUntraced(function* (subjectId, native, projected, factors, instant) {
|
|
51
|
+
const row = subject.decode(projected, "s_");
|
|
48
52
|
sessionInvariant((yield* mapping.subjectId.toSubject(row[s.id])) === subjectId);
|
|
49
53
|
if (!s.isActiveStatus(row[s.status])) return void 0;
|
|
50
54
|
const credentials = [];
|
|
@@ -66,6 +70,10 @@ const makeNativeSessionAuthorityState = Effect.fnUntraced(function* (tables, map
|
|
|
66
70
|
return {
|
|
67
71
|
native,
|
|
68
72
|
row,
|
|
73
|
+
policyValues: batch ? (s.requirementColumns ?? []).map((key) => ({
|
|
74
|
+
column: subject.selectedColumn(key),
|
|
75
|
+
value: fragment([parameter(projected[nativeSqlAlias("s_", subject.keys.indexOf(key))])])
|
|
76
|
+
})) : [],
|
|
69
77
|
revision: yield* Schema.decodeUnknownEffect(AuthenticationRevision)({
|
|
70
78
|
subjectId,
|
|
71
79
|
securityRevision: row[s.securityRevision],
|
|
@@ -83,17 +91,22 @@ const makeNativeSessionAuthorityState = Effect.fnUntraced(function* (tables, map
|
|
|
83
91
|
sessionInvariant(owners.length <= 1);
|
|
84
92
|
if (owners[0] === void 0) return void 0;
|
|
85
93
|
const factors = yield* sql`select ${credential.fields("c_")} from ${credential.name} where ${id(credential, c.subjectId, native)} and ${activeCredential(credential)} order by ${credential.column(c.credentialId)} limit 65 ${lock}`;
|
|
86
|
-
return yield* decode(subjectId, native,
|
|
94
|
+
return yield* decode(subjectId, native, owners[0], factors.map((row) => credential.decode(row, "c_")), owners[0].engine_now);
|
|
87
95
|
}
|
|
88
96
|
const rows = yield* sql`select ${joinedSubject.fields("s_")}, ${joinedCredential.fields("c_")}, ${now} as engine_now from ${joinedSubject.name} left join ${joinedCredential.name} on ${id(joinedCredential, c.subjectId, native)} and ${activeCredential(joinedCredential)} where ${id(joinedSubject, s.id, native)} and ${activeSubject(joinedSubject)} limit 65`;
|
|
89
97
|
if (rows[0] === void 0) return void 0;
|
|
90
|
-
return yield* decode(subjectId, native,
|
|
98
|
+
return yield* decode(subjectId, native, rows[0], rows.map((row) => joinedCredential.decode(row, "c_")), rows[0].engine_now);
|
|
91
99
|
});
|
|
92
|
-
const
|
|
93
|
-
if (!batch) return
|
|
100
|
+
const guardPolicy = Effect.fnUntraced(function* (current) {
|
|
101
|
+
if (!batch) return;
|
|
94
102
|
sessionInvariant(s.requirementColumns !== void 0);
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
yield* guardSqlitePolicy({
|
|
104
|
+
name: "session-subject-policy",
|
|
105
|
+
table: subject.name,
|
|
106
|
+
owner: id(subject, s.id, current.native),
|
|
107
|
+
values: current.policyValues
|
|
108
|
+
});
|
|
109
|
+
});
|
|
97
110
|
const authorityCondition = (revision, native) => sql.and([
|
|
98
111
|
sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, revision.securityRevision)} and ${activeSubject(subject)})`,
|
|
99
112
|
sql`(select count(*) from ${credential.name} where ${id(credential, c.subjectId, native)} and ${activeCredential(credential)}) = ${revision.credentials.length}`,
|
|
@@ -116,7 +129,7 @@ const makeNativeSessionAuthorityState = Effect.fnUntraced(function* (tables, map
|
|
|
116
129
|
decode,
|
|
117
130
|
read,
|
|
118
131
|
authorityCondition,
|
|
119
|
-
|
|
132
|
+
guardPolicy
|
|
120
133
|
};
|
|
121
134
|
});
|
|
122
135
|
/** Evaluate freshness at the database sample. The final write also enforces the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-state.mjs","names":[],"sources":["../../src/internal/session-native-state.ts"],"sourcesContent":["import { reportAuthFailure } from \"@yielded/auth/Persistence\";\nimport type { SubjectId } from \"@yielded/auth/Schema\";\nimport {\n type AuthenticationRequirement,\n AuthenticationRevision,\n SessionConflict,\n SessionInvalid,\n SessionUnavailable,\n SessionStepUpInvalid,\n PendingAuthenticationInvalid,\n StaleAuthentication,\n assessAuthentication,\n type AuthenticationEvidence,\n} from \"@yielded/auth/Sessions\";\nimport { Cause, Effect, Schema, DateTime } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { SessionAuthorityTables, SessionExecution } from \"./models/session-model\";\nimport type { NativeSqlTables, SqlTable } from \"./native-sql-table\";\nimport { anySqlCondition, exactSqlText } from \"./sql-change\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport const sessionUnavailable = () => SessionUnavailable.make({});\n\nexport const sessionInvariant: (condition: unknown) => asserts condition = (condition) => {\n if (!condition)\n throw PersistenceMappingError.make({ operation: \"decode\", cause: \"Invalid session authority\" });\n};\n\nexport type SessionDomainFailure =\n | SessionConflict\n | SessionInvalid\n | SessionUnavailable\n | SessionStepUpInvalid\n | PendingAuthenticationInvalid\n | StaleAuthentication;\n\nconst isDomainFailure = (error: unknown): error is SessionDomainFailure =>\n Schema.is(SessionConflict)(error) ||\n Schema.is(SessionInvalid)(error) ||\n Schema.is(SessionUnavailable)(error) ||\n Schema.is(SessionStepUpInvalid)(error) ||\n Schema.is(PendingAuthenticationInvalid)(error) ||\n Schema.is(StaleAuthentication)(error);\n\n/** Only schema-checked domain failures cross the owner boundary; SQL/mapping\n * failures remain Unavailable. The executor preserves every mixed cause reason. */\nexport const sessionFailure = <E>(\n error: E,\n): Extract<E, SessionDomainFailure> | SessionUnavailable =>\n isDomainFailure(error) ? (error as Extract<E, SessionDomainFailure>) : sessionUnavailable();\n\nexport const normalizeSessionOperation = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n effect.pipe(\n Effect.tapError((error) =>\n isDomainFailure(error)\n ? Effect.void\n : reportAuthFailure(\"auth-persistence\", Cause.fail(error)),\n ),\n Effect.mapError(sessionFailure),\n );\n\nexport type NativeSessionAuthorityMapping = SessionAuthorityTables<\n AnyTableModel,\n AnyTableModel,\n unknown\n> &\n SessionExecution;\n\nexport interface NativeSessionAuthority {\n readonly native: unknown;\n readonly row: Record<string, unknown>;\n readonly revision: AuthenticationRevision;\n readonly requirement: AuthenticationRequirement;\n readonly now: number;\n}\n\nexport const sameSessionRevision = (left: AuthenticationRevision, right: AuthenticationRevision) =>\n left.subjectId === right.subjectId &&\n left.securityRevision === right.securityRevision &&\n left.credentials.length === right.credentials.length &&\n new Set(left.credentials.map((f) => f.credentialId)).size === left.credentials.length &&\n left.credentials.every((f) =>\n right.credentials.some(\n (other) => other.credentialId === f.credentialId && other.revision === f.revision,\n ),\n );\n\n/** Known-subject joins bind each physical ID column independently. Subject locks\n * precede every credential lock, including inside an application coordinator. */\nexport const makeNativeSessionAuthorityState = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: NativeSessionAuthorityMapping,\n batch = false,\n) {\n const sql = (yield* SqlClient).withoutTransforms();\n\n const s = mapping.subject,\n c = mapping.credential;\n\n if (batch) sessionInvariant(s.requirementColumns !== undefined);\n\n const subject = tables(s.table),\n credential = tables(c.table);\n\n const joinedSubject = subject.as(\"session_subject\"),\n joinedCredential = credential.as(\"session_factor\");\n\n const now = tables.expression(mapping.clock.engineNowMillis);\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n const nativeLocks = !batch && sql.onDialectOrElse({ sqlite: () => false, orElse: () => true });\n\n const exact = (table: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, table.column(key), table.value(key, value));\n\n const id = (table: SqlTable, key: string, value: unknown) =>\n sql`${table.column(key)} = ${table.value(key, value)}`;\n\n const activeSubject = (table: SqlTable) => id(table, s.status, s.activeStatusValue);\n\n const activeCredential = (table: SqlTable) => {\n if (c.status === undefined) return sql`1 = 1`;\n sessionInvariant(c.activeStatusValue !== undefined);\n\n return id(table, c.status, c.activeStatusValue);\n };\n\n const requested = (table: SqlTable, ids: ReadonlyArray<string>) =>\n anySqlCondition(ids.map((value) => exact(table, c.credentialId, value)));\n\n const decode = Effect.fnUntraced(function* (\n subjectId: SubjectId,\n native: unknown,\n row: Record<string, unknown>,\n factors: ReadonlyArray<Record<string, unknown>>,\n instant: unknown,\n ): Effect.fn.Return<\n NativeSessionAuthority | undefined,\n PersistenceMappingError | Schema.SchemaError\n > {\n sessionInvariant((yield* mapping.subjectId.toSubject(row[s.id])) === subjectId);\n if (!s.isActiveStatus(row[s.status])) return undefined;\n const credentials: { credentialId: string; revision: string }[] = [];\n\n for (const factor of factors) {\n if (factor[c.credentialId] === null || factor[c.credentialId] === undefined) continue;\n sessionInvariant((yield* mapping.subjectId.toSubject(factor[c.subjectId])) === subjectId);\n if (c.status !== undefined) sessionInvariant(c.isActiveStatus?.(factor[c.status]) === true);\n\n const value = yield* Schema.decodeUnknownEffect(\n Schema.Struct({ credentialId: Schema.String, revision: Schema.String }),\n )({ credentialId: factor[c.credentialId], revision: factor[c.revision] });\n\n sessionInvariant(!credentials.some((other) => other.credentialId === value.credentialId));\n credentials.push(value);\n }\n sessionInvariant(credentials.length <= 64);\n\n return {\n native,\n row,\n revision: yield* Schema.decodeUnknownEffect(AuthenticationRevision)({\n subjectId,\n securityRevision: row[s.securityRevision],\n credentials: credentials.sort((a, b) => a.credentialId.localeCompare(b.credentialId)),\n }),\n requirement: yield* s.decodeRequirement(row),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(instant)),\n };\n });\n\n const read = Effect.fnUntraced(function* (subjectId: SubjectId, locking = false) {\n const native = yield* mapping.subjectId.toNative(subjectId);\n\n sessionInvariant((yield* mapping.subjectId.toSubject(native)) === subjectId);\n if (locking && nativeLocks) {\n const owners =\n yield* sql`select ${subject.fields(\"s_\")}, ${now} as engine_now from ${subject.name} where ${id(subject, s.id, native)} and ${activeSubject(subject)} limit 2 ${lock}`;\n\n sessionInvariant(owners.length <= 1);\n if (owners[0] === undefined) return undefined;\n\n const factors =\n yield* sql`select ${credential.fields(\"c_\")} from ${credential.name} where ${id(credential, c.subjectId, native)} and ${activeCredential(credential)} order by ${credential.column(c.credentialId)} limit 65 ${lock}`;\n\n return yield* decode(\n subjectId,\n native,\n subject.decode(owners[0], \"s_\"),\n factors.map((row) => credential.decode(row, \"c_\")),\n owners[0].engine_now,\n );\n }\n\n const rows =\n yield* sql`select ${joinedSubject.fields(\"s_\")}, ${joinedCredential.fields(\"c_\")}, ${now} as engine_now from ${joinedSubject.name} left join ${joinedCredential.name} on ${id(joinedCredential, c.subjectId, native)} and ${activeCredential(joinedCredential)} where ${id(joinedSubject, s.id, native)} and ${activeSubject(joinedSubject)} limit 65`;\n\n if (rows[0] === undefined) return undefined;\n\n return yield* decode(\n subjectId,\n native,\n joinedSubject.decode(rows[0], \"s_\"),\n rows.map((row) => joinedCredential.decode(row, \"c_\")),\n rows[0].engine_now,\n );\n });\n\n const policyCondition = (row: Record<string, unknown>, native: unknown): Fragment => {\n if (!batch) return sql`1 = 1`;\n sessionInvariant(s.requirementColumns !== undefined);\n\n return sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${sql.and(s.requirementColumns.map((key) => (row[key] === null ? sql`${subject.column(key)} is null` : typeof row[key] === \"string\" ? exact(subject, key, row[key]) : id(subject, key, row[key]))))})`;\n };\n\n const authorityCondition = (revision: AuthenticationRevision, native: unknown): Fragment =>\n sql.and([\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, revision.securityRevision)} and ${activeSubject(subject)})`,\n sql`(select count(*) from ${credential.name} where ${id(credential, c.subjectId, native)} and ${activeCredential(credential)}) = ${revision.credentials.length}`,\n ...revision.credentials.map(\n (factor) =>\n sql`exists(select 1 from ${credential.name} where ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialId, factor.credentialId)} and ${exact(credential, c.revision, factor.revision)} and ${activeCredential(credential)})`,\n ),\n ]);\n\n return {\n sql,\n subject,\n credential,\n joinedSubject,\n joinedCredential,\n now,\n lock,\n nativeLocks,\n exact,\n id,\n activeSubject,\n activeCredential,\n requested,\n decode,\n read,\n authorityCondition,\n policyCondition,\n };\n});\n\n/** Evaluate freshness at the database sample. The final write also enforces the\n * earliest future boundary at which the same evidence would cease to satisfy. */\nexport const assessSessionAt = Effect.fnUntraced(function* (\n evidence: AuthenticationEvidence,\n requirement: AuthenticationRequirement,\n now: number,\n) {\n const result = yield* assessAuthentication(evidence, requirement, DateTime.makeUnsafe(now));\n\n return result;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-native-state.mjs","names":[],"sources":["../../src/internal/session-native-state.ts"],"sourcesContent":["import { reportAuthFailure } from \"@yielded/auth/Persistence\";\nimport type { SubjectId } from \"@yielded/auth/Schema\";\nimport {\n type AuthenticationRequirement,\n AuthenticationRevision,\n SessionConflict,\n SessionInvalid,\n SessionUnavailable,\n SessionStepUpInvalid,\n PendingAuthenticationInvalid,\n StaleAuthentication,\n assessAuthentication,\n type AuthenticationEvidence,\n} from \"@yielded/auth/Sessions\";\nimport { Cause, Effect, Schema, DateTime } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport { fragment, parameter, type Fragment } from \"effect/sql/Statement\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { SessionAuthorityTables, SessionExecution } from \"./models/session-model\";\nimport { nativeSqlAlias, type NativeSqlTables, type SqlTable } from \"./native-sql-table\";\nimport { anySqlCondition, exactSqlText } from \"./sql-change\";\nimport { guardSqlitePolicy, type SqlitePolicyValue } from \"./sqlite-policy-guard\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport const sessionUnavailable = () => SessionUnavailable.make({});\n\nexport const sessionInvariant: (condition: unknown) => asserts condition = (condition) => {\n if (!condition)\n throw PersistenceMappingError.make({ operation: \"decode\", cause: \"Invalid session authority\" });\n};\n\nexport type SessionDomainFailure =\n | SessionConflict\n | SessionInvalid\n | SessionUnavailable\n | SessionStepUpInvalid\n | PendingAuthenticationInvalid\n | StaleAuthentication;\n\nconst isDomainFailure = (error: unknown): error is SessionDomainFailure =>\n Schema.is(SessionConflict)(error) ||\n Schema.is(SessionInvalid)(error) ||\n Schema.is(SessionUnavailable)(error) ||\n Schema.is(SessionStepUpInvalid)(error) ||\n Schema.is(PendingAuthenticationInvalid)(error) ||\n Schema.is(StaleAuthentication)(error);\n\n/** Only schema-checked domain failures cross the owner boundary; SQL/mapping\n * failures remain Unavailable. The executor preserves every mixed cause reason. */\nexport const sessionFailure = <E>(\n error: E,\n): Extract<E, SessionDomainFailure> | SessionUnavailable =>\n isDomainFailure(error) ? (error as Extract<E, SessionDomainFailure>) : sessionUnavailable();\n\nexport const normalizeSessionOperation = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n effect.pipe(\n Effect.tapError((error) =>\n isDomainFailure(error)\n ? Effect.void\n : reportAuthFailure(\"auth-persistence\", Cause.fail(error)),\n ),\n Effect.mapError(sessionFailure),\n );\n\nexport type NativeSessionAuthorityMapping = SessionAuthorityTables<\n AnyTableModel,\n AnyTableModel,\n unknown\n> &\n SessionExecution;\n\nexport interface NativeSessionAuthority {\n readonly native: unknown;\n readonly row: Record<string, unknown>;\n readonly policyValues: ReadonlyArray<SqlitePolicyValue>;\n readonly revision: AuthenticationRevision;\n readonly requirement: AuthenticationRequirement;\n readonly now: number;\n}\n\nexport const sameSessionRevision = (left: AuthenticationRevision, right: AuthenticationRevision) =>\n left.subjectId === right.subjectId &&\n left.securityRevision === right.securityRevision &&\n left.credentials.length === right.credentials.length &&\n new Set(left.credentials.map((f) => f.credentialId)).size === left.credentials.length &&\n left.credentials.every((f) =>\n right.credentials.some(\n (other) => other.credentialId === f.credentialId && other.revision === f.revision,\n ),\n );\n\n/** Known-subject joins bind each physical ID column independently. Subject locks\n * precede every credential lock, including inside an application coordinator. */\nexport const makeNativeSessionAuthorityState = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: NativeSessionAuthorityMapping,\n batch = false,\n) {\n const sql = (yield* SqlClient).withoutTransforms();\n\n const s = mapping.subject,\n c = mapping.credential;\n\n if (batch) sessionInvariant(s.requirementColumns !== undefined);\n\n const subject = tables(s.table),\n credential = tables(c.table);\n\n const joinedSubject = subject.as(\"session_subject\"),\n joinedCredential = credential.as(\"session_factor\");\n\n const now = tables.expression(mapping.clock.engineNowMillis);\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n const nativeLocks = !batch && sql.onDialectOrElse({ sqlite: () => false, orElse: () => true });\n\n const exact = (table: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, table.column(key), table.value(key, value));\n\n const id = (table: SqlTable, key: string, value: unknown) =>\n sql`${table.column(key)} = ${table.value(key, value)}`;\n\n const activeSubject = (table: SqlTable) => id(table, s.status, s.activeStatusValue);\n\n const activeCredential = (table: SqlTable) => {\n if (c.status === undefined) return sql`1 = 1`;\n sessionInvariant(c.activeStatusValue !== undefined);\n\n return id(table, c.status, c.activeStatusValue);\n };\n\n const requested = (table: SqlTable, ids: ReadonlyArray<string>) =>\n anySqlCondition(ids.map((value) => exact(table, c.credentialId, value)));\n\n const decode = Effect.fnUntraced(function* (\n subjectId: SubjectId,\n native: unknown,\n projected: Record<string, unknown>,\n factors: ReadonlyArray<Record<string, unknown>>,\n instant: unknown,\n ): Effect.fn.Return<\n NativeSessionAuthority | undefined,\n PersistenceMappingError | Schema.SchemaError\n > {\n const row = subject.decode(projected, \"s_\");\n\n sessionInvariant((yield* mapping.subjectId.toSubject(row[s.id])) === subjectId);\n if (!s.isActiveStatus(row[s.status])) return undefined;\n const credentials: { credentialId: string; revision: string }[] = [];\n\n for (const factor of factors) {\n if (factor[c.credentialId] === null || factor[c.credentialId] === undefined) continue;\n sessionInvariant((yield* mapping.subjectId.toSubject(factor[c.subjectId])) === subjectId);\n if (c.status !== undefined) sessionInvariant(c.isActiveStatus?.(factor[c.status]) === true);\n\n const value = yield* Schema.decodeUnknownEffect(\n Schema.Struct({ credentialId: Schema.String, revision: Schema.String }),\n )({ credentialId: factor[c.credentialId], revision: factor[c.revision] });\n\n sessionInvariant(!credentials.some((other) => other.credentialId === value.credentialId));\n credentials.push(value);\n }\n sessionInvariant(credentials.length <= 64);\n\n return {\n native,\n row,\n // Compare the selected SQL representation, without round-tripping through\n // application codecs (for example, JSON whitespace or date precision).\n policyValues: batch\n ? (s.requirementColumns ?? []).map((key) => ({\n column: subject.selectedColumn(key),\n value: fragment([\n parameter(projected[nativeSqlAlias(\"s_\", subject.keys.indexOf(key))]),\n ]),\n }))\n : [],\n revision: yield* Schema.decodeUnknownEffect(AuthenticationRevision)({\n subjectId,\n securityRevision: row[s.securityRevision],\n credentials: credentials.sort((a, b) => a.credentialId.localeCompare(b.credentialId)),\n }),\n requirement: yield* s.decodeRequirement(row),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(instant)),\n };\n });\n\n const read = Effect.fnUntraced(function* (subjectId: SubjectId, locking = false) {\n const native = yield* mapping.subjectId.toNative(subjectId);\n\n sessionInvariant((yield* mapping.subjectId.toSubject(native)) === subjectId);\n if (locking && nativeLocks) {\n const owners =\n yield* sql`select ${subject.fields(\"s_\")}, ${now} as engine_now from ${subject.name} where ${id(subject, s.id, native)} and ${activeSubject(subject)} limit 2 ${lock}`;\n\n sessionInvariant(owners.length <= 1);\n if (owners[0] === undefined) return undefined;\n\n const factors =\n yield* sql`select ${credential.fields(\"c_\")} from ${credential.name} where ${id(credential, c.subjectId, native)} and ${activeCredential(credential)} order by ${credential.column(c.credentialId)} limit 65 ${lock}`;\n\n return yield* decode(\n subjectId,\n native,\n owners[0],\n factors.map((row) => credential.decode(row, \"c_\")),\n owners[0].engine_now,\n );\n }\n\n const rows =\n yield* sql`select ${joinedSubject.fields(\"s_\")}, ${joinedCredential.fields(\"c_\")}, ${now} as engine_now from ${joinedSubject.name} left join ${joinedCredential.name} on ${id(joinedCredential, c.subjectId, native)} and ${activeCredential(joinedCredential)} where ${id(joinedSubject, s.id, native)} and ${activeSubject(joinedSubject)} limit 65`;\n\n if (rows[0] === undefined) return undefined;\n\n return yield* decode(\n subjectId,\n native,\n rows[0],\n rows.map((row) => joinedCredential.decode(row, \"c_\")),\n rows[0].engine_now,\n );\n });\n\n const guardPolicy = Effect.fnUntraced(function* (current: NativeSessionAuthority) {\n if (!batch) return;\n sessionInvariant(s.requirementColumns !== undefined);\n\n yield* guardSqlitePolicy({\n name: \"session-subject-policy\",\n table: subject.name,\n owner: id(subject, s.id, current.native),\n values: current.policyValues,\n });\n });\n\n const authorityCondition = (revision: AuthenticationRevision, native: unknown): Fragment =>\n sql.and([\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, revision.securityRevision)} and ${activeSubject(subject)})`,\n sql`(select count(*) from ${credential.name} where ${id(credential, c.subjectId, native)} and ${activeCredential(credential)}) = ${revision.credentials.length}`,\n ...revision.credentials.map(\n (factor) =>\n sql`exists(select 1 from ${credential.name} where ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialId, factor.credentialId)} and ${exact(credential, c.revision, factor.revision)} and ${activeCredential(credential)})`,\n ),\n ]);\n\n return {\n sql,\n subject,\n credential,\n joinedSubject,\n joinedCredential,\n now,\n lock,\n nativeLocks,\n exact,\n id,\n activeSubject,\n activeCredential,\n requested,\n decode,\n read,\n authorityCondition,\n guardPolicy,\n };\n});\n\n/** Evaluate freshness at the database sample. The final write also enforces the\n * earliest future boundary at which the same evidence would cease to satisfy. */\nexport const assessSessionAt = Effect.fnUntraced(function* (\n evidence: AuthenticationEvidence,\n requirement: AuthenticationRequirement,\n now: number,\n) {\n const result = yield* assessAuthentication(evidence, requirement, DateTime.makeUnsafe(now));\n\n return result;\n});\n"],"mappings":";;;;;;;;;;AAyBA,MAAa,2BAA2B,mBAAmB,KAAK,CAAC,CAAC;AAElE,MAAa,oBAA+D,cAAc;CACxF,IAAI,CAAC,WACH,MAAM,wBAAwB,KAAK;EAAE,WAAW;EAAU,OAAO;CAA4B,CAAC;AAClG;AAUA,MAAM,mBAAmB,UACvB,OAAO,GAAG,eAAe,CAAC,CAAC,KAAK,KAChC,OAAO,GAAG,cAAc,CAAC,CAAC,KAAK,KAC/B,OAAO,GAAG,kBAAkB,CAAC,CAAC,KAAK,KACnC,OAAO,GAAG,oBAAoB,CAAC,CAAC,KAAK,KACrC,OAAO,GAAG,4BAA4B,CAAC,CAAC,KAAK,KAC7C,OAAO,GAAG,mBAAmB,CAAC,CAAC,KAAK;;;AAItC,MAAa,kBACX,UAEA,gBAAgB,KAAK,IAAK,QAA6C,mBAAmB;AAE5F,MAAa,6BAAsC,WACjD,OAAO,KACL,OAAO,UAAU,UACf,gBAAgB,KAAK,IACjB,OAAO,OACP,kBAAkB,oBAAoB,MAAM,KAAK,KAAK,CAAC,CAC7D,GACA,OAAO,SAAS,cAAc,CAChC;AAkBF,MAAa,uBAAuB,MAA8B,UAChE,KAAK,cAAc,MAAM,aACzB,KAAK,qBAAqB,MAAM,oBAChC,KAAK,YAAY,WAAW,MAAM,YAAY,UAC9C,IAAI,IAAI,KAAK,YAAY,KAAK,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK,YAAY,UAC/E,KAAK,YAAY,OAAO,MACtB,MAAM,YAAY,MACf,UAAU,MAAM,iBAAiB,EAAE,gBAAgB,MAAM,aAAa,EAAE,QAC3E,CACF;;;AAIF,MAAa,kCAAkC,OAAO,WAAW,WAC/D,QACA,SACA,QAAQ,OACR;CACA,MAAM,OAAO,OAAO,UAAA,CAAW,kBAAkB;CAEjD,MAAM,IAAI,QAAQ,SAChB,IAAI,QAAQ;CAEd,IAAI,OAAO,iBAAiB,EAAE,uBAAuB,KAAA,CAAS;CAE9D,MAAM,UAAU,OAAO,EAAE,KAAK,GAC5B,aAAa,OAAO,EAAE,KAAK;CAE7B,MAAM,gBAAgB,QAAQ,GAAG,iBAAiB,GAChD,mBAAmB,WAAW,GAAG,gBAAgB;CAEnD,MAAM,MAAM,OAAO,WAAW,QAAQ,MAAM,eAAe;CAC3D,MAAM,OAAO,IAAI,gBAAgB;EAAE,cAAc,GAAG;EAAI,cAAc,GAAG;CAAa,CAAC;CACvF,MAAM,cAAc,CAAC,SAAS,IAAI,gBAAgB;EAAE,cAAc;EAAO,cAAc;CAAK,CAAC;CAE7F,MAAM,SAAS,OAAiB,KAAa,UAC3C,aAAa,KAAK,MAAM,OAAO,GAAG,GAAG,MAAM,MAAM,KAAK,KAAK,CAAC;CAE9D,MAAM,MAAM,OAAiB,KAAa,UACxC,GAAG,GAAG,MAAM,OAAO,GAAG,EAAE,KAAK,MAAM,MAAM,KAAK,KAAK;CAErD,MAAM,iBAAiB,UAAoB,GAAG,OAAO,EAAE,QAAQ,EAAE,iBAAiB;CAElF,MAAM,oBAAoB,UAAoB;EAC5C,IAAI,EAAE,WAAW,KAAA,GAAW,OAAO,GAAG;EACtC,iBAAiB,EAAE,sBAAsB,KAAA,CAAS;EAElD,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,iBAAiB;CAChD;CAEA,MAAM,aAAa,OAAiB,QAClC,gBAAgB,IAAI,KAAK,UAAU,MAAM,OAAO,EAAE,cAAc,KAAK,CAAC,CAAC;CAEzE,MAAM,SAAS,OAAO,WAAW,WAC/B,WACA,QACA,WACA,SACA,SAIA;EACA,MAAM,MAAM,QAAQ,OAAO,WAAW,IAAI;EAE1C,kBAAkB,OAAO,QAAQ,UAAU,UAAU,IAAI,EAAE,GAAG,OAAO,SAAS;EAC9E,IAAI,CAAC,EAAE,eAAe,IAAI,EAAE,OAAO,GAAG,OAAO,KAAA;EAC7C,MAAM,cAA4D,CAAC;EAEnE,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,OAAO,EAAE,kBAAkB,QAAQ,OAAO,EAAE,kBAAkB,KAAA,GAAW;GAC7E,kBAAkB,OAAO,QAAQ,UAAU,UAAU,OAAO,EAAE,UAAU,OAAO,SAAS;GACxF,IAAI,EAAE,WAAW,KAAA,GAAW,iBAAiB,EAAE,iBAAiB,OAAO,EAAE,OAAO,MAAM,IAAI;GAE1F,MAAM,QAAQ,OAAO,OAAO,oBAC1B,OAAO,OAAO;IAAE,cAAc,OAAO;IAAQ,UAAU,OAAO;GAAO,CAAC,CACxE,CAAC,CAAC;IAAE,cAAc,OAAO,EAAE;IAAe,UAAU,OAAO,EAAE;GAAU,CAAC;GAExE,iBAAiB,CAAC,YAAY,MAAM,UAAU,MAAM,iBAAiB,MAAM,YAAY,CAAC;GACxF,YAAY,KAAK,KAAK;EACxB;EACA,iBAAiB,YAAY,UAAU,EAAE;EAEzC,OAAO;GACL;GACA;GAGA,cAAc,SACT,EAAE,sBAAsB,CAAC,EAAA,CAAG,KAAK,SAAS;IACzC,QAAQ,QAAQ,eAAe,GAAG;IAClC,OAAO,SAAS,CACd,UAAU,UAAU,eAAe,MAAM,QAAQ,KAAK,QAAQ,GAAG,CAAC,EAAE,CACtE,CAAC;GACH,EAAE,IACF,CAAC;GACL,UAAU,OAAO,OAAO,oBAAoB,sBAAsB,CAAC,CAAC;IAClE;IACA,kBAAkB,IAAI,EAAE;IACxB,aAAa,YAAY,MAAM,GAAG,MAAM,EAAE,aAAa,cAAc,EAAE,YAAY,CAAC;GACtF,CAAC;GACD,aAAa,OAAO,EAAE,kBAAkB,GAAG;GAC3C,KAAK,OAAO,OAAO,aAAa,OAAO,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC;EAC7D;CACF,CAAC;CAED,MAAM,OAAO,OAAO,WAAW,WAAW,WAAsB,UAAU,OAAO;EAC/E,MAAM,SAAS,OAAO,QAAQ,UAAU,SAAS,SAAS;EAE1D,kBAAkB,OAAO,QAAQ,UAAU,UAAU,MAAM,OAAO,SAAS;EAC3E,IAAI,WAAW,aAAa;GAC1B,MAAM,SACJ,OAAO,GAAG,UAAU,QAAQ,OAAO,IAAI,EAAE,IAAI,IAAI,sBAAsB,QAAQ,KAAK,SAAS,GAAG,SAAS,EAAE,IAAI,MAAM,EAAE,OAAO,cAAc,OAAO,EAAE,WAAW;GAElK,iBAAiB,OAAO,UAAU,CAAC;GACnC,IAAI,OAAO,OAAO,KAAA,GAAW,OAAO,KAAA;GAEpC,MAAM,UACJ,OAAO,GAAG,UAAU,WAAW,OAAO,IAAI,EAAE,QAAQ,WAAW,KAAK,SAAS,GAAG,YAAY,EAAE,WAAW,MAAM,EAAE,OAAO,iBAAiB,UAAU,EAAE,YAAY,WAAW,OAAO,EAAE,YAAY,EAAE,YAAY;GAEjN,OAAO,OAAO,OACZ,WACA,QACA,OAAO,IACP,QAAQ,KAAK,QAAQ,WAAW,OAAO,KAAK,IAAI,CAAC,GACjD,OAAO,EAAE,CAAC,UACZ;EACF;EAEA,MAAM,OACJ,OAAO,GAAG,UAAU,cAAc,OAAO,IAAI,EAAE,IAAI,iBAAiB,OAAO,IAAI,EAAE,IAAI,IAAI,sBAAsB,cAAc,KAAK,aAAa,iBAAiB,KAAK,MAAM,GAAG,kBAAkB,EAAE,WAAW,MAAM,EAAE,OAAO,iBAAiB,gBAAgB,EAAE,SAAS,GAAG,eAAe,EAAE,IAAI,MAAM,EAAE,OAAO,cAAc,aAAa,EAAE;EAE9U,IAAI,KAAK,OAAO,KAAA,GAAW,OAAO,KAAA;EAElC,OAAO,OAAO,OACZ,WACA,QACA,KAAK,IACL,KAAK,KAAK,QAAQ,iBAAiB,OAAO,KAAK,IAAI,CAAC,GACpD,KAAK,EAAE,CAAC,UACV;CACF,CAAC;CAED,MAAM,cAAc,OAAO,WAAW,WAAW,SAAiC;EAChF,IAAI,CAAC,OAAO;EACZ,iBAAiB,EAAE,uBAAuB,KAAA,CAAS;EAEnD,OAAO,kBAAkB;GACvB,MAAM;GACN,OAAO,QAAQ;GACf,OAAO,GAAG,SAAS,EAAE,IAAI,QAAQ,MAAM;GACvC,QAAQ,QAAQ;EAClB,CAAC;CACH,CAAC;CAED,MAAM,sBAAsB,UAAkC,WAC5D,IAAI,IAAI;EACN,GAAG,wBAAwB,QAAQ,KAAK,SAAS,GAAG,SAAS,EAAE,IAAI,MAAM,EAAE,OAAO,MAAM,SAAS,EAAE,kBAAkB,SAAS,gBAAgB,EAAE,OAAO,cAAc,OAAO,EAAE;EAC9K,GAAG,yBAAyB,WAAW,KAAK,SAAS,GAAG,YAAY,EAAE,WAAW,MAAM,EAAE,OAAO,iBAAiB,UAAU,EAAE,MAAM,SAAS,YAAY;EACxJ,GAAG,SAAS,YAAY,KACrB,WACC,GAAG,wBAAwB,WAAW,KAAK,SAAS,GAAG,YAAY,EAAE,WAAW,MAAM,EAAE,OAAO,MAAM,YAAY,EAAE,cAAc,OAAO,YAAY,EAAE,OAAO,MAAM,YAAY,EAAE,UAAU,OAAO,QAAQ,EAAE,OAAO,iBAAiB,UAAU,EAAE,EACpP;CACF,CAAC;CAEH,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF,CAAC;;;AAID,MAAa,kBAAkB,OAAO,WAAW,WAC/C,UACA,aACA,KACA;CAGA,OAAO,OAFe,qBAAqB,UAAU,aAAa,SAAS,WAAW,GAAG,CAAC;AAG5F,CAAC"}
|
|
@@ -102,7 +102,8 @@ const makeNativeStatefulSessionServices = Effect.fnUntraced(function* (tables, m
|
|
|
102
102
|
issuedAt: DateTime.makeUnsafe(current.now)
|
|
103
103
|
};
|
|
104
104
|
const deadline = Math.min(expires, assessment.validUntil, pendingExpiry);
|
|
105
|
-
|
|
105
|
+
yield* state.guardPolicy(current);
|
|
106
|
+
const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;
|
|
106
107
|
const values = {
|
|
107
108
|
...s.encodeInsert(record, {
|
|
108
109
|
subjectId: current.native,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-stateful.mjs","names":[],"sources":["../../src/internal/session-native-stateful.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n SessionConflict,\n SessionInvalid,\n PendingAuthenticationInvalid,\n StaleAuthentication,\n SessionMetadata,\n SessionId,\n snapshotSessionAuthenticationProvenance,\n type StatefulSessionPersistence,\n type StatefulSessionRecord,\n type SessionRepository,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option, Schema } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { StatefulSessionMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { sessionEvidenceDeadline } from \"./session-native-authority\";\nimport { makeNativeSessionPending, prepareNativeSession } from \"./session-native-pending\";\nimport {\n makeConditionalSqlInsert,\n makeNativeSessionRecords,\n sameSessionRecord,\n} from \"./session-native-record\";\nimport {\n makeNativeSessionAuthorityState,\n sameSessionRevision,\n normalizeSessionOperation,\n sessionInvariant,\n sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue, preservesRevision } from \"./session-policy\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n appendSqlBatchStatement,\n registerSqlBatchPostcondition,\n registerSqlPostcondition,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeStatefulSessionMapping<Claims> = StatefulSessionMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n unknown\n>;\n\nexport const makeNativeStatefulSessionServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeStatefulSessionMapping<Claims>,\n): Effect.fn.Return<\n {\n readonly statefulSessionPersistence: StatefulSessionPersistence<Claims>;\n readonly sessionRepository: SessionRepository;\n },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const state = yield* makeNativeSessionAuthorityState(tables, mapping, batch !== undefined);\n const conditionalInsert = yield* makeConditionalSqlInsert();\n const records = yield* makeNativeSessionRecords(tables, mapping);\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const pending =\n mapping.pending === undefined\n ? undefined\n : yield* makeNativeSessionPending(\n tables,\n { ...mapping, ...mapping.pending },\n batch !== undefined,\n );\n\n const { sql, s, table, now, exact, live } = records;\n\n const native = <A, E, R>(\n work: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), mode)\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const stage = Effect.fnUntraced(function* (statement: Fragment, expected?: number) {\n if (batch !== undefined) {\n yield* appendSqlBatchStatement(sql`${statement}`);\n if (expected !== undefined)\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = ${expected}`));\n\n return expected;\n }\n\n return yield* executeSqlChange(sql, statement);\n });\n\n const finalCondition = Effect.fnUntraced(function* (name: string, condition: Fragment) {\n if (batch !== undefined)\n yield* registerSqlBatchPostcondition({ name, statement: sqlBatchAssertion(sql, condition) });\n else if (Option.isSome(external))\n yield* registerSqlPostcondition({\n name,\n check: Effect.gen(function* () {\n const rows = yield* sql`select 1 where ${condition}`;\n\n sessionInvariant(rows.length === 1);\n }).pipe(\n Effect.mapError((cause) => PersistenceMappingError.make({ operation: \"decode\", cause })),\n ),\n });\n });\n\n const recordCondition = Effect.fnUntraced(function* (\n record: StatefulSessionRecord<Claims>,\n values: Readonly<Record<string, unknown>>,\n ) {\n return sql`exists(select 1 from ${table.name} where ${yield* records.owner(record)} and ${exact(table, s.securityRevision, record.securityRevision)} and ${records.millis(table, s.expiresAt)} = ${DateTime.toEpochMillis(record.expiresAt)} and ${records.millis(table, s.absoluteExpiresAt)} = ${DateTime.toEpochMillis(record.absoluteExpiresAt)} and ${records.encodedCondition(values)} and ${live(table)})`;\n });\n\n const classify = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n effect.pipe(\n Effect.mapError((error) =>\n isMappedConstraintConflict(mapping.isConstraintConflict, error)\n ? SessionConflict.make({})\n : error,\n ),\n );\n\n const verify = Effect.fnUntraced(function* (digest: StatefulSessionRecord<Claims>[\"digest\"]) {\n const subject = state.subject.as(\"verified_subject\"),\n session = table.as(\"verified_session\"),\n owner = mapping.subject;\n\n if (\n table.unencodedTextColumn?.(s.subjectId) !== undefined &&\n state.subject.unencodedTextColumn?.(owner.id) !== undefined\n ) {\n const rows =\n yield* sql`select ${session.fields(\"session_\")}, ${subject.fields(\"subject_\")} from ${session.name} join ${subject.name} on ${exactSqlText(sql, subject.column(owner.id), session.column(s.subjectId))} and ${state.activeSubject(subject)} and ${exactSqlText(sql, subject.column(owner.securityRevision), session.column(s.securityRevision))} where ${exact(session, s.digest, digest)} and ${live(session)} limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n const record = yield* records.decode(session.decode(rows[0], \"session_\"));\n const decoded = subject.decode(rows[0], \"subject_\");\n\n sessionInvariant(\n (yield* mapping.subjectId.toSubject(decoded[owner.id])) === record.subjectId &&\n decoded[owner.securityRevision] === record.securityRevision &&\n owner.isActiveStatus(decoded[owner.status]),\n );\n\n return record;\n }\n const record = yield* records.read(digest);\n\n if (record === undefined) return undefined;\n const nativeSubject = yield* mapping.subjectId.toNative(record.subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"subject_\")} from ${subject.name} where ${state.id(subject, owner.id, nativeSubject)} and ${state.activeSubject(subject)} and ${state.exact(subject, owner.securityRevision, record.securityRevision)} and ${now} < ${DateTime.toEpochMillis(record.expiresAt)} limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n sessionInvariant(\n (yield* mapping.subjectId.toSubject(subject.decode(rows[0], \"subject_\")[owner.id])) ===\n record.subjectId,\n );\n\n return record;\n });\n\n const persistence: StatefulSessionPersistence<Claims> = {\n establish: (input, project) =>\n native(\n classify(\n Effect.gen(function* () {\n const current = yield* state.read(input.evidence.revision.subjectId, true);\n\n if (\n current === undefined ||\n !sameSessionRevision(current.revision, input.evidence.revision)\n )\n return yield* StaleAuthentication.make({});\n\n const assessment = yield* sessionEvidenceDeadline(\n input.evidence,\n current.requirement,\n current.now,\n );\n\n const expires = DateTime.toEpochMillis(input.session.expiresAt),\n absolute = DateTime.toEpochMillis(input.session.absoluteExpiresAt);\n\n if (current.now >= expires || expires > absolute)\n return yield* StaleAuthentication.make({});\n let pendingExpiry = Infinity;\n\n if (input.pending !== undefined) {\n if (pending === undefined || mapping.pending === undefined)\n return yield* PendingAuthenticationInvalid.make({});\n const found = yield* pending.read(\"Login\", input.pending.digest);\n\n if (\n found === undefined ||\n found.record.version !== input.pending.version ||\n found.record.subjectId !== current.revision.subjectId ||\n found.record.flowId !== input.pending.flowId ||\n found.record.bindingDigest !== input.pending.bindingDigest\n )\n return yield* PendingAuthenticationInvalid.make({});\n const original = yield* mapping.pending.login.decode(found.record.snapshot);\n\n if (\n original.digest !== input.pending.digest ||\n original.version !== input.pending.version ||\n !preservesRevision(input.evidence, original.evidence) ||\n DateTime.toEpochMillis(original.expiresAt) !== found.record.expiresAtMillis ||\n original.attemptLimit !== found.record.attemptLimit\n )\n return yield* PendingAuthenticationInvalid.make({});\n pendingExpiry = found.record.expiresAtMillis;\n yield* pending.consume(\"Login\", input.pending, current.native);\n }\n\n const nativeSession = yield* allocateSessionValue(\n \"interactive\",\n s.allocateId,\n s.allocateIdSync,\n );\n\n const sessionId = yield* mapping.sessionId.toSession(nativeSession);\n\n if (sessionId === input.handoffSourceSessionId) return yield* SessionConflict.make({});\n\n const record: StatefulSessionRecord<Claims> = {\n ...input.session,\n sessionId,\n subjectId: current.revision.subjectId,\n securityRevision: current.revision.securityRevision,\n assurance:\n input.handoffSourceSessionId === undefined\n ? assessment.assessed.assurance\n : input.session.assurance,\n provenance: yield* snapshotSessionAuthenticationProvenance({\n evidence: input.evidence,\n }),\n issuedAt: DateTime.makeUnsafe(current.now),\n };\n\n const deadline = Math.min(expires, assessment.validUntil, pendingExpiry);\n const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${state.policyCondition(current.row, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n const values = {\n ...s.encodeInsert(record, { subjectId: current.native, sessionId: nativeSession }),\n [s.sessionId]: nativeSession,\n [s.subjectId]: current.native,\n [s.digest]: record.digest,\n [s.securityRevision]: record.securityRevision,\n [s.issuedAt]: s.encodeInstant(record.issuedAt),\n [s.expiresAt]: s.encodeInstant(record.expiresAt),\n [s.absoluteExpiresAt]: s.encodeInstant(record.absoluteExpiresAt),\n };\n\n const changed = yield* stage(conditionalInsert(table, values, condition), 1);\n\n if (changed !== 1) return yield* StaleAuthentication.make({});\n const final = sql`${condition} and ${yield* recordCondition(record, values)}${input.pending === undefined || pending === undefined ? sql`` : sql` and ${pending.consumedCondition(\"Login\", input.pending, current.native)}`}`;\n\n yield* finalCondition(\"stateful-session-issuance\", final);\n if (batch === undefined && Option.isSome(external))\n yield* registerSqlPostcondition({\n name: \"stateful-session-issued-metadata\",\n check: Effect.gen(function* () {\n const finalRecord = yield* records.read(record.digest);\n\n sessionInvariant(\n finalRecord !== undefined && sameSessionRecord(finalRecord, record),\n );\n const authority = yield* state.read(record.subjectId);\n\n sessionInvariant(\n authority !== undefined &&\n sameSessionRevision(authority.revision, input.evidence.revision),\n );\n yield* sessionEvidenceDeadline(\n input.evidence,\n authority.requirement,\n authority.now,\n );\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepareNativeSession(record, project);\n }),\n ),\n ),\n verify: ({ digest }) =>\n executor\n .verify(verify(digest))\n .pipe(\n Effect.flatMap((record) =>\n record === undefined ? Effect.fail(SessionInvalid.make({})) : Effect.succeed(record),\n ),\n ),\n rotate: (input, project) =>\n native(\n classify(\n Effect.gen(function* () {\n const absolute = DateTime.toEpochMillis(input.record.absoluteExpiresAt),\n expires = DateTime.toEpochMillis(input.nextExpiresAt);\n\n if (\n expires > absolute ||\n input.nextDigest === input.record.digest ||\n input.nextCredentialVersion === input.record.credentialVersion\n )\n return yield* SessionConflict.make({});\n\n const needsSample =\n batch !== undefined ||\n sql.onDialectOrElse({ mysql: () => true, orElse: () => false });\n\n const sampled = needsSample\n ? Number((yield* sql`select ${now} as engine_now`)[0]?.engine_now)\n : DateTime.toEpochMillis(input.record.issuedAt);\n\n sessionInvariant(Number.isSafeInteger(sampled));\n\n const prepared = {\n ...input.record,\n digest: input.nextDigest,\n credentialVersion: input.nextCredentialVersion,\n expiresAt: input.nextExpiresAt,\n issuedAt: DateTime.makeUnsafe(sampled),\n };\n\n const values = {\n ...s.encodeRotation(prepared),\n [s.digest]: prepared.digest,\n [s.issuedAt]: tables.expression(mapping.clock.fromMillis(now)),\n [s.expiresAt]: s.encodeInstant(prepared.expiresAt),\n };\n\n const condition = sql`${yield* records.owner(input.record)} and ${live(table)} and ${now} < ${expires} and ${records.millis(table, s.absoluteExpiresAt)} = ${absolute}`;\n const update = sql`${table.update(values)} where ${condition}`;\n let record = prepared;\n\n if (needsSample) {\n // Batch receipts are prepared before execution. The exact sampled time is\n // written and checked; no speculative UPDATE result is treated as committed.\n const statement = sql`${table.update({ ...values, [s.issuedAt]: s.encodeInstant(prepared.issuedAt) })} where ${condition} and ${now} >= ${sampled}`;\n const changed = yield* stage(statement, 1);\n\n if (changed !== 1) return yield* SessionConflict.make({});\n } else {\n const rows = yield* sql`${update} returning ${table.fields(\"session_\")}`;\n\n if (rows.length !== 1) return yield* SessionConflict.make({});\n record = yield* records.decode(table.decode(rows[0]!, \"session_\"));\n }\n yield* finalCondition(\n \"stateful-session-rotation\",\n yield* recordCondition(record, {\n ...values,\n [s.issuedAt]: s.encodeInstant(record.issuedAt),\n }),\n );\n\n return yield* prepareNativeSession(record, project);\n }),\n ),\n \"statement\",\n ),\n revokeDigest: (digest, project) =>\n native(\n Effect.gen(function* () {\n let expected: number | undefined;\n\n if (batch !== undefined)\n expected =\n (yield* sql`select 1 from ${table.name} where ${exact(table, s.digest, digest)} limit 2`)\n .length;\n\n const changed = yield* stage(\n sql`delete from ${table.name} where ${exact(table, s.digest, digest)}`,\n expected,\n );\n\n sessionInvariant(changed !== undefined && changed <= 1);\n yield* finalCondition(\n \"stateful-session-digest-revocation\",\n sql`not exists(select 1 from ${table.name} where ${exact(table, s.digest, digest)})`,\n );\n\n return yield* prepareNativeSession(changed === 1, project);\n }),\n \"statement\",\n ),\n revoke: (input, project) =>\n native(\n Effect.gen(function* () {\n const subject = yield* mapping.subjectId.toNative(input.subjectId),\n session = yield* mapping.sessionId.toNative(input.sessionId);\n\n const condition = sql`${state.id(table, s.subjectId, subject)} and ${state.id(table, s.sessionId, session)}`;\n\n yield* stage(sql`delete from ${table.name} where ${condition}`);\n yield* finalCondition(\n \"stateful-session-revocation\",\n sql`not exists(select 1 from ${table.name} where ${condition})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n \"statement\",\n ),\n revokeAll: (input, project) =>\n native(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n owner = mapping.subject;\n\n const next = yield* allocateSessionValue(\n \"interactive\",\n owner.nextSecurityRevision?.(input.expectedSecurityRevision),\n owner.nextSecurityRevisionSync === undefined\n ? undefined\n : () => owner.nextSecurityRevisionSync!(input.expectedSecurityRevision),\n );\n\n sessionInvariant(next !== input.expectedSecurityRevision);\n\n const changed = yield* stage(\n sql`${state.subject.update({ [owner.securityRevision]: next })} where ${state.id(state.subject, owner.id, nativeSubject)} and ${state.activeSubject(state.subject)} and ${state.exact(state.subject, owner.securityRevision, input.expectedSecurityRevision)}`,\n 1,\n );\n\n if (changed !== 1) return yield* StaleAuthentication.make({});\n yield* finalCondition(\n \"all-session-revocation\",\n sql`exists(select 1 from ${state.subject.name} where ${state.id(state.subject, owner.id, nativeSubject)} and ${state.exact(state.subject, owner.securityRevision, next)})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n \"statement\",\n ),\n };\n\n const sessionRepository: SessionRepository = {\n list: (input) =>\n executor.read(\n Effect.gen(function* () {\n sessionInvariant(\n Number.isSafeInteger(input.limit) && input.limit > 0 && input.limit <= 1000,\n );\n\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n st = state.subject.as(\"listed_subject\"),\n list = table.as(\"listed_session\"),\n owner = mapping.subject;\n\n const cursor =\n input.cursor === undefined\n ? undefined\n : yield* mapping.sessionId.toNative(\n yield* Schema.decodeEffect(SessionId)(input.cursor),\n );\n\n const rows =\n yield* sql`select ${list.fields(\"session_\")}, ${st.fields(\"subject_\")} from ${list.name} join ${st.name} on ${state.id(st, owner.id, nativeSubject)} and ${state.activeSubject(st)} and ${exactSqlText(sql, st.column(owner.securityRevision), list.column(s.securityRevision))} where ${state.id(list, s.subjectId, nativeSubject)} and ${live(list)}${cursor === undefined ? sql`` : sql` and ${list.column(s.sessionId)} > ${list.value(s.sessionId, cursor)}`} order by ${list.column(s.sessionId)} limit ${input.limit + 1}`;\n\n const decoded = yield* Effect.forEach(rows.slice(0, input.limit), (row) =>\n Effect.gen(function* () {\n const record = yield* records.decode(list.decode(row, \"session_\"));\n\n sessionInvariant(\n record.subjectId === input.subjectId &&\n (yield* mapping.subjectId.toSubject(st.decode(row, \"subject_\")[owner.id])) ===\n input.subjectId,\n );\n\n return yield* Schema.decodeEffect(Schema.toType(SessionMetadata))(record);\n }),\n );\n\n const nextCursor = rows.length > input.limit ? decoded.at(-1)?.sessionId : undefined;\n\n return { sessions: decoded, ...(nextCursor === undefined ? {} : { nextCursor }) };\n }),\n ),\n };\n\n return { statefulSessionPersistence: persistence, sessionRepository };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-native-stateful.mjs","names":[],"sources":["../../src/internal/session-native-stateful.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n SessionConflict,\n SessionInvalid,\n PendingAuthenticationInvalid,\n StaleAuthentication,\n SessionMetadata,\n SessionId,\n snapshotSessionAuthenticationProvenance,\n type StatefulSessionPersistence,\n type StatefulSessionRecord,\n type SessionRepository,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option, Schema } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { StatefulSessionMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { sessionEvidenceDeadline } from \"./session-native-authority\";\nimport { makeNativeSessionPending, prepareNativeSession } from \"./session-native-pending\";\nimport {\n makeConditionalSqlInsert,\n makeNativeSessionRecords,\n sameSessionRecord,\n} from \"./session-native-record\";\nimport {\n makeNativeSessionAuthorityState,\n sameSessionRevision,\n normalizeSessionOperation,\n sessionInvariant,\n sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue, preservesRevision } from \"./session-policy\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n appendSqlBatchStatement,\n registerSqlBatchPostcondition,\n registerSqlPostcondition,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeStatefulSessionMapping<Claims> = StatefulSessionMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n unknown\n>;\n\nexport const makeNativeStatefulSessionServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeStatefulSessionMapping<Claims>,\n): Effect.fn.Return<\n {\n readonly statefulSessionPersistence: StatefulSessionPersistence<Claims>;\n readonly sessionRepository: SessionRepository;\n },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const state = yield* makeNativeSessionAuthorityState(tables, mapping, batch !== undefined);\n const conditionalInsert = yield* makeConditionalSqlInsert();\n const records = yield* makeNativeSessionRecords(tables, mapping);\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const pending =\n mapping.pending === undefined\n ? undefined\n : yield* makeNativeSessionPending(\n tables,\n { ...mapping, ...mapping.pending },\n batch !== undefined,\n );\n\n const { sql, s, table, now, exact, live } = records;\n\n const native = <A, E, R>(\n work: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), mode)\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const stage = Effect.fnUntraced(function* (statement: Fragment, expected?: number) {\n if (batch !== undefined) {\n yield* appendSqlBatchStatement(sql`${statement}`);\n if (expected !== undefined)\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = ${expected}`));\n\n return expected;\n }\n\n return yield* executeSqlChange(sql, statement);\n });\n\n const finalCondition = Effect.fnUntraced(function* (name: string, condition: Fragment) {\n if (batch !== undefined)\n yield* registerSqlBatchPostcondition({ name, statement: sqlBatchAssertion(sql, condition) });\n else if (Option.isSome(external))\n yield* registerSqlPostcondition({\n name,\n check: Effect.gen(function* () {\n const rows = yield* sql`select 1 where ${condition}`;\n\n sessionInvariant(rows.length === 1);\n }).pipe(\n Effect.mapError((cause) => PersistenceMappingError.make({ operation: \"decode\", cause })),\n ),\n });\n });\n\n const recordCondition = Effect.fnUntraced(function* (\n record: StatefulSessionRecord<Claims>,\n values: Readonly<Record<string, unknown>>,\n ) {\n return sql`exists(select 1 from ${table.name} where ${yield* records.owner(record)} and ${exact(table, s.securityRevision, record.securityRevision)} and ${records.millis(table, s.expiresAt)} = ${DateTime.toEpochMillis(record.expiresAt)} and ${records.millis(table, s.absoluteExpiresAt)} = ${DateTime.toEpochMillis(record.absoluteExpiresAt)} and ${records.encodedCondition(values)} and ${live(table)})`;\n });\n\n const classify = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n effect.pipe(\n Effect.mapError((error) =>\n isMappedConstraintConflict(mapping.isConstraintConflict, error)\n ? SessionConflict.make({})\n : error,\n ),\n );\n\n const verify = Effect.fnUntraced(function* (digest: StatefulSessionRecord<Claims>[\"digest\"]) {\n const subject = state.subject.as(\"verified_subject\"),\n session = table.as(\"verified_session\"),\n owner = mapping.subject;\n\n if (\n table.unencodedTextColumn?.(s.subjectId) !== undefined &&\n state.subject.unencodedTextColumn?.(owner.id) !== undefined\n ) {\n const rows =\n yield* sql`select ${session.fields(\"session_\")}, ${subject.fields(\"subject_\")} from ${session.name} join ${subject.name} on ${exactSqlText(sql, subject.column(owner.id), session.column(s.subjectId))} and ${state.activeSubject(subject)} and ${exactSqlText(sql, subject.column(owner.securityRevision), session.column(s.securityRevision))} where ${exact(session, s.digest, digest)} and ${live(session)} limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n const record = yield* records.decode(session.decode(rows[0], \"session_\"));\n const decoded = subject.decode(rows[0], \"subject_\");\n\n sessionInvariant(\n (yield* mapping.subjectId.toSubject(decoded[owner.id])) === record.subjectId &&\n decoded[owner.securityRevision] === record.securityRevision &&\n owner.isActiveStatus(decoded[owner.status]),\n );\n\n return record;\n }\n const record = yield* records.read(digest);\n\n if (record === undefined) return undefined;\n const nativeSubject = yield* mapping.subjectId.toNative(record.subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"subject_\")} from ${subject.name} where ${state.id(subject, owner.id, nativeSubject)} and ${state.activeSubject(subject)} and ${state.exact(subject, owner.securityRevision, record.securityRevision)} and ${now} < ${DateTime.toEpochMillis(record.expiresAt)} limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n sessionInvariant(\n (yield* mapping.subjectId.toSubject(subject.decode(rows[0], \"subject_\")[owner.id])) ===\n record.subjectId,\n );\n\n return record;\n });\n\n const persistence: StatefulSessionPersistence<Claims> = {\n establish: (input, project) =>\n native(\n classify(\n Effect.gen(function* () {\n const current = yield* state.read(input.evidence.revision.subjectId, true);\n\n if (\n current === undefined ||\n !sameSessionRevision(current.revision, input.evidence.revision)\n )\n return yield* StaleAuthentication.make({});\n\n const assessment = yield* sessionEvidenceDeadline(\n input.evidence,\n current.requirement,\n current.now,\n );\n\n const expires = DateTime.toEpochMillis(input.session.expiresAt),\n absolute = DateTime.toEpochMillis(input.session.absoluteExpiresAt);\n\n if (current.now >= expires || expires > absolute)\n return yield* StaleAuthentication.make({});\n let pendingExpiry = Infinity;\n\n if (input.pending !== undefined) {\n if (pending === undefined || mapping.pending === undefined)\n return yield* PendingAuthenticationInvalid.make({});\n const found = yield* pending.read(\"Login\", input.pending.digest);\n\n if (\n found === undefined ||\n found.record.version !== input.pending.version ||\n found.record.subjectId !== current.revision.subjectId ||\n found.record.flowId !== input.pending.flowId ||\n found.record.bindingDigest !== input.pending.bindingDigest\n )\n return yield* PendingAuthenticationInvalid.make({});\n const original = yield* mapping.pending.login.decode(found.record.snapshot);\n\n if (\n original.digest !== input.pending.digest ||\n original.version !== input.pending.version ||\n !preservesRevision(input.evidence, original.evidence) ||\n DateTime.toEpochMillis(original.expiresAt) !== found.record.expiresAtMillis ||\n original.attemptLimit !== found.record.attemptLimit\n )\n return yield* PendingAuthenticationInvalid.make({});\n pendingExpiry = found.record.expiresAtMillis;\n yield* pending.consume(\"Login\", input.pending, current.native);\n }\n\n const nativeSession = yield* allocateSessionValue(\n \"interactive\",\n s.allocateId,\n s.allocateIdSync,\n );\n\n const sessionId = yield* mapping.sessionId.toSession(nativeSession);\n\n if (sessionId === input.handoffSourceSessionId) return yield* SessionConflict.make({});\n\n const record: StatefulSessionRecord<Claims> = {\n ...input.session,\n sessionId,\n subjectId: current.revision.subjectId,\n securityRevision: current.revision.securityRevision,\n assurance:\n input.handoffSourceSessionId === undefined\n ? assessment.assessed.assurance\n : input.session.assurance,\n provenance: yield* snapshotSessionAuthenticationProvenance({\n evidence: input.evidence,\n }),\n issuedAt: DateTime.makeUnsafe(current.now),\n };\n\n const deadline = Math.min(expires, assessment.validUntil, pendingExpiry);\n\n yield* state.guardPolicy(current);\n const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n const values = {\n ...s.encodeInsert(record, { subjectId: current.native, sessionId: nativeSession }),\n [s.sessionId]: nativeSession,\n [s.subjectId]: current.native,\n [s.digest]: record.digest,\n [s.securityRevision]: record.securityRevision,\n [s.issuedAt]: s.encodeInstant(record.issuedAt),\n [s.expiresAt]: s.encodeInstant(record.expiresAt),\n [s.absoluteExpiresAt]: s.encodeInstant(record.absoluteExpiresAt),\n };\n\n const changed = yield* stage(conditionalInsert(table, values, condition), 1);\n\n if (changed !== 1) return yield* StaleAuthentication.make({});\n const final = sql`${condition} and ${yield* recordCondition(record, values)}${input.pending === undefined || pending === undefined ? sql`` : sql` and ${pending.consumedCondition(\"Login\", input.pending, current.native)}`}`;\n\n yield* finalCondition(\"stateful-session-issuance\", final);\n if (batch === undefined && Option.isSome(external))\n yield* registerSqlPostcondition({\n name: \"stateful-session-issued-metadata\",\n check: Effect.gen(function* () {\n const finalRecord = yield* records.read(record.digest);\n\n sessionInvariant(\n finalRecord !== undefined && sameSessionRecord(finalRecord, record),\n );\n const authority = yield* state.read(record.subjectId);\n\n sessionInvariant(\n authority !== undefined &&\n sameSessionRevision(authority.revision, input.evidence.revision),\n );\n yield* sessionEvidenceDeadline(\n input.evidence,\n authority.requirement,\n authority.now,\n );\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepareNativeSession(record, project);\n }),\n ),\n ),\n verify: ({ digest }) =>\n executor\n .verify(verify(digest))\n .pipe(\n Effect.flatMap((record) =>\n record === undefined ? Effect.fail(SessionInvalid.make({})) : Effect.succeed(record),\n ),\n ),\n rotate: (input, project) =>\n native(\n classify(\n Effect.gen(function* () {\n const absolute = DateTime.toEpochMillis(input.record.absoluteExpiresAt),\n expires = DateTime.toEpochMillis(input.nextExpiresAt);\n\n if (\n expires > absolute ||\n input.nextDigest === input.record.digest ||\n input.nextCredentialVersion === input.record.credentialVersion\n )\n return yield* SessionConflict.make({});\n\n const needsSample =\n batch !== undefined ||\n sql.onDialectOrElse({ mysql: () => true, orElse: () => false });\n\n const sampled = needsSample\n ? Number((yield* sql`select ${now} as engine_now`)[0]?.engine_now)\n : DateTime.toEpochMillis(input.record.issuedAt);\n\n sessionInvariant(Number.isSafeInteger(sampled));\n\n const prepared = {\n ...input.record,\n digest: input.nextDigest,\n credentialVersion: input.nextCredentialVersion,\n expiresAt: input.nextExpiresAt,\n issuedAt: DateTime.makeUnsafe(sampled),\n };\n\n const values = {\n ...s.encodeRotation(prepared),\n [s.digest]: prepared.digest,\n [s.issuedAt]: tables.expression(mapping.clock.fromMillis(now)),\n [s.expiresAt]: s.encodeInstant(prepared.expiresAt),\n };\n\n const condition = sql`${yield* records.owner(input.record)} and ${live(table)} and ${now} < ${expires} and ${records.millis(table, s.absoluteExpiresAt)} = ${absolute}`;\n const update = sql`${table.update(values)} where ${condition}`;\n let record = prepared;\n\n if (needsSample) {\n // Batch receipts are prepared before execution. The exact sampled time is\n // written and checked; no speculative UPDATE result is treated as committed.\n const statement = sql`${table.update({ ...values, [s.issuedAt]: s.encodeInstant(prepared.issuedAt) })} where ${condition} and ${now} >= ${sampled}`;\n const changed = yield* stage(statement, 1);\n\n if (changed !== 1) return yield* SessionConflict.make({});\n } else {\n const rows = yield* sql`${update} returning ${table.fields(\"session_\")}`;\n\n if (rows.length !== 1) return yield* SessionConflict.make({});\n record = yield* records.decode(table.decode(rows[0]!, \"session_\"));\n }\n yield* finalCondition(\n \"stateful-session-rotation\",\n yield* recordCondition(record, {\n ...values,\n [s.issuedAt]: s.encodeInstant(record.issuedAt),\n }),\n );\n\n return yield* prepareNativeSession(record, project);\n }),\n ),\n \"statement\",\n ),\n revokeDigest: (digest, project) =>\n native(\n Effect.gen(function* () {\n let expected: number | undefined;\n\n if (batch !== undefined)\n expected =\n (yield* sql`select 1 from ${table.name} where ${exact(table, s.digest, digest)} limit 2`)\n .length;\n\n const changed = yield* stage(\n sql`delete from ${table.name} where ${exact(table, s.digest, digest)}`,\n expected,\n );\n\n sessionInvariant(changed !== undefined && changed <= 1);\n yield* finalCondition(\n \"stateful-session-digest-revocation\",\n sql`not exists(select 1 from ${table.name} where ${exact(table, s.digest, digest)})`,\n );\n\n return yield* prepareNativeSession(changed === 1, project);\n }),\n \"statement\",\n ),\n revoke: (input, project) =>\n native(\n Effect.gen(function* () {\n const subject = yield* mapping.subjectId.toNative(input.subjectId),\n session = yield* mapping.sessionId.toNative(input.sessionId);\n\n const condition = sql`${state.id(table, s.subjectId, subject)} and ${state.id(table, s.sessionId, session)}`;\n\n yield* stage(sql`delete from ${table.name} where ${condition}`);\n yield* finalCondition(\n \"stateful-session-revocation\",\n sql`not exists(select 1 from ${table.name} where ${condition})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n \"statement\",\n ),\n revokeAll: (input, project) =>\n native(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n owner = mapping.subject;\n\n const next = yield* allocateSessionValue(\n \"interactive\",\n owner.nextSecurityRevision?.(input.expectedSecurityRevision),\n owner.nextSecurityRevisionSync === undefined\n ? undefined\n : () => owner.nextSecurityRevisionSync!(input.expectedSecurityRevision),\n );\n\n sessionInvariant(next !== input.expectedSecurityRevision);\n\n const changed = yield* stage(\n sql`${state.subject.update({ [owner.securityRevision]: next })} where ${state.id(state.subject, owner.id, nativeSubject)} and ${state.activeSubject(state.subject)} and ${state.exact(state.subject, owner.securityRevision, input.expectedSecurityRevision)}`,\n 1,\n );\n\n if (changed !== 1) return yield* StaleAuthentication.make({});\n yield* finalCondition(\n \"all-session-revocation\",\n sql`exists(select 1 from ${state.subject.name} where ${state.id(state.subject, owner.id, nativeSubject)} and ${state.exact(state.subject, owner.securityRevision, next)})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n \"statement\",\n ),\n };\n\n const sessionRepository: SessionRepository = {\n list: (input) =>\n executor.read(\n Effect.gen(function* () {\n sessionInvariant(\n Number.isSafeInteger(input.limit) && input.limit > 0 && input.limit <= 1000,\n );\n\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n st = state.subject.as(\"listed_subject\"),\n list = table.as(\"listed_session\"),\n owner = mapping.subject;\n\n const cursor =\n input.cursor === undefined\n ? undefined\n : yield* mapping.sessionId.toNative(\n yield* Schema.decodeEffect(SessionId)(input.cursor),\n );\n\n const rows =\n yield* sql`select ${list.fields(\"session_\")}, ${st.fields(\"subject_\")} from ${list.name} join ${st.name} on ${state.id(st, owner.id, nativeSubject)} and ${state.activeSubject(st)} and ${exactSqlText(sql, st.column(owner.securityRevision), list.column(s.securityRevision))} where ${state.id(list, s.subjectId, nativeSubject)} and ${live(list)}${cursor === undefined ? sql`` : sql` and ${list.column(s.sessionId)} > ${list.value(s.sessionId, cursor)}`} order by ${list.column(s.sessionId)} limit ${input.limit + 1}`;\n\n const decoded = yield* Effect.forEach(rows.slice(0, input.limit), (row) =>\n Effect.gen(function* () {\n const record = yield* records.decode(list.decode(row, \"session_\"));\n\n sessionInvariant(\n record.subjectId === input.subjectId &&\n (yield* mapping.subjectId.toSubject(st.decode(row, \"subject_\")[owner.id])) ===\n input.subjectId,\n );\n\n return yield* Schema.decodeEffect(Schema.toType(SessionMetadata))(record);\n }),\n );\n\n const nextCursor = rows.length > input.limit ? decoded.at(-1)?.sessionId : undefined;\n\n return { sessions: decoded, ...(nextCursor === undefined ? {} : { nextCursor }) };\n }),\n ),\n };\n\n return { statefulSessionPersistence: persistence, sessionRepository };\n});\n"],"mappings":"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"}
|
|
@@ -90,7 +90,8 @@ const makeNativeSessionStepUpServices = Effect.fnUntraced(function* (tables, map
|
|
|
90
90
|
...input,
|
|
91
91
|
version
|
|
92
92
|
}, snapshot = yield* encodeStepUpIntent(intent);
|
|
93
|
-
|
|
93
|
+
yield* state.guardPolicy(current);
|
|
94
|
+
const condition = sql`${state.authorityCondition(intent.revision, current.native)} and ${original.condition} and ${now} >= ${DateTime.toEpochMillis(intent.sourceAuthenticatedAt)} and ${now} < ${DateTime.toEpochMillis(intent.expiresAt)}`;
|
|
94
95
|
yield* pending.insert({
|
|
95
96
|
moduleId: mapping.moduleId,
|
|
96
97
|
kind: "StepUp",
|
|
@@ -143,7 +144,8 @@ const makeNativeSessionStepUpServices = Effect.fnUntraced(function* (tables, map
|
|
|
143
144
|
const deadline = Math.min(base.validUntil, profile.validUntil, DateTime.toEpochMillis(intent.expiresAt), DateTime.toEpochMillis(replacement.inspection.session.expiresAt));
|
|
144
145
|
if (current.now >= deadline || DateTime.toEpochMillis(replacement.inspection.session.issuedAt) > current.now || DateTime.toEpochMillis(replacement.inspection.session.expiresAt) > DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)) return yield* SessionStepUpInvalid.make({});
|
|
145
146
|
const snapshot = yield* encodeStepUpIntent(intent);
|
|
146
|
-
|
|
147
|
+
yield* state.guardPolicy(current);
|
|
148
|
+
const authority = sql`${state.authorityCondition(evidence.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;
|
|
147
149
|
yield* pending.consume("StepUp", intent, current.native, sql`${snapshotCondition(intent, snapshot)} and ${authority} and ${original.condition}`);
|
|
148
150
|
let replacementCondition = sql`1 = 1`;
|
|
149
151
|
let replacementRecord;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-step-up.mjs","names":[],"sources":["../../src/internal/session-native-step-up.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { AuthenticationAssurance } from \"@yielded/auth/Operations\";\nimport {\n SessionConflict,\n SessionStepUpInvalid,\n StaleAuthentication,\n SessionMetadata,\n type SessionStepUpPersistence,\n type SessionStepUpIntent,\n type StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option, Schema } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { SessionStepUpMapping } from \"./models/step-up-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { sessionEvidenceDeadline } from \"./session-native-authority\";\nimport { prepareNativeSession, type StoredSessionPending } from \"./session-native-pending\";\nimport { makeNativeSessionPendingReader } from \"./session-native-pending-read\";\nimport {\n makeNativeSessionRecords,\n sameSessionRecord,\n makeConditionalSqlInsert,\n} from \"./session-native-record\";\nimport {\n sameSessionRevision,\n normalizeSessionOperation,\n sessionInvariant,\n sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue } from \"./session-policy\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n} from \"./sql-commit\";\nimport {\n encodeStepUpIntent,\n decodeStepUpIntent,\n stepUpIntentLive,\n validateStepUpPlan,\n} from \"./step-up-state\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeSessionStepUpMapping<Claims> = SessionStepUpMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n unknown\n>;\n\nconst assuranceJson = Schema.encodeSync(Schema.fromJsonString(SessionMetadata.fields.assurance));\n\nexport const makeNativeSessionStepUpServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeSessionStepUpMapping<Claims>,\n): Effect.fn.Return<\n { readonly sessionStepUpPersistence: SessionStepUpPersistence<Claims> },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const {\n state,\n pending,\n read: readPending,\n } = yield* makeNativeSessionPendingReader(tables, mapping, batch !== undefined);\n\n const conditionalInsert = yield* makeConditionalSqlInsert();\n\n const records =\n mapping.source.kind === \"Stateful\"\n ? yield* makeNativeSessionRecords(tables, { ...mapping, ...mapping.source })\n : undefined;\n\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable),\n external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const { sql, now } = state;\n\n const native = <A, E, R>(\n work: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), mode)\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const decode = Effect.fnUntraced(function* (stored: StoredSessionPending) {\n const intent = yield* decodeStepUpIntent(stored.snapshot);\n\n sessionInvariant(\n intent.digest === stored.digest &&\n intent.version === stored.version &&\n intent.flowId === stored.flowId &&\n intent.bindingDigest === stored.bindingDigest &&\n intent.revision.subjectId === stored.subjectId &&\n DateTime.toEpochMillis(intent.expiresAt) === stored.expiresAtMillis &&\n intent.attemptLimit === stored.attemptLimit,\n );\n\n return intent;\n });\n\n const source = Effect.fnUntraced(function* (\n intent: Omit<SessionStepUpIntent, \"version\">,\n instant: number,\n ) {\n if (!stepUpIntentLive(intent, mapping.source.kind, DateTime.makeUnsafe(instant)))\n return undefined;\n const subject = yield* mapping.subjectId.toNative(intent.revision.subjectId);\n\n if (mapping.source.kind === \"Stateful\") {\n sessionInvariant(records !== undefined);\n\n const nativeSession = yield* mapping.source.sessionId.toNative(intent.sourceSessionId),\n { s, table } = records;\n\n const rows =\n yield* sql`select ${table.fields(\"source_\")} from ${table.name} where ${state.id(table, s.subjectId, subject)} and ${state.id(table, s.sessionId, nativeSession)} and ${records.live(table)} limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n const record = yield* records.decode(table.decode(rows[0], \"source_\"));\n\n if (\n record.credentialVersion !== intent.sourceCredentialVersion ||\n record.subjectId !== intent.revision.subjectId ||\n record.sessionId !== intent.sourceSessionId ||\n record.securityRevision !== intent.revision.securityRevision ||\n DateTime.toEpochMillis(record.assurance.authenticatedAt) !==\n DateTime.toEpochMillis(intent.sourceAuthenticatedAt) ||\n DateTime.toEpochMillis(record.expiresAt) !==\n DateTime.toEpochMillis(intent.sourceExpiresAt) ||\n DateTime.toEpochMillis(record.absoluteExpiresAt) !==\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)\n )\n return undefined;\n\n return {\n record,\n condition: sql`exists(select 1 from ${table.name} where ${yield* records.owner(record)} and ${records.live(table)})`,\n };\n }\n if (mapping.source.kind === \"StateAssistedSigned\") {\n const t = mapping.source.tombstone,\n table = tables(t.table),\n session = yield* mapping.source.sessionId.toNative(intent.sourceSessionId);\n\n const condition = sql`not exists(select 1 from ${table.name} where ${state.exact(table, t.moduleId, mapping.moduleId)} and ${state.id(table, t.subjectId, subject)} and ${state.id(table, t.sessionId, session)})`;\n\n if ((yield* sql`select 1 where ${condition}`).length !== 1) return undefined;\n\n return { record: undefined, condition };\n }\n\n return { record: undefined, condition: sql`1 = 1` };\n });\n\n const final = Effect.fnUntraced(function* (name: string, condition: Fragment) {\n if (batch !== undefined)\n yield* registerSqlBatchPostcondition({ name, statement: sqlBatchAssertion(sql, condition) });\n else if (Option.isSome(external))\n yield* registerSqlPostcondition({\n name,\n check: Effect.gen(function* () {\n sessionInvariant((yield* sql`select 1 where ${condition}`).length === 1);\n }).pipe(\n Effect.mapError((cause) => PersistenceMappingError.make({ operation: \"decode\", cause })),\n ),\n });\n });\n\n const snapshotCondition = (intent: SessionStepUpIntent, snapshot: string) =>\n sql.and([\n state.exact(pending.table, pending.p.snapshot, snapshot),\n sql`${tables.expression(mapping.clock.toMillis(pending.table.column(pending.p.expiresAt)))} = ${DateTime.toEpochMillis(intent.expiresAt)}`,\n sql`${pending.table.column(pending.p.attemptLimit)} = ${pending.table.value(pending.p.attemptLimit, intent.attemptLimit)}`,\n ]);\n\n const sessionStepUpPersistence: SessionStepUpPersistence<Claims> = {\n create: (input, _now, project) =>\n native(\n Effect.gen(function* () {\n const current = yield* state.read(input.revision.subjectId, true);\n\n if (current === undefined || !sameSessionRevision(current.revision, input.revision))\n return yield* StaleAuthentication.make({});\n const original = yield* source(input, current.now);\n\n if (original === undefined) return yield* StaleAuthentication.make({});\n\n const version = yield* allocateSessionValue(\n \"interactive\",\n mapping.pending.allocateVersion,\n mapping.pending.allocateVersionSync,\n );\n\n const intent = { ...input, version },\n snapshot = yield* encodeStepUpIntent(intent);\n\n const condition = sql`${state.authorityCondition(intent.revision, current.native)} and ${state.policyCondition(current.row, current.native)} and ${original.condition} and ${now} >= ${DateTime.toEpochMillis(intent.sourceAuthenticatedAt)} and ${now} < ${DateTime.toEpochMillis(intent.expiresAt)}`;\n\n yield* pending\n .insert(\n {\n moduleId: mapping.moduleId,\n kind: \"StepUp\",\n digest: intent.digest,\n version,\n flowId: intent.flowId,\n subjectId: current.native,\n bindingDigest: intent.bindingDigest,\n snapshot,\n expiresAt: intent.expiresAt,\n attemptLimit: intent.attemptLimit,\n },\n condition,\n )\n .pipe(\n Effect.mapError((error) =>\n isMappedConstraintConflict(mapping.isConstraintConflict, error)\n ? SessionConflict.make({})\n : error,\n ),\n );\n yield* final(\n \"session-step-up-issuance\",\n sql`${condition} and exists(select 1 from ${pending.table.name} where ${pending.predicate(pending.table, \"StepUp\", intent.digest)} and ${state.exact(pending.table, pending.p.version, version)} and ${snapshotCondition(intent, snapshot)})`,\n );\n\n return yield* prepareNativeSession(intent, project);\n }),\n ),\n read: ({ digest }) =>\n executor\n .read(\n Effect.gen(function* () {\n const selected = yield* readPending(\"StepUp\", digest);\n\n if (selected === undefined) return undefined;\n\n const intent = yield* decode(selected.record),\n current = selected.authority;\n\n if (\n !sameSessionRevision(current.revision, intent.revision) ||\n !stepUpIntentLive(intent, mapping.source.kind, DateTime.makeUnsafe(current.now))\n )\n return undefined;\n\n // The detached source stays original. Its live digest and metadata\n // are rechecked by complete before any replacement is committed.\n return { intent, requirement: current.requirement };\n }),\n )\n .pipe(\n Effect.flatMap((value) =>\n value === undefined\n ? Effect.fail(SessionStepUpInvalid.make({}))\n : Effect.succeed(value),\n ),\n ),\n reject: ({ digest }, project) =>\n native(\n Effect.gen(function* () {\n yield* pending.reject(\"StepUp\", digest);\n\n return yield* prepareNativeSession({ _tag: \"Rejected\" } as const, project);\n }),\n \"statement\",\n ),\n complete: (plan, project) =>\n native(\n Effect.gen(function* () {\n yield* validateStepUpPlan(plan);\n const { intent, evidence, replacement } = plan;\n const current = yield* state.read(intent.revision.subjectId, true);\n\n if (\n current === undefined ||\n !sameSessionRevision(current.revision, evidence.revision) ||\n !sameSessionRevision(current.revision, intent.revision)\n )\n return yield* StaleAuthentication.make({});\n const original = yield* source(intent, current.now);\n\n if (original === undefined) return yield* SessionStepUpInvalid.make({});\n if (plan.source.guard._tag === \"Stateful\") {\n if (\n original.record === undefined ||\n original.record.digest !== plan.source.guard.digest ||\n !sameSessionRecord(original.record, {\n ...plan.source.inspection.session,\n provenance: plan.source.inspection.provenance,\n credentialVersion: plan.source.inspection.credentialVersion,\n digest: plan.source.guard.digest,\n })\n )\n return yield* SessionStepUpInvalid.make({});\n }\n\n const base = yield* sessionEvidenceDeadline(evidence, current.requirement, current.now),\n profile = yield* sessionEvidenceDeadline(evidence, intent.requirement, current.now);\n\n if (\n assuranceJson(\n AuthenticationAssurance.make({\n ...base.assessed.assurance,\n authenticatedAt: profile.assessed.assurance.authenticatedAt,\n }),\n ) !== assuranceJson(replacement.inspection.session.assurance)\n )\n return yield* SessionStepUpInvalid.make({});\n\n const deadline = Math.min(\n base.validUntil,\n profile.validUntil,\n DateTime.toEpochMillis(intent.expiresAt),\n DateTime.toEpochMillis(replacement.inspection.session.expiresAt),\n );\n\n if (\n current.now >= deadline ||\n DateTime.toEpochMillis(replacement.inspection.session.issuedAt) > current.now ||\n DateTime.toEpochMillis(replacement.inspection.session.expiresAt) >\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)\n )\n return yield* SessionStepUpInvalid.make({});\n const snapshot = yield* encodeStepUpIntent(intent);\n const authority = sql`${state.authorityCondition(evidence.revision, current.native)} and ${state.policyCondition(current.row, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n yield* pending.consume(\n \"StepUp\",\n intent,\n current.native,\n sql`${snapshotCondition(intent, snapshot)} and ${authority} and ${original.condition}`,\n );\n let replacementCondition: Fragment = sql`1 = 1`;\n let replacementRecord: StatefulSessionRecord<Claims> | undefined;\n\n if (replacement._tag === \"Stateful\" && mapping.source.kind === \"Stateful\") {\n sessionInvariant(records !== undefined && original.record !== undefined);\n\n const next: StatefulSessionRecord<Claims> = {\n ...replacement.inspection.session,\n digest: replacement.nextDigest,\n credentialVersion: replacement.inspection.credentialVersion,\n provenance: replacement.inspection.provenance,\n };\n\n if (\n next.digest === original.record.digest ||\n next.credentialVersion === original.record.credentialVersion\n )\n return yield* SessionStepUpInvalid.make({});\n const { s, table } = records;\n\n const values = {\n ...s.encodeRotation(next),\n [s.digest]: next.digest,\n [s.issuedAt]: s.encodeInstant(next.issuedAt),\n [s.expiresAt]: s.encodeInstant(next.expiresAt),\n [s.absoluteExpiresAt]: s.encodeInstant(next.absoluteExpiresAt),\n [s.securityRevision]: next.securityRevision,\n };\n\n yield* pending.stage(\n sql`${table.update(values)} where ${yield* records.owner(original.record)} and ${records.live(table)} and ${authority}`,\n 1,\n );\n replacementCondition = sql`exists(select 1 from ${table.name} where ${yield* records.owner(next)} and ${records.encodedCondition(values)} and ${records.live(table)})`;\n replacementRecord = next;\n } else if (\n replacement._tag === \"StateAssistedSigned\" &&\n mapping.source.kind === \"StateAssistedSigned\"\n ) {\n const t = mapping.source.tombstone,\n table = tables(t.table),\n session = yield* mapping.source.sessionId.toNative(intent.sourceSessionId);\n\n const values = {\n ...t.encodeInsert({\n moduleId: mapping.moduleId,\n subjectId: current.native,\n sessionId: session,\n absoluteExpiresAt: intent.sourceAbsoluteExpiresAt,\n }),\n [t.moduleId]: mapping.moduleId,\n [t.subjectId]: current.native,\n [t.sessionId]: session,\n [t.absoluteExpiresAt]: t.encodeInstant(intent.sourceAbsoluteExpiresAt),\n };\n\n yield* pending.stage(\n conditionalInsert(table, values, sql`${authority} and ${original.condition}`),\n 1,\n );\n replacementCondition = sql`exists(select 1 from ${table.name} where ${state.exact(table, t.moduleId, mapping.moduleId)} and ${state.id(table, t.subjectId, current.native)} and ${state.id(table, t.sessionId, session)} and ${tables.expression(mapping.clock.toMillis(table.column(t.absoluteExpiresAt)))} >= ${DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)})`;\n } else if (\n replacement._tag !== \"StatelessSigned\" ||\n mapping.source.kind !== \"StatelessSigned\"\n )\n return yield* SessionStepUpInvalid.make({});\n yield* final(\n \"session-step-up-completion\",\n sql`${authority} and ${pending.consumedCondition(\"StepUp\", intent, current.native)} and ${replacementCondition}`,\n );\n if (batch === undefined && Option.isSome(external))\n yield* registerSqlPostcondition({\n name: \"session-step-up-current-policy\",\n check: Effect.gen(function* () {\n const actual = yield* state.read(intent.revision.subjectId);\n\n sessionInvariant(\n actual !== undefined && sameSessionRevision(actual.revision, evidence.revision),\n );\n yield* sessionEvidenceDeadline(evidence, actual.requirement, actual.now);\n yield* sessionEvidenceDeadline(evidence, intent.requirement, actual.now);\n if (replacementRecord !== undefined && records !== undefined) {\n const row = yield* records.read(replacementRecord.digest);\n\n sessionInvariant(row !== undefined && sameSessionRecord(row, replacementRecord));\n }\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepareNativeSession(replacement.inspection, project);\n }),\n ),\n };\n\n return { sessionStepUpPersistence };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-native-step-up.mjs","names":[],"sources":["../../src/internal/session-native-step-up.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { AuthenticationAssurance } from \"@yielded/auth/Operations\";\nimport {\n SessionConflict,\n SessionStepUpInvalid,\n StaleAuthentication,\n SessionMetadata,\n type SessionStepUpPersistence,\n type SessionStepUpIntent,\n type StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option, Schema } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { SessionStepUpMapping } from \"./models/step-up-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { sessionEvidenceDeadline } from \"./session-native-authority\";\nimport { prepareNativeSession, type StoredSessionPending } from \"./session-native-pending\";\nimport { makeNativeSessionPendingReader } from \"./session-native-pending-read\";\nimport {\n makeNativeSessionRecords,\n sameSessionRecord,\n makeConditionalSqlInsert,\n} from \"./session-native-record\";\nimport {\n sameSessionRevision,\n normalizeSessionOperation,\n sessionInvariant,\n sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue } from \"./session-policy\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n} from \"./sql-commit\";\nimport {\n encodeStepUpIntent,\n decodeStepUpIntent,\n stepUpIntentLive,\n validateStepUpPlan,\n} from \"./step-up-state\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeSessionStepUpMapping<Claims> = SessionStepUpMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n unknown\n>;\n\nconst assuranceJson = Schema.encodeSync(Schema.fromJsonString(SessionMetadata.fields.assurance));\n\nexport const makeNativeSessionStepUpServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeSessionStepUpMapping<Claims>,\n): Effect.fn.Return<\n { readonly sessionStepUpPersistence: SessionStepUpPersistence<Claims> },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const {\n state,\n pending,\n read: readPending,\n } = yield* makeNativeSessionPendingReader(tables, mapping, batch !== undefined);\n\n const conditionalInsert = yield* makeConditionalSqlInsert();\n\n const records =\n mapping.source.kind === \"Stateful\"\n ? yield* makeNativeSessionRecords(tables, { ...mapping, ...mapping.source })\n : undefined;\n\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable),\n external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const { sql, now } = state;\n\n const native = <A, E, R>(\n work: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), mode)\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const decode = Effect.fnUntraced(function* (stored: StoredSessionPending) {\n const intent = yield* decodeStepUpIntent(stored.snapshot);\n\n sessionInvariant(\n intent.digest === stored.digest &&\n intent.version === stored.version &&\n intent.flowId === stored.flowId &&\n intent.bindingDigest === stored.bindingDigest &&\n intent.revision.subjectId === stored.subjectId &&\n DateTime.toEpochMillis(intent.expiresAt) === stored.expiresAtMillis &&\n intent.attemptLimit === stored.attemptLimit,\n );\n\n return intent;\n });\n\n const source = Effect.fnUntraced(function* (\n intent: Omit<SessionStepUpIntent, \"version\">,\n instant: number,\n ) {\n if (!stepUpIntentLive(intent, mapping.source.kind, DateTime.makeUnsafe(instant)))\n return undefined;\n const subject = yield* mapping.subjectId.toNative(intent.revision.subjectId);\n\n if (mapping.source.kind === \"Stateful\") {\n sessionInvariant(records !== undefined);\n\n const nativeSession = yield* mapping.source.sessionId.toNative(intent.sourceSessionId),\n { s, table } = records;\n\n const rows =\n yield* sql`select ${table.fields(\"source_\")} from ${table.name} where ${state.id(table, s.subjectId, subject)} and ${state.id(table, s.sessionId, nativeSession)} and ${records.live(table)} limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n const record = yield* records.decode(table.decode(rows[0], \"source_\"));\n\n if (\n record.credentialVersion !== intent.sourceCredentialVersion ||\n record.subjectId !== intent.revision.subjectId ||\n record.sessionId !== intent.sourceSessionId ||\n record.securityRevision !== intent.revision.securityRevision ||\n DateTime.toEpochMillis(record.assurance.authenticatedAt) !==\n DateTime.toEpochMillis(intent.sourceAuthenticatedAt) ||\n DateTime.toEpochMillis(record.expiresAt) !==\n DateTime.toEpochMillis(intent.sourceExpiresAt) ||\n DateTime.toEpochMillis(record.absoluteExpiresAt) !==\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)\n )\n return undefined;\n\n return {\n record,\n condition: sql`exists(select 1 from ${table.name} where ${yield* records.owner(record)} and ${records.live(table)})`,\n };\n }\n if (mapping.source.kind === \"StateAssistedSigned\") {\n const t = mapping.source.tombstone,\n table = tables(t.table),\n session = yield* mapping.source.sessionId.toNative(intent.sourceSessionId);\n\n const condition = sql`not exists(select 1 from ${table.name} where ${state.exact(table, t.moduleId, mapping.moduleId)} and ${state.id(table, t.subjectId, subject)} and ${state.id(table, t.sessionId, session)})`;\n\n if ((yield* sql`select 1 where ${condition}`).length !== 1) return undefined;\n\n return { record: undefined, condition };\n }\n\n return { record: undefined, condition: sql`1 = 1` };\n });\n\n const final = Effect.fnUntraced(function* (name: string, condition: Fragment) {\n if (batch !== undefined)\n yield* registerSqlBatchPostcondition({ name, statement: sqlBatchAssertion(sql, condition) });\n else if (Option.isSome(external))\n yield* registerSqlPostcondition({\n name,\n check: Effect.gen(function* () {\n sessionInvariant((yield* sql`select 1 where ${condition}`).length === 1);\n }).pipe(\n Effect.mapError((cause) => PersistenceMappingError.make({ operation: \"decode\", cause })),\n ),\n });\n });\n\n const snapshotCondition = (intent: SessionStepUpIntent, snapshot: string) =>\n sql.and([\n state.exact(pending.table, pending.p.snapshot, snapshot),\n sql`${tables.expression(mapping.clock.toMillis(pending.table.column(pending.p.expiresAt)))} = ${DateTime.toEpochMillis(intent.expiresAt)}`,\n sql`${pending.table.column(pending.p.attemptLimit)} = ${pending.table.value(pending.p.attemptLimit, intent.attemptLimit)}`,\n ]);\n\n const sessionStepUpPersistence: SessionStepUpPersistence<Claims> = {\n create: (input, _now, project) =>\n native(\n Effect.gen(function* () {\n const current = yield* state.read(input.revision.subjectId, true);\n\n if (current === undefined || !sameSessionRevision(current.revision, input.revision))\n return yield* StaleAuthentication.make({});\n const original = yield* source(input, current.now);\n\n if (original === undefined) return yield* StaleAuthentication.make({});\n\n const version = yield* allocateSessionValue(\n \"interactive\",\n mapping.pending.allocateVersion,\n mapping.pending.allocateVersionSync,\n );\n\n const intent = { ...input, version },\n snapshot = yield* encodeStepUpIntent(intent);\n\n yield* state.guardPolicy(current);\n const condition = sql`${state.authorityCondition(intent.revision, current.native)} and ${original.condition} and ${now} >= ${DateTime.toEpochMillis(intent.sourceAuthenticatedAt)} and ${now} < ${DateTime.toEpochMillis(intent.expiresAt)}`;\n\n yield* pending\n .insert(\n {\n moduleId: mapping.moduleId,\n kind: \"StepUp\",\n digest: intent.digest,\n version,\n flowId: intent.flowId,\n subjectId: current.native,\n bindingDigest: intent.bindingDigest,\n snapshot,\n expiresAt: intent.expiresAt,\n attemptLimit: intent.attemptLimit,\n },\n condition,\n )\n .pipe(\n Effect.mapError((error) =>\n isMappedConstraintConflict(mapping.isConstraintConflict, error)\n ? SessionConflict.make({})\n : error,\n ),\n );\n yield* final(\n \"session-step-up-issuance\",\n sql`${condition} and exists(select 1 from ${pending.table.name} where ${pending.predicate(pending.table, \"StepUp\", intent.digest)} and ${state.exact(pending.table, pending.p.version, version)} and ${snapshotCondition(intent, snapshot)})`,\n );\n\n return yield* prepareNativeSession(intent, project);\n }),\n ),\n read: ({ digest }) =>\n executor\n .read(\n Effect.gen(function* () {\n const selected = yield* readPending(\"StepUp\", digest);\n\n if (selected === undefined) return undefined;\n\n const intent = yield* decode(selected.record),\n current = selected.authority;\n\n if (\n !sameSessionRevision(current.revision, intent.revision) ||\n !stepUpIntentLive(intent, mapping.source.kind, DateTime.makeUnsafe(current.now))\n )\n return undefined;\n\n // The detached source stays original. Its live digest and metadata\n // are rechecked by complete before any replacement is committed.\n return { intent, requirement: current.requirement };\n }),\n )\n .pipe(\n Effect.flatMap((value) =>\n value === undefined\n ? Effect.fail(SessionStepUpInvalid.make({}))\n : Effect.succeed(value),\n ),\n ),\n reject: ({ digest }, project) =>\n native(\n Effect.gen(function* () {\n yield* pending.reject(\"StepUp\", digest);\n\n return yield* prepareNativeSession({ _tag: \"Rejected\" } as const, project);\n }),\n \"statement\",\n ),\n complete: (plan, project) =>\n native(\n Effect.gen(function* () {\n yield* validateStepUpPlan(plan);\n const { intent, evidence, replacement } = plan;\n const current = yield* state.read(intent.revision.subjectId, true);\n\n if (\n current === undefined ||\n !sameSessionRevision(current.revision, evidence.revision) ||\n !sameSessionRevision(current.revision, intent.revision)\n )\n return yield* StaleAuthentication.make({});\n const original = yield* source(intent, current.now);\n\n if (original === undefined) return yield* SessionStepUpInvalid.make({});\n if (plan.source.guard._tag === \"Stateful\") {\n if (\n original.record === undefined ||\n original.record.digest !== plan.source.guard.digest ||\n !sameSessionRecord(original.record, {\n ...plan.source.inspection.session,\n provenance: plan.source.inspection.provenance,\n credentialVersion: plan.source.inspection.credentialVersion,\n digest: plan.source.guard.digest,\n })\n )\n return yield* SessionStepUpInvalid.make({});\n }\n\n const base = yield* sessionEvidenceDeadline(evidence, current.requirement, current.now),\n profile = yield* sessionEvidenceDeadline(evidence, intent.requirement, current.now);\n\n if (\n assuranceJson(\n AuthenticationAssurance.make({\n ...base.assessed.assurance,\n authenticatedAt: profile.assessed.assurance.authenticatedAt,\n }),\n ) !== assuranceJson(replacement.inspection.session.assurance)\n )\n return yield* SessionStepUpInvalid.make({});\n\n const deadline = Math.min(\n base.validUntil,\n profile.validUntil,\n DateTime.toEpochMillis(intent.expiresAt),\n DateTime.toEpochMillis(replacement.inspection.session.expiresAt),\n );\n\n if (\n current.now >= deadline ||\n DateTime.toEpochMillis(replacement.inspection.session.issuedAt) > current.now ||\n DateTime.toEpochMillis(replacement.inspection.session.expiresAt) >\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)\n )\n return yield* SessionStepUpInvalid.make({});\n const snapshot = yield* encodeStepUpIntent(intent);\n\n yield* state.guardPolicy(current);\n const authority = sql`${state.authorityCondition(evidence.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n yield* pending.consume(\n \"StepUp\",\n intent,\n current.native,\n sql`${snapshotCondition(intent, snapshot)} and ${authority} and ${original.condition}`,\n );\n let replacementCondition: Fragment = sql`1 = 1`;\n let replacementRecord: StatefulSessionRecord<Claims> | undefined;\n\n if (replacement._tag === \"Stateful\" && mapping.source.kind === \"Stateful\") {\n sessionInvariant(records !== undefined && original.record !== undefined);\n\n const next: StatefulSessionRecord<Claims> = {\n ...replacement.inspection.session,\n digest: replacement.nextDigest,\n credentialVersion: replacement.inspection.credentialVersion,\n provenance: replacement.inspection.provenance,\n };\n\n if (\n next.digest === original.record.digest ||\n next.credentialVersion === original.record.credentialVersion\n )\n return yield* SessionStepUpInvalid.make({});\n const { s, table } = records;\n\n const values = {\n ...s.encodeRotation(next),\n [s.digest]: next.digest,\n [s.issuedAt]: s.encodeInstant(next.issuedAt),\n [s.expiresAt]: s.encodeInstant(next.expiresAt),\n [s.absoluteExpiresAt]: s.encodeInstant(next.absoluteExpiresAt),\n [s.securityRevision]: next.securityRevision,\n };\n\n yield* pending.stage(\n sql`${table.update(values)} where ${yield* records.owner(original.record)} and ${records.live(table)} and ${authority}`,\n 1,\n );\n replacementCondition = sql`exists(select 1 from ${table.name} where ${yield* records.owner(next)} and ${records.encodedCondition(values)} and ${records.live(table)})`;\n replacementRecord = next;\n } else if (\n replacement._tag === \"StateAssistedSigned\" &&\n mapping.source.kind === \"StateAssistedSigned\"\n ) {\n const t = mapping.source.tombstone,\n table = tables(t.table),\n session = yield* mapping.source.sessionId.toNative(intent.sourceSessionId);\n\n const values = {\n ...t.encodeInsert({\n moduleId: mapping.moduleId,\n subjectId: current.native,\n sessionId: session,\n absoluteExpiresAt: intent.sourceAbsoluteExpiresAt,\n }),\n [t.moduleId]: mapping.moduleId,\n [t.subjectId]: current.native,\n [t.sessionId]: session,\n [t.absoluteExpiresAt]: t.encodeInstant(intent.sourceAbsoluteExpiresAt),\n };\n\n yield* pending.stage(\n conditionalInsert(table, values, sql`${authority} and ${original.condition}`),\n 1,\n );\n replacementCondition = sql`exists(select 1 from ${table.name} where ${state.exact(table, t.moduleId, mapping.moduleId)} and ${state.id(table, t.subjectId, current.native)} and ${state.id(table, t.sessionId, session)} and ${tables.expression(mapping.clock.toMillis(table.column(t.absoluteExpiresAt)))} >= ${DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)})`;\n } else if (\n replacement._tag !== \"StatelessSigned\" ||\n mapping.source.kind !== \"StatelessSigned\"\n )\n return yield* SessionStepUpInvalid.make({});\n yield* final(\n \"session-step-up-completion\",\n sql`${authority} and ${pending.consumedCondition(\"StepUp\", intent, current.native)} and ${replacementCondition}`,\n );\n if (batch === undefined && Option.isSome(external))\n yield* registerSqlPostcondition({\n name: \"session-step-up-current-policy\",\n check: Effect.gen(function* () {\n const actual = yield* state.read(intent.revision.subjectId);\n\n sessionInvariant(\n actual !== undefined && sameSessionRevision(actual.revision, evidence.revision),\n );\n yield* sessionEvidenceDeadline(evidence, actual.requirement, actual.now);\n yield* sessionEvidenceDeadline(evidence, intent.requirement, actual.now);\n if (replacementRecord !== undefined && records !== undefined) {\n const row = yield* records.read(replacementRecord.digest);\n\n sessionInvariant(row !== undefined && sameSessionRecord(row, replacementRecord));\n }\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepareNativeSession(replacement.inspection, project);\n }),\n ),\n };\n\n return { sessionStepUpPersistence };\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { appendSqlBatchStatement, registerSqlBatchPostcondition } from "./sql-commit.mjs";
|
|
2
|
+
import { Array, Effect } from "effect";
|
|
3
|
+
import { SqlClient } from "effect/sql/SqlClient";
|
|
4
|
+
//#region src/internal/sqlite-policy-guard.ts
|
|
5
|
+
/** Check original values before writes and at the end of the same atomic batch.
|
|
6
|
+
* Small groups bound parameters and expression depth without expanding retained
|
|
7
|
+
* values into hex/JSON. BINARY preserves text bytes; IS also compares NULLs. */
|
|
8
|
+
const guardSqlitePolicy = Effect.fnUntraced(function* (input) {
|
|
9
|
+
const sql = (yield* SqlClient).withoutTransforms();
|
|
10
|
+
for (const values of Array.chunksOf(input.values, 32)) {
|
|
11
|
+
const matches = sql.and(values.map(({ column, value }) => sql`${column} collate binary is ${value}`));
|
|
12
|
+
const statement = sql`select case when ${sql`exists(select 1 from ${input.table} where ${input.owner} and ${matches})`} then 1 else json_extract('[]', '$[auth-batch-precondition]') end as valid`;
|
|
13
|
+
yield* appendSqlBatchStatement(statement);
|
|
14
|
+
yield* registerSqlBatchPostcondition({
|
|
15
|
+
name: input.name,
|
|
16
|
+
statement
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
//#endregion
|
|
21
|
+
export { guardSqlitePolicy };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=sqlite-policy-guard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-policy-guard.mjs","names":[],"sources":["../../src/internal/sqlite-policy-guard.ts"],"sourcesContent":["import { Array, Effect } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { appendSqlBatchStatement, registerSqlBatchPostcondition } from \"./sql-commit\";\n\nexport interface SqlitePolicyValue {\n readonly column: Fragment;\n readonly value: Fragment;\n}\n\n/** Check original values before writes and at the end of the same atomic batch.\n * Small groups bound parameters and expression depth without expanding retained\n * values into hex/JSON. BINARY preserves text bytes; IS also compares NULLs. */\nexport const guardSqlitePolicy = Effect.fnUntraced(function* (input: {\n readonly name: string;\n readonly table: Fragment;\n readonly owner: Fragment;\n readonly values: ReadonlyArray<SqlitePolicyValue>;\n}) {\n const sql = (yield* SqlClient).withoutTransforms();\n\n for (const values of Array.chunksOf(input.values, 32)) {\n const matches = sql.and(\n values.map(({ column, value }) => sql`${column} collate binary is ${value}`),\n );\n\n const condition = sql`exists(select 1 from ${input.table} where ${input.owner} and ${matches})`;\n const statement = sql`select case when ${condition} then 1 else json_extract('[]', '$[auth-batch-precondition]') end as valid`;\n\n yield* appendSqlBatchStatement(statement);\n yield* registerSqlBatchPostcondition({ name: input.name, statement });\n }\n});\n"],"mappings":";;;;;;;AAcA,MAAa,oBAAoB,OAAO,WAAW,WAAW,OAK3D;CACD,MAAM,OAAO,OAAO,UAAA,CAAW,kBAAkB;CAEjD,KAAK,MAAM,UAAU,MAAM,SAAS,MAAM,QAAQ,EAAE,GAAG;EACrD,MAAM,UAAU,IAAI,IAClB,OAAO,KAAK,EAAE,QAAQ,YAAY,GAAG,GAAG,OAAO,qBAAqB,OAAO,CAC7E;EAGA,MAAM,YAAY,GAAG,oBAAoB,GADpB,wBAAwB,MAAM,MAAM,SAAS,MAAM,MAAM,OAAO,QAAQ,GAC1C;EAEnD,OAAO,wBAAwB,SAAS;EACxC,OAAO,8BAA8B;GAAE,MAAM,MAAM;GAAM;EAAU,CAAC;CACtE;AACF,CAAC"}
|
|
@@ -6,17 +6,19 @@ import { AnyPasswordPersistenceMapping } from "./models/password-model.mjs";
|
|
|
6
6
|
import { PendingAuthenticationMapping, StatefulSessionMapping } from "./models/session-model.mjs";
|
|
7
7
|
import { StorageRole } from "./storage-tables.mjs";
|
|
8
8
|
import { MappingInput } from "./configuration.mjs";
|
|
9
|
+
import { NativeSqlTables } from "./native-sql-table.mjs";
|
|
9
10
|
import { Crypto, Effect, Schema } from "effect";
|
|
11
|
+
import { SqlClient } from "effect/sql";
|
|
10
12
|
import { SecurityRevision } from "@yielded/auth/Sessions";
|
|
11
13
|
//#region src/internal/storage-mapping.d.ts
|
|
12
14
|
declare const string: (value: unknown) => string;
|
|
13
15
|
/** Derive shared row mappings from a managed or custom storage layout.
|
|
14
16
|
* Acquire Effect Crypto once; the returned allocators retain that implementation.
|
|
15
|
-
* Each factory requires its role tables to exist.
|
|
16
|
-
*
|
|
17
|
-
* authority policy for explicit services.
|
|
17
|
+
* Each factory requires its role tables to exist. Supplying native tables derives
|
|
18
|
+
* batch commit predicates and subject policy columns. Adapter authors may refine
|
|
19
|
+
* clocks and authority policy for explicit services.
|
|
18
20
|
*/
|
|
19
|
-
export declare const makeMappings: (input: MappingInput) => Effect.Effect<{
|
|
21
|
+
export declare const makeMappings: (input: MappingInput, tables?: NativeSqlTables | undefined) => Effect.Effect<{
|
|
20
22
|
table: (role: StorageRole) => object;
|
|
21
23
|
authority: () => {
|
|
22
24
|
subjectId: {
|
|
@@ -29,6 +31,7 @@ export declare const makeMappings: (input: MappingInput) => Effect.Effect<{
|
|
|
29
31
|
id: string;
|
|
30
32
|
status: string;
|
|
31
33
|
securityRevision: string;
|
|
34
|
+
requirementColumns?: string[];
|
|
32
35
|
isActiveStatus: (value: unknown) => boolean;
|
|
33
36
|
d1ActiveStatusValue: unknown;
|
|
34
37
|
activeStatusValue: unknown;
|
|
@@ -43,6 +46,7 @@ export declare const makeMappings: (input: MappingInput) => Effect.Effect<{
|
|
|
43
46
|
revision: string;
|
|
44
47
|
status: string;
|
|
45
48
|
activeStatusValue: boolean;
|
|
49
|
+
d1ActiveStatusValue: boolean;
|
|
46
50
|
isActiveStatus: (value: unknown) => value is true;
|
|
47
51
|
encodeInsert: (row: object) => {
|
|
48
52
|
active: boolean;
|
|
@@ -58,6 +62,6 @@ export declare const makeMappings: (input: MappingInput) => Effect.Effect<{
|
|
|
58
62
|
sessions: <C extends Schema.Codec<unknown, unknown, never, never>>(claims: C, moduleId: string) => StatefulSessionMapping<C["Type"], TableModel, TableModel, TableModel, TableModel, unknown, unknown>;
|
|
59
63
|
pending: <C extends Schema.Codec<unknown, unknown, never, never>>(claims: C, moduleId: string) => PendingAuthenticationMapping<C["Type"], TableModel, TableModel, TableModel, unknown>;
|
|
60
64
|
string: typeof string;
|
|
61
|
-
}, PersistenceMappingError, Crypto.Crypto |
|
|
65
|
+
}, PersistenceMappingError, Crypto.Crypto | SqlClient.SqlClient>;
|
|
62
66
|
//#endregion
|
|
63
67
|
//# sourceMappingURL=storage-mapping.d.mts.map
|