@yielded/auth-persistence 0.1.0-beta.31 → 0.1.0-beta.32
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 +9 -2
- package/dist/Adapter.mjs +8 -8
- package/dist/OAuthPersistence.mjs +10 -5
- package/dist/OAuthPersistence.mjs.map +1 -1
- package/dist/internal/configuration.d.mts +31 -5
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-native.mjs +10 -2
- package/dist/internal/email-native.mjs.map +1 -1
- package/dist/internal/email-policy.mjs +3 -2
- package/dist/internal/email-policy.mjs.map +1 -1
- package/dist/internal/models/oauth-model.d.mts +4 -2
- package/dist/internal/models/oauth-model.mjs.map +1 -1
- package/dist/internal/oauth/native-accounts.d.mts +1 -1
- package/dist/internal/oauth/native-accounts.mjs +6 -6
- package/dist/internal/oauth/native-accounts.mjs.map +1 -1
- package/dist/internal/oauth/native-connected.d.mts +1 -1
- package/dist/internal/oauth/native-connected.mjs +1 -1
- package/dist/internal/oauth/native-registration.mjs +19 -8
- package/dist/internal/oauth/native-registration.mjs.map +1 -1
- package/dist/internal/oauth/native-sign-in.d.mts +1 -1
- package/dist/internal/oauth/native-sign-in.mjs +1 -1
- package/dist/internal/oauth/native-state.mjs +21 -11
- package/dist/internal/oauth/native-state.mjs.map +1 -1
- package/dist/internal/oauth/state.d.mts +1 -1
- package/dist/internal/oauth/state.mjs +1 -1
- package/dist/internal/oauth-managed.mjs +182 -0
- package/dist/internal/oauth-managed.mjs.map +1 -0
- package/dist/internal/passkey-managed.mjs +1 -1
- package/dist/internal/passkey-native-registration.mjs +1 -1
- package/dist/internal/passkey-native-state.mjs +33 -23
- package/dist/internal/passkey-native-state.mjs.map +1 -1
- package/dist/internal/passkey-native-write.mjs +3 -3
- package/dist/internal/passkey-native-write.mjs.map +1 -1
- package/dist/internal/passkey-native.d.mts +15 -7
- package/dist/internal/passkey-native.mjs +2 -1
- package/dist/internal/passkey-native.mjs.map +1 -1
- package/dist/internal/passkey-policy.mjs +9 -7
- package/dist/internal/passkey-policy.mjs.map +1 -1
- package/dist/internal/password-native.mjs +9 -6
- package/dist/internal/password-native.mjs.map +1 -1
- package/dist/internal/password-policy.mjs +7 -6
- package/dist/internal/password-policy.mjs.map +1 -1
- package/dist/internal/persistence.mjs +96 -37
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/phone-native.mjs +1 -1
- package/dist/internal/phone-native.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +6 -5
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.d.mts +2 -2
- 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 +26 -20
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-record.mjs +6 -4
- package/dist/internal/session-native-record.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +3 -0
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.d.mts +2 -2
- package/dist/internal/session-native-stateful.mjs +65 -37
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.d.mts +2 -2
- package/dist/internal/session-native-step-up.mjs +14 -18
- package/dist/internal/session-native-step-up.mjs.map +1 -1
- package/dist/internal/session-native-validity.mjs +3 -1
- package/dist/internal/session-native-validity.mjs.map +1 -1
- package/dist/internal/sql-authority.mjs +5 -1
- package/dist/internal/sql-authority.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +2 -1
- package/dist/internal/sql-storage-validation.mjs.map +1 -1
- package/dist/internal/sql-table.d.mts +1 -0
- package/dist/internal/sql-table.mjs.map +1 -1
- package/dist/internal/step-up-state.mjs +19 -6
- package/dist/internal/step-up-state.mjs.map +1 -1
- package/dist/internal/storage-mapping.mjs +1 -1
- package/dist/internal/storage-tables.d.mts +237 -0
- package/dist/internal/storage-tables.mjs +68 -0
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +3 -1
- package/dist/internal/storage-validation.mjs +70 -17
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/totp-native.mjs +1 -1
- package/package.json +1 -1
- package/src/OAuthPersistence.ts +7 -2
- package/src/internal/configuration.ts +46 -7
- package/src/internal/email-native.ts +16 -6
- package/src/internal/email-policy.ts +12 -3
- package/src/internal/models/oauth-model.ts +7 -1
- package/src/internal/oauth/native-accounts.ts +3 -3
- package/src/internal/oauth/native-registration.ts +17 -6
- package/src/internal/oauth/native-state.ts +38 -18
- package/src/internal/oauth-managed.ts +277 -0
- package/src/internal/passkey-native-state.ts +59 -52
- package/src/internal/passkey-native-write.ts +2 -2
- package/src/internal/passkey-native.ts +4 -2
- package/src/internal/passkey-policy.ts +13 -9
- package/src/internal/password-native.ts +17 -9
- package/src/internal/password-policy.ts +16 -8
- package/src/internal/persistence.ts +216 -70
- package/src/internal/phone-native.ts +1 -1
- package/src/internal/session-native-authority.ts +23 -7
- package/src/internal/session-native-login.ts +6 -3
- package/src/internal/session-native-pending-read.ts +46 -33
- package/src/internal/session-native-record.ts +6 -4
- package/src/internal/session-native-state.ts +3 -0
- package/src/internal/session-native-stateful.ts +144 -51
- package/src/internal/session-native-step-up.ts +27 -23
- package/src/internal/session-native-validity.ts +3 -1
- package/src/internal/sql-authority.ts +5 -1
- package/src/internal/sql-storage-validation.ts +53 -46
- package/src/internal/sql-table.ts +1 -0
- package/src/internal/step-up-state.ts +43 -6
- package/src/internal/storage-tables.ts +76 -0
- package/src/internal/storage-validation.ts +127 -27
|
@@ -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 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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"}
|
|
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 { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport {\n SessionConflict,\n SessionStepUpInvalid,\n StaleAuthentication,\n type SessionStepUpPersistence,\n type SessionStepUpIntent,\n type StatefulSessionRecord,\n type SessionUnavailable,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } 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\nexport const makeNativeSessionStepUpServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeSessionStepUpMapping<Claims>,\n): Effect.fn.Return<\n { readonly sessionStepUpPersistence: SessionStepUpPersistence<Claims> },\n SessionUnavailable,\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 ).pipe(Effect.provideService(AuthenticationClock, state.clockPolicy));\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 (!(yield* 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.provideService(AuthenticationClock, state.clockPolicy),\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} >= ${current.now} and ${DateTime.toEpochMillis(intent.sourceAuthenticatedAt)} <= ${now} + ${state.clockPolicy.futureToleranceMillis} 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 !(yield* stepUpIntentLive(\n intent,\n mapping.source.kind,\n DateTime.makeUnsafe(current.now),\n ))\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.provideService(AuthenticationClock, state.clockPolicy),\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 prepared = yield* sessionEvidenceDeadline(\n evidence,\n plan.baseRequirement,\n current.now,\n ),\n base = yield* sessionEvidenceDeadline(evidence, current.requirement, current.now),\n profile = yield* sessionEvidenceDeadline(evidence, intent.requirement, current.now);\n\n const deadline = Math.min(\n prepared.validUntil,\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 state.clockPolicy.futureToleranceMillis ||\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, plan.baseRequirement, actual.now);\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.provideService(AuthenticationClock, state.clockPolicy),\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":";;;;;;;;;;;;;;AA4DA,MAAa,kCAAkC,OAAO,WAAW,WAC/D,QACA,SAKA;CACA,MAAM,QAAQ,OAAO;CAErB,MAAM,EACJ,OACA,SACA,MAAM,gBACJ,OAAO,+BAA+B,QAAQ,SAAS,UAAU,KAAA,CAAS;CAE9E,MAAM,oBAAoB,OAAO,yBAAyB;CAE1D,MAAM,UACJ,QAAQ,OAAO,SAAS,aACpB,OAAO,yBAAyB,QAAQ;EAAE,GAAG;EAAS,GAAG,QAAQ;CAAO,CAAC,IACzE,KAAA;CAEN,MAAM,WAAW,OAAO,sBAAsB,kBAAkB,GAC9D,WAAW,OAAO,OAAO,cAAc,gBAAgB;CAEzD,MAAM,EAAE,KAAK,QAAQ;CAErB,MAAM,UACJ,MACA,OAAoC,mBAEnC,UAAU,KAAA,IACP,SAAS,UAAU,0BAA0B,IAAI,GAAG,IAAI,IACxD,SACG,eAAe,0BAA0B,IAAI,CAAC,CAAC,CAC/C,KAAK,OAAO,eAAe,gBAAgB,KAAK,CAAC,EAAA,CACtD,KAAK,OAAO,eAAe,qBAAqB,MAAM,WAAW,CAAC;CAEtE,MAAM,SAAS,OAAO,WAAW,WAAW,QAA8B;EACxE,MAAM,SAAS,OAAO,mBAAmB,OAAO,QAAQ;EAExD,iBACE,OAAO,WAAW,OAAO,UACvB,OAAO,YAAY,OAAO,WAC1B,OAAO,WAAW,OAAO,UACzB,OAAO,kBAAkB,OAAO,iBAChC,OAAO,SAAS,cAAc,OAAO,aACrC,SAAS,cAAc,OAAO,SAAS,MAAM,OAAO,mBACpD,OAAO,iBAAiB,OAAO,YACnC;EAEA,OAAO;CACT,CAAC;CAED,MAAM,SAAS,OAAO,WAAW,WAC/B,QACA,SACA;EACA,IAAI,EAAE,OAAO,iBAAiB,QAAQ,QAAQ,OAAO,MAAM,SAAS,WAAW,OAAO,CAAC,IACrF,OAAO,KAAA;EACT,MAAM,UAAU,OAAO,QAAQ,UAAU,SAAS,OAAO,SAAS,SAAS;EAE3E,IAAI,QAAQ,OAAO,SAAS,YAAY;GACtC,iBAAiB,YAAY,KAAA,CAAS;GAEtC,MAAM,gBAAgB,OAAO,QAAQ,OAAO,UAAU,SAAS,OAAO,eAAe,GACnF,EAAE,GAAG,UAAU;GAEjB,MAAM,OACJ,OAAO,GAAG,UAAU,MAAM,OAAO,SAAS,EAAE,QAAQ,MAAM,KAAK,SAAS,MAAM,GAAG,OAAO,EAAE,WAAW,OAAO,EAAE,OAAO,MAAM,GAAG,OAAO,EAAE,WAAW,aAAa,EAAE,OAAO,QAAQ,KAAK,KAAK,EAAE;GAE9L,iBAAiB,KAAK,UAAU,CAAC;GACjC,IAAI,KAAK,OAAO,KAAA,GAAW,OAAO,KAAA;GAClC,MAAM,SAAS,OAAO,QAAQ,OAAO,MAAM,OAAO,KAAK,IAAI,SAAS,CAAC;GAErE,IACE,OAAO,sBAAsB,OAAO,2BACpC,OAAO,cAAc,OAAO,SAAS,aACrC,OAAO,cAAc,OAAO,mBAC5B,OAAO,qBAAqB,OAAO,SAAS,oBAC5C,SAAS,cAAc,OAAO,UAAU,eAAe,MACrD,SAAS,cAAc,OAAO,qBAAqB,KACrD,SAAS,cAAc,OAAO,SAAS,MACrC,SAAS,cAAc,OAAO,eAAe,KAC/C,SAAS,cAAc,OAAO,iBAAiB,MAC7C,SAAS,cAAc,OAAO,uBAAuB,GAEvD,OAAO,KAAA;GAET,OAAO;IACL;IACA,WAAW,GAAG,wBAAwB,MAAM,KAAK,SAAS,OAAO,QAAQ,MAAM,MAAM,EAAE,OAAO,QAAQ,KAAK,KAAK,EAAE;GACpH;EACF;EACA,IAAI,QAAQ,OAAO,SAAS,uBAAuB;GACjD,MAAM,IAAI,QAAQ,OAAO,WACvB,QAAQ,OAAO,EAAE,KAAK,GACtB,UAAU,OAAO,QAAQ,OAAO,UAAU,SAAS,OAAO,eAAe;GAE3E,MAAM,YAAY,GAAG,4BAA4B,MAAM,KAAK,SAAS,MAAM,MAAM,OAAO,EAAE,UAAU,QAAQ,QAAQ,EAAE,OAAO,MAAM,GAAG,OAAO,EAAE,WAAW,OAAO,EAAE,OAAO,MAAM,GAAG,OAAO,EAAE,WAAW,OAAO,EAAE;GAEhN,KAAK,OAAO,GAAG,kBAAkB,YAAA,CAAa,WAAW,GAAG,OAAO,KAAA;GAEnE,OAAO;IAAE,QAAQ,KAAA;IAAW;GAAU;EACxC;EAEA,OAAO;GAAE,QAAQ,KAAA;GAAW,WAAW,GAAG;EAAQ;CACpD,CAAC;CAED,MAAM,QAAQ,OAAO,WAAW,WAAW,MAAc,WAAqB;EAC5E,IAAI,UAAU,KAAA,GACZ,OAAO,8BAA8B;GAAE;GAAM,WAAW,kBAAkB,KAAK,SAAS;EAAE,CAAC;OACxF,IAAI,OAAO,OAAO,QAAQ,GAC7B,OAAO,yBAAyB;GAC9B;GACA,OAAO,OAAO,IAAI,aAAa;IAC7B,kBAAkB,OAAO,GAAG,kBAAkB,YAAA,CAAa,WAAW,CAAC;GACzE,CAAC,CAAC,CAAC,KACD,OAAO,eAAe,qBAAqB,MAAM,WAAW,GAC5D,OAAO,UAAU,UAAU,wBAAwB,KAAK;IAAE,WAAW;IAAU;GAAM,CAAC,CAAC,CACzF;EACF,CAAC;CACL,CAAC;CAED,MAAM,qBAAqB,QAA6B,aACtD,IAAI,IAAI;EACN,MAAM,MAAM,QAAQ,OAAO,QAAQ,EAAE,UAAU,QAAQ;EACvD,GAAG,GAAG,OAAO,WAAW,QAAQ,MAAM,SAAS,QAAQ,MAAM,OAAO,QAAQ,EAAE,SAAS,CAAC,CAAC,EAAE,KAAK,SAAS,cAAc,OAAO,SAAS;EACvI,GAAG,GAAG,QAAQ,MAAM,OAAO,QAAQ,EAAE,YAAY,EAAE,KAAK,QAAQ,MAAM,MAAM,QAAQ,EAAE,cAAc,OAAO,YAAY;CACzH,CAAC;CAyQH,OAAO,EAAE,0BAAA;EAtQP,SAAS,OAAO,MAAM,YACpB,OACE,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,MAAM,KAAK,MAAM,SAAS,WAAW,IAAI;GAEhE,IAAI,YAAY,KAAA,KAAa,CAAC,oBAAoB,QAAQ,UAAU,MAAM,QAAQ,GAChF,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;GAC3C,MAAM,WAAW,OAAO,OAAO,OAAO,QAAQ,GAAG;GAEjD,IAAI,aAAa,KAAA,GAAW,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;GAErE,MAAM,UAAU,OAAO,qBACrB,eACA,QAAQ,QAAQ,iBAChB,QAAQ,QAAQ,mBAClB;GAEA,MAAM,SAAS;IAAE,GAAG;IAAO;GAAQ,GACjC,WAAW,OAAO,mBAAmB,MAAM;GAE7C,OAAO,MAAM,YAAY,OAAO;GAChC,MAAM,YAAY,GAAG,GAAG,MAAM,mBAAmB,OAAO,UAAU,QAAQ,MAAM,EAAE,OAAO,SAAS,UAAU,OAAO,IAAI,MAAM,QAAQ,IAAI,OAAO,SAAS,cAAc,OAAO,qBAAqB,EAAE,MAAM,IAAI,KAAK,MAAM,YAAY,sBAAsB,OAAO,IAAI,KAAK,SAAS,cAAc,OAAO,SAAS;GAEnT,OAAO,QACJ,OACC;IACE,UAAU,QAAQ;IAClB,MAAM;IACN,QAAQ,OAAO;IACf;IACA,QAAQ,OAAO;IACf,WAAW,QAAQ;IACnB,eAAe,OAAO;IACtB;IACA,WAAW,OAAO;IAClB,cAAc,OAAO;GACvB,GACA,SACF,CAAC,CACA,KACC,OAAO,UAAU,UACf,2BAA2B,QAAQ,sBAAsB,KAAK,IAC1D,gBAAgB,KAAK,CAAC,CAAC,IACvB,KACN,CACF;GACF,OAAO,MACL,4BACA,GAAG,GAAG,UAAU,4BAA4B,QAAQ,MAAM,KAAK,SAAS,QAAQ,UAAU,QAAQ,OAAO,UAAU,OAAO,MAAM,EAAE,OAAO,MAAM,MAAM,QAAQ,OAAO,QAAQ,EAAE,SAAS,OAAO,EAAE,OAAO,kBAAkB,QAAQ,QAAQ,EAAE,EAC7O;GAEA,OAAO,OAAO,qBAAqB,QAAQ,OAAO;EACpD,CAAC,CACH;EACF,OAAO,EAAE,aACP,SACG,KACC,OAAO,IAAI,aAAa;GACtB,MAAM,WAAW,OAAO,YAAY,UAAU,MAAM;GAEpD,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;GAEnC,MAAM,SAAS,OAAO,OAAO,SAAS,MAAM,GAC1C,UAAU,SAAS;GAErB,IACE,CAAC,oBAAoB,QAAQ,UAAU,OAAO,QAAQ,KACtD,EAAE,OAAO,iBACP,QACA,QAAQ,OAAO,MACf,SAAS,WAAW,QAAQ,GAAG,CACjC,IAEA,OAAO,KAAA;GAIT,OAAO;IAAE;IAAQ,aAAa,QAAQ;GAAY;EACpD,CAAC,CACH,CAAC,CACA,KACC,OAAO,eAAe,qBAAqB,MAAM,WAAW,GAC5D,OAAO,SAAS,UACd,UAAU,KAAA,IACN,OAAO,KAAK,qBAAqB,KAAK,CAAC,CAAC,CAAC,IACzC,OAAO,QAAQ,KAAK,CAC1B,CACF;EACJ,SAAS,EAAE,UAAU,YACnB,OACE,OAAO,IAAI,aAAa;GACtB,OAAO,QAAQ,OAAO,UAAU,MAAM;GAEtC,OAAO,OAAO,qBAAqB,EAAE,MAAM,WAAW,GAAY,OAAO;EAC3E,CAAC,GACD,WACF;EACF,WAAW,MAAM,YACf,OACE,OAAO,IAAI,aAAa;GACtB,OAAO,mBAAmB,IAAI;GAC9B,MAAM,EAAE,QAAQ,UAAU,gBAAgB;GAC1C,MAAM,UAAU,OAAO,MAAM,KAAK,OAAO,SAAS,WAAW,IAAI;GAEjE,IACE,YAAY,KAAA,KACZ,CAAC,oBAAoB,QAAQ,UAAU,SAAS,QAAQ,KACxD,CAAC,oBAAoB,QAAQ,UAAU,OAAO,QAAQ,GAEtD,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;GAC3C,MAAM,WAAW,OAAO,OAAO,QAAQ,QAAQ,GAAG;GAElD,IAAI,aAAa,KAAA,GAAW,OAAO,OAAO,qBAAqB,KAAK,CAAC,CAAC;GACtE,IAAI,KAAK,OAAO,MAAM,SAAS,YAE3B;QAAA,SAAS,WAAW,KAAA,KACpB,SAAS,OAAO,WAAW,KAAK,OAAO,MAAM,UAC7C,CAAC,kBAAkB,SAAS,QAAQ;KAClC,GAAG,KAAK,OAAO,WAAW;KAC1B,YAAY,KAAK,OAAO,WAAW;KACnC,mBAAmB,KAAK,OAAO,WAAW;KAC1C,QAAQ,KAAK,OAAO,MAAM;IAC5B,CAAC,GAED,OAAO,OAAO,qBAAqB,KAAK,CAAC,CAAC;GAAA;GAG9C,MAAM,WAAW,OAAO,wBACpB,UACA,KAAK,iBACL,QAAQ,GACV,GACA,OAAO,OAAO,wBAAwB,UAAU,QAAQ,aAAa,QAAQ,GAAG,GAChF,UAAU,OAAO,wBAAwB,UAAU,OAAO,aAAa,QAAQ,GAAG;GAEpF,MAAM,WAAW,KAAK,IACpB,SAAS,YACT,KAAK,YACL,QAAQ,YACR,SAAS,cAAc,OAAO,SAAS,GACvC,SAAS,cAAc,YAAY,WAAW,QAAQ,SAAS,CACjE;GAEA,IACE,QAAQ,OAAO,YACf,SAAS,cAAc,YAAY,WAAW,QAAQ,QAAQ,IAAI,QAAQ,MACxE,MAAM,YAAY,yBACpB,SAAS,cAAc,YAAY,WAAW,QAAQ,SAAS,IAC7D,SAAS,cAAc,OAAO,uBAAuB,GAEvD,OAAO,OAAO,qBAAqB,KAAK,CAAC,CAAC;GAC5C,MAAM,WAAW,OAAO,mBAAmB,MAAM;GAEjD,OAAO,MAAM,YAAY,OAAO;GAChC,MAAM,YAAY,GAAG,GAAG,MAAM,mBAAmB,SAAS,UAAU,QAAQ,MAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,IAAI,OAAO,IAAI,KAAK;GAEjI,OAAO,QAAQ,QACb,UACA,QACA,QAAQ,QACR,GAAG,GAAG,kBAAkB,QAAQ,QAAQ,EAAE,OAAO,UAAU,OAAO,SAAS,WAC7E;GACA,IAAI,uBAAiC,GAAG;GACxC,IAAI;GAEJ,IAAI,YAAY,SAAS,cAAc,QAAQ,OAAO,SAAS,YAAY;IACzE,iBAAiB,YAAY,KAAA,KAAa,SAAS,WAAW,KAAA,CAAS;IAEvE,MAAM,OAAsC;KAC1C,GAAG,YAAY,WAAW;KAC1B,QAAQ,YAAY;KACpB,mBAAmB,YAAY,WAAW;KAC1C,YAAY,YAAY,WAAW;IACrC;IAEA,IACE,KAAK,WAAW,SAAS,OAAO,UAChC,KAAK,sBAAsB,SAAS,OAAO,mBAE3C,OAAO,OAAO,qBAAqB,KAAK,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,UAAU;IAErB,MAAM,SAAS;KACb,GAAG,EAAE,eAAe,IAAI;MACvB,EAAE,SAAS,KAAK;MAChB,EAAE,WAAW,EAAE,cAAc,KAAK,QAAQ;MAC1C,EAAE,YAAY,EAAE,cAAc,KAAK,SAAS;MAC5C,EAAE,oBAAoB,EAAE,cAAc,KAAK,iBAAiB;MAC5D,EAAE,mBAAmB,KAAK;IAC7B;IAEA,OAAO,QAAQ,MACb,GAAG,GAAG,MAAM,OAAO,MAAM,EAAE,SAAS,OAAO,QAAQ,MAAM,SAAS,MAAM,EAAE,OAAO,QAAQ,KAAK,KAAK,EAAE,OAAO,aAC5G,CACF;IACA,uBAAuB,GAAG,wBAAwB,MAAM,KAAK,SAAS,OAAO,QAAQ,MAAM,IAAI,EAAE,OAAO,QAAQ,iBAAiB,MAAM,EAAE,OAAO,QAAQ,KAAK,KAAK,EAAE;IACpK,oBAAoB;GACtB,OAAO,IACL,YAAY,SAAS,yBACrB,QAAQ,OAAO,SAAS,uBACxB;IACA,MAAM,IAAI,QAAQ,OAAO,WACvB,QAAQ,OAAO,EAAE,KAAK,GACtB,UAAU,OAAO,QAAQ,OAAO,UAAU,SAAS,OAAO,eAAe;IAE3E,MAAM,SAAS;KACb,GAAG,EAAE,aAAa;MAChB,UAAU,QAAQ;MAClB,WAAW,QAAQ;MACnB,WAAW;MACX,mBAAmB,OAAO;KAC5B,CAAC;MACA,EAAE,WAAW,QAAQ;MACrB,EAAE,YAAY,QAAQ;MACtB,EAAE,YAAY;MACd,EAAE,oBAAoB,EAAE,cAAc,OAAO,uBAAuB;IACvE;IAEA,OAAO,QAAQ,MACb,kBAAkB,OAAO,QAAQ,GAAG,GAAG,UAAU,OAAO,SAAS,WAAW,GAC5E,CACF;IACA,uBAAuB,GAAG,wBAAwB,MAAM,KAAK,SAAS,MAAM,MAAM,OAAO,EAAE,UAAU,QAAQ,QAAQ,EAAE,OAAO,MAAM,GAAG,OAAO,EAAE,WAAW,QAAQ,MAAM,EAAE,OAAO,MAAM,GAAG,OAAO,EAAE,WAAW,OAAO,EAAE,OAAO,OAAO,WAAW,QAAQ,MAAM,SAAS,MAAM,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,MAAM,SAAS,cAAc,OAAO,uBAAuB,EAAE;GAC3W,OAAO,IACL,YAAY,SAAS,qBACrB,QAAQ,OAAO,SAAS,mBAExB,OAAO,OAAO,qBAAqB,KAAK,CAAC,CAAC;GAC5C,OAAO,MACL,8BACA,GAAG,GAAG,UAAU,OAAO,QAAQ,kBAAkB,UAAU,QAAQ,QAAQ,MAAM,EAAE,OAAO,sBAC5F;GACA,IAAI,UAAU,KAAA,KAAa,OAAO,OAAO,QAAQ,GAC/C,OAAO,yBAAyB;IAC9B,MAAM;IACN,OAAO,OAAO,IAAI,aAAa;KAC7B,MAAM,SAAS,OAAO,MAAM,KAAK,OAAO,SAAS,SAAS;KAE1D,iBACE,WAAW,KAAA,KAAa,oBAAoB,OAAO,UAAU,SAAS,QAAQ,CAChF;KACA,OAAO,wBAAwB,UAAU,KAAK,iBAAiB,OAAO,GAAG;KACzE,OAAO,wBAAwB,UAAU,OAAO,aAAa,OAAO,GAAG;KACvE,OAAO,wBAAwB,UAAU,OAAO,aAAa,OAAO,GAAG;KACvE,IAAI,sBAAsB,KAAA,KAAa,YAAY,KAAA,GAAW;MAC5D,MAAM,MAAM,OAAO,QAAQ,KAAK,kBAAkB,MAAM;MAExD,iBAAiB,QAAQ,KAAA,KAAa,kBAAkB,KAAK,iBAAiB,CAAC;KACjF;IACF,CAAC,CAAC,CAAC,KACD,OAAO,eAAe,qBAAqB,MAAM,WAAW,GAC5D,OAAO,UAAU,UACf,wBAAwB,KAAK;KAAE,WAAW;KAAU;IAAM,CAAC,CAC7D,CACF;GACF,CAAC;GAEH,OAAO,OAAO,qBAAqB,YAAY,YAAY,OAAO;EACpE,CAAC,CACH;CAG4B,EAAE;AACpC,CAAC"}
|
|
@@ -8,9 +8,11 @@ import { allocateSessionValue } from "./session-policy.mjs";
|
|
|
8
8
|
import { DateTime, Effect, Option } from "effect";
|
|
9
9
|
import { SessionInvalid, StaleAuthentication } from "@yielded/auth/Sessions";
|
|
10
10
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
11
|
+
import { AuthenticationClock } from "@yielded/auth/Operations";
|
|
11
12
|
//#region src/internal/session-native-validity.ts
|
|
12
13
|
const makeNativeSignedSessionValidityServices = Effect.fnUntraced(function* (tables, mapping) {
|
|
13
14
|
const batch = yield* SqlBatchCommit;
|
|
15
|
+
const { futureToleranceMillis } = yield* AuthenticationClock;
|
|
14
16
|
const sql = (yield* SqlClient).withoutTransforms(), executor = yield* makeSqlCommitExecutor(sessionUnavailable);
|
|
15
17
|
const external = yield* Effect.serviceOption(CurrentSqlCommit);
|
|
16
18
|
const s = mapping.subject, t = mapping.tombstone, subject = tables(s.table), tombstone = tables(t.table), now = tables.expression(mapping.clock.engineNowMillis);
|
|
@@ -48,7 +50,7 @@ const makeNativeSignedSessionValidityServices = Effect.fnUntraced(function* (tab
|
|
|
48
50
|
return { signedSessionValidity: {
|
|
49
51
|
verify: (record) => executor.verify(Effect.gen(function* () {
|
|
50
52
|
const nativeSubject = yield* mapping.subjectId.toNative(record.subjectId), nativeSession = yield* mapping.sessionId.toNative(record.sessionId);
|
|
51
|
-
const rows = yield* sql`select ${subject.fields("subject_")} from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, record.securityRevision)} and ${now}
|
|
53
|
+
const rows = yield* sql`select ${subject.fields("subject_")} from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, record.securityRevision)} and ${DateTime.toEpochMillis(record.issuedAt)} <= ${now} + ${futureToleranceMillis} and ${DateTime.toEpochMillis(record.issuedAt)} < ${DateTime.toEpochMillis(record.expiresAt)} and ${now} < ${DateTime.toEpochMillis(record.expiresAt)} and ${DateTime.toEpochMillis(record.expiresAt)} <= ${DateTime.toEpochMillis(record.absoluteExpiresAt)} and not exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)}) limit 2`;
|
|
52
54
|
sessionInvariant(rows.length <= 1);
|
|
53
55
|
if (rows[0] === void 0) return false;
|
|
54
56
|
const decoded = subject.decode(rows[0], "subject_");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-validity.mjs","names":[],"sources":["../../src/internal/session-native-validity.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n SessionInvalid,\n StaleAuthentication,\n type SignedSessionValidity,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { SignedSessionValidityMapping } from \"./models/session-model\";\nimport type { NativeSqlTables, SqlTable } from \"./native-sql-table\";\nimport { prepareNativeSession } from \"./session-native-pending\";\nimport {\n normalizeSessionOperation,\n sessionInvariant,\n sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue } 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 NativeSignedSessionValidityMapping = SignedSessionValidityMapping<\n AnyTableModel,\n AnyTableModel,\n unknown,\n unknown\n>;\n\nexport const makeNativeSignedSessionValidityServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: NativeSignedSessionValidityMapping,\n): Effect.fn.Return<\n { readonly signedSessionValidity: SignedSessionValidity },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const sql = (yield* SqlClient).withoutTransforms(),\n executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const s = mapping.subject,\n t = mapping.tombstone,\n subject = tables(s.table),\n tombstone = tables(t.table),\n now = tables.expression(mapping.clock.engineNowMillis);\n\n const id = (table: SqlTable, key: string, value: unknown) =>\n sql`${table.column(key)} = ${table.value(key, value)}`;\n\n const exact = (table: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, table.column(key), table.value(key, value));\n\n const key = (nativeSubject: unknown, nativeSession: unknown) =>\n sql.and([\n exact(tombstone, t.moduleId, mapping.moduleId),\n id(tombstone, t.subjectId, nativeSubject),\n id(tombstone, t.sessionId, nativeSession),\n ]);\n\n const native = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), \"statement\")\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 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 signedSessionValidity: SignedSessionValidity = {\n verify: (record) =>\n executor\n .verify(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(record.subjectId),\n nativeSession = yield* mapping.sessionId.toNative(record.sessionId);\n\n const rows =\n yield* sql`select ${subject.fields(\"subject_\")} from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, record.securityRevision)} and ${now} >= ${DateTime.toEpochMillis(record.issuedAt)} and ${now} < ${DateTime.toEpochMillis(record.expiresAt)} and ${DateTime.toEpochMillis(record.expiresAt)} <= ${DateTime.toEpochMillis(record.absoluteExpiresAt)} and not exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)}) limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return false;\n const decoded = subject.decode(rows[0], \"subject_\");\n\n sessionInvariant(\n (yield* mapping.subjectId.toSubject(decoded[s.id])) === record.subjectId &&\n s.isActiveStatus(decoded[s.status]),\n );\n\n return true;\n }),\n )\n .pipe(\n Effect.flatMap((valid) => (valid ? Effect.void : Effect.fail(SessionInvalid.make({})))),\n ),\n revoke: (input, project) =>\n native(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n nativeSession = yield* mapping.sessionId.toNative(input.sessionId);\n\n const row = {\n ...t.encodeInsert({\n moduleId: mapping.moduleId,\n subjectId: nativeSubject,\n sessionId: nativeSession,\n absoluteExpiresAt: input.absoluteExpiresAt,\n }),\n [t.moduleId]: mapping.moduleId,\n [t.subjectId]: nativeSubject,\n [t.sessionId]: nativeSession,\n [t.absoluteExpiresAt]: t.encodeInstant(input.absoluteExpiresAt),\n };\n\n const expiry = tables.expression(\n mapping.clock.toMillis(tombstone.column(t.absoluteExpiresAt)),\n );\n\n const next = DateTime.toEpochMillis(input.absoluteExpiresAt);\n const longest = sql`case when ${expiry} >= ${next} then ${tombstone.column(t.absoluteExpiresAt)} else ${tombstone.value(t.absoluteExpiresAt, row[t.absoluteExpiresAt])} end`;\n const insert = tombstone.insert(row);\n\n const statement = sql.onDialectOrElse({\n mysql: () =>\n sql`${insert} on duplicate key update ${tombstone.columnName(t.absoluteExpiresAt)} = ${longest}`,\n orElse: () =>\n sql`${insert} on conflict (${tombstone.columnName(t.moduleId)}, ${tombstone.columnName(t.subjectId)}, ${tombstone.columnName(t.sessionId)}) do update set ${tombstone.columnName(t.absoluteExpiresAt)} = ${longest}`,\n });\n\n yield* stage(statement);\n yield* final(\n \"assisted-session-revocation\",\n sql`exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)} and ${expiry} >= ${next})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n ),\n revokeAll: (input, project) =>\n native(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId);\n\n const next = yield* allocateSessionValue(\n \"interactive\",\n s.nextSecurityRevision?.(input.expectedSecurityRevision),\n s.nextSecurityRevisionSync === undefined\n ? undefined\n : () => s.nextSecurityRevisionSync!(input.expectedSecurityRevision),\n );\n\n sessionInvariant(next !== input.expectedSecurityRevision);\n\n const changed = yield* stage(\n sql`${subject.update({ [s.securityRevision]: next })} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, input.expectedSecurityRevision)}`,\n 1,\n );\n\n if (changed !== 1) return yield* StaleAuthentication.make({});\n yield* final(\n \"all-assisted-session-revocation\",\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${exact(subject, s.securityRevision, next)})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n ),\n };\n\n return { signedSessionValidity };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-native-validity.mjs","names":[],"sources":["../../src/internal/session-native-validity.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport {\n SessionInvalid,\n StaleAuthentication,\n type SignedSessionValidity,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { SignedSessionValidityMapping } from \"./models/session-model\";\nimport type { NativeSqlTables, SqlTable } from \"./native-sql-table\";\nimport { prepareNativeSession } from \"./session-native-pending\";\nimport {\n normalizeSessionOperation,\n sessionInvariant,\n sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue } 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 NativeSignedSessionValidityMapping = SignedSessionValidityMapping<\n AnyTableModel,\n AnyTableModel,\n unknown,\n unknown\n>;\n\nexport const makeNativeSignedSessionValidityServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: NativeSignedSessionValidityMapping,\n): Effect.fn.Return<\n { readonly signedSessionValidity: SignedSessionValidity },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n const { futureToleranceMillis } = yield* AuthenticationClock;\n\n const sql = (yield* SqlClient).withoutTransforms(),\n executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const s = mapping.subject,\n t = mapping.tombstone,\n subject = tables(s.table),\n tombstone = tables(t.table),\n now = tables.expression(mapping.clock.engineNowMillis);\n\n const id = (table: SqlTable, key: string, value: unknown) =>\n sql`${table.column(key)} = ${table.value(key, value)}`;\n\n const exact = (table: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, table.column(key), table.value(key, value));\n\n const key = (nativeSubject: unknown, nativeSession: unknown) =>\n sql.and([\n exact(tombstone, t.moduleId, mapping.moduleId),\n id(tombstone, t.subjectId, nativeSubject),\n id(tombstone, t.sessionId, nativeSession),\n ]);\n\n const native = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), \"statement\")\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 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 signedSessionValidity: SignedSessionValidity = {\n verify: (record) =>\n executor\n .verify(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(record.subjectId),\n nativeSession = yield* mapping.sessionId.toNative(record.sessionId);\n\n const rows =\n yield* sql`select ${subject.fields(\"subject_\")} from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, record.securityRevision)} and ${DateTime.toEpochMillis(record.issuedAt)} <= ${now} + ${futureToleranceMillis} and ${DateTime.toEpochMillis(record.issuedAt)} < ${DateTime.toEpochMillis(record.expiresAt)} and ${now} < ${DateTime.toEpochMillis(record.expiresAt)} and ${DateTime.toEpochMillis(record.expiresAt)} <= ${DateTime.toEpochMillis(record.absoluteExpiresAt)} and not exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)}) limit 2`;\n\n sessionInvariant(rows.length <= 1);\n if (rows[0] === undefined) return false;\n const decoded = subject.decode(rows[0], \"subject_\");\n\n sessionInvariant(\n (yield* mapping.subjectId.toSubject(decoded[s.id])) === record.subjectId &&\n s.isActiveStatus(decoded[s.status]),\n );\n\n return true;\n }),\n )\n .pipe(\n Effect.flatMap((valid) => (valid ? Effect.void : Effect.fail(SessionInvalid.make({})))),\n ),\n revoke: (input, project) =>\n native(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n nativeSession = yield* mapping.sessionId.toNative(input.sessionId);\n\n const row = {\n ...t.encodeInsert({\n moduleId: mapping.moduleId,\n subjectId: nativeSubject,\n sessionId: nativeSession,\n absoluteExpiresAt: input.absoluteExpiresAt,\n }),\n [t.moduleId]: mapping.moduleId,\n [t.subjectId]: nativeSubject,\n [t.sessionId]: nativeSession,\n [t.absoluteExpiresAt]: t.encodeInstant(input.absoluteExpiresAt),\n };\n\n const expiry = tables.expression(\n mapping.clock.toMillis(tombstone.column(t.absoluteExpiresAt)),\n );\n\n const next = DateTime.toEpochMillis(input.absoluteExpiresAt);\n const longest = sql`case when ${expiry} >= ${next} then ${tombstone.column(t.absoluteExpiresAt)} else ${tombstone.value(t.absoluteExpiresAt, row[t.absoluteExpiresAt])} end`;\n const insert = tombstone.insert(row);\n\n const statement = sql.onDialectOrElse({\n mysql: () =>\n sql`${insert} on duplicate key update ${tombstone.columnName(t.absoluteExpiresAt)} = ${longest}`,\n orElse: () =>\n sql`${insert} on conflict (${tombstone.columnName(t.moduleId)}, ${tombstone.columnName(t.subjectId)}, ${tombstone.columnName(t.sessionId)}) do update set ${tombstone.columnName(t.absoluteExpiresAt)} = ${longest}`,\n });\n\n yield* stage(statement);\n yield* final(\n \"assisted-session-revocation\",\n sql`exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)} and ${expiry} >= ${next})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n ),\n revokeAll: (input, project) =>\n native(\n Effect.gen(function* () {\n const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId);\n\n const next = yield* allocateSessionValue(\n \"interactive\",\n s.nextSecurityRevision?.(input.expectedSecurityRevision),\n s.nextSecurityRevisionSync === undefined\n ? undefined\n : () => s.nextSecurityRevisionSync!(input.expectedSecurityRevision),\n );\n\n sessionInvariant(next !== input.expectedSecurityRevision);\n\n const changed = yield* stage(\n sql`${subject.update({ [s.securityRevision]: next })} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, input.expectedSecurityRevision)}`,\n 1,\n );\n\n if (changed !== 1) return yield* StaleAuthentication.make({});\n yield* final(\n \"all-assisted-session-revocation\",\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${exact(subject, s.securityRevision, next)})`,\n );\n\n return yield* prepareNativeSession(undefined, project);\n }),\n ),\n };\n\n return { signedSessionValidity };\n});\n"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ import { makeNativeAuthenticationAuthorityServices } from "./session-native-auth
|
|
|
6
6
|
import { validateSqlStorage } from "./sql-storage-validation.mjs";
|
|
7
7
|
import { Effect } from "effect";
|
|
8
8
|
import { SqlClient } from "effect/sql";
|
|
9
|
+
import { reportAuthDiagnostic } from "@yielded/auth/Persistence";
|
|
9
10
|
import { SessionUnavailable } from "@yielded/auth/Sessions";
|
|
10
11
|
//#region src/internal/sql-authority.ts
|
|
11
12
|
/** Direct SQL uses the same authority owner as the mapped Drizzle adapters. */
|
|
@@ -15,7 +16,10 @@ const makeAuthenticationAuthorityServices = Effect.fnUntraced(function* (mapping
|
|
|
15
16
|
pg: () => true,
|
|
16
17
|
sqlite: () => true,
|
|
17
18
|
orElse: () => false
|
|
18
|
-
}))
|
|
19
|
+
})) {
|
|
20
|
+
yield* reportAuthDiagnostic("persistence-validation", "configuration");
|
|
21
|
+
return yield* PersistenceConfigurationError.make({ reason: "Authentication authority requires PostgreSQL or SQLite" });
|
|
22
|
+
}
|
|
19
23
|
yield* validateSqlStorage(mapping).pipe(Effect.mapError(() => SessionUnavailable.make({})));
|
|
20
24
|
return yield* makeNativeAuthenticationAuthorityServices(makeNativeSqlTables(sql), {
|
|
21
25
|
...mapping,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-authority.mjs","names":[],"sources":["../../src/internal/sql-authority.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { SessionUnavailable, type AuthenticationAuthority } from \"@yielded/auth/Sessions\";\nimport { Effect } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport { PersistenceConfigurationError } from \"./configuration\";\nimport type { AuthenticationAuthorityMapping } from \"./models/session-model\";\nimport { makeNativeSqlTables } from \"./native-sql-table\";\nimport {\n makeNativeAuthenticationAuthorityServices,\n type NativeAuthenticationAuthorityMapping,\n} from \"./session-native-authority\";\nimport { SqlBatchCommit } from \"./sql-commit\";\nimport { SqlExpression, compileSqlExpression } from \"./sql-expression\";\nimport type { SqlTableModel } from \"./sql-oauth-model\";\nimport { validateSqlStorage } from \"./sql-storage-validation\";\n\n/** Direct SQL uses the same authority owner as the mapped Drizzle adapters. */\nexport const makeAuthenticationAuthorityServices = Effect.fnUntraced(function* <Claims, N>(\n mapping: AuthenticationAuthorityMapping<\n Claims,\n SqlTableModel,\n SqlTableModel,\n SqlTableModel,\n N,\n SqlExpression\n >,\n): Effect.fn.Return<\n { readonly authenticationAuthority: AuthenticationAuthority[\"Service\"] },\n SessionUnavailable | PersistenceConfigurationError,\n LifecycleHooks | SqlClient.SqlClient\n> {\n const sql = yield* SqlClient.SqlClient;\n\n if (!sql.onDialectOrElse({ pg: () => true, sqlite: () => true, orElse: () => false }))\n return yield* PersistenceConfigurationError.make({\n reason: \"Authentication authority requires PostgreSQL or SQLite\",\n });\n yield* validateSqlStorage(mapping).pipe(Effect.mapError(() => SessionUnavailable.make({})));\n\n // Erase mapped native ID callback variance, never decoded authentication data.\n return yield* makeNativeAuthenticationAuthorityServices(makeNativeSqlTables(sql), {\n ...mapping,\n clock: {\n ...mapping.clock,\n toMillis: (value: unknown) =>\n mapping.clock.toMillis(new SqlExpression((client) => compileSqlExpression(client, value))),\n fromMillis: (value: unknown) =>\n mapping.clock.fromMillis(\n new SqlExpression((client) => compileSqlExpression(client, value)),\n ),\n },\n } as unknown as NativeAuthenticationAuthorityMapping<Claims>).pipe(\n Effect.provideService(SqlBatchCommit, undefined),\n );\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"sql-authority.mjs","names":[],"sources":["../../src/internal/sql-authority.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { reportAuthDiagnostic } from \"@yielded/auth/Persistence\";\nimport { SessionUnavailable, type AuthenticationAuthority } from \"@yielded/auth/Sessions\";\nimport { Effect } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport { PersistenceConfigurationError } from \"./configuration\";\nimport type { AuthenticationAuthorityMapping } from \"./models/session-model\";\nimport { makeNativeSqlTables } from \"./native-sql-table\";\nimport {\n makeNativeAuthenticationAuthorityServices,\n type NativeAuthenticationAuthorityMapping,\n} from \"./session-native-authority\";\nimport { SqlBatchCommit } from \"./sql-commit\";\nimport { SqlExpression, compileSqlExpression } from \"./sql-expression\";\nimport type { SqlTableModel } from \"./sql-oauth-model\";\nimport { validateSqlStorage } from \"./sql-storage-validation\";\n\n/** Direct SQL uses the same authority owner as the mapped Drizzle adapters. */\nexport const makeAuthenticationAuthorityServices = Effect.fnUntraced(function* <Claims, N>(\n mapping: AuthenticationAuthorityMapping<\n Claims,\n SqlTableModel,\n SqlTableModel,\n SqlTableModel,\n N,\n SqlExpression\n >,\n): Effect.fn.Return<\n { readonly authenticationAuthority: AuthenticationAuthority[\"Service\"] },\n SessionUnavailable | PersistenceConfigurationError,\n LifecycleHooks | SqlClient.SqlClient\n> {\n const sql = yield* SqlClient.SqlClient;\n\n if (!sql.onDialectOrElse({ pg: () => true, sqlite: () => true, orElse: () => false })) {\n yield* reportAuthDiagnostic(\"persistence-validation\", \"configuration\");\n\n return yield* PersistenceConfigurationError.make({\n reason: \"Authentication authority requires PostgreSQL or SQLite\",\n });\n }\n yield* validateSqlStorage(mapping).pipe(Effect.mapError(() => SessionUnavailable.make({})));\n\n // Erase mapped native ID callback variance, never decoded authentication data.\n return yield* makeNativeAuthenticationAuthorityServices(makeNativeSqlTables(sql), {\n ...mapping,\n clock: {\n ...mapping.clock,\n toMillis: (value: unknown) =>\n mapping.clock.toMillis(new SqlExpression((client) => compileSqlExpression(client, value))),\n fromMillis: (value: unknown) =>\n mapping.clock.fromMillis(\n new SqlExpression((client) => compileSqlExpression(client, value)),\n ),\n },\n } as unknown as NativeAuthenticationAuthorityMapping<Claims>).pipe(\n Effect.provideService(SqlBatchCommit, undefined),\n );\n});\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAa,sCAAsC,OAAO,WAAW,WACnE,SAYA;CACA,MAAM,MAAM,OAAO,UAAU;CAE7B,IAAI,CAAC,IAAI,gBAAgB;EAAE,UAAU;EAAM,cAAc;EAAM,cAAc;CAAM,CAAC,GAAG;EACrF,OAAO,qBAAqB,0BAA0B,eAAe;EAErE,OAAO,OAAO,8BAA8B,KAAK,EAC/C,QAAQ,yDACV,CAAC;CACH;CACA,OAAO,mBAAmB,OAAO,CAAC,CAAC,KAAK,OAAO,eAAe,mBAAmB,KAAK,CAAC,CAAC,CAAC,CAAC;CAG1F,OAAO,OAAO,0CAA0C,oBAAoB,GAAG,GAAG;EAChF,GAAG;EACH,OAAO;GACL,GAAG,QAAQ;GACX,WAAW,UACT,QAAQ,MAAM,SAAS,IAAI,eAAe,WAAW,qBAAqB,QAAQ,KAAK,CAAC,CAAC;GAC3F,aAAa,UACX,QAAQ,MAAM,WACZ,IAAI,eAAe,WAAW,qBAAqB,QAAQ,KAAK,CAAC,CACnE;EACJ;CACF,CAA4D,CAAC,CAAC,KAC5D,OAAO,eAAe,gBAAgB,KAAA,CAAS,CACjD;AACF,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { validateStorageBatch } from "./storage-validation.mjs";
|
|
|
4
4
|
import { storageKeyPlans } from "./storage-plans.mjs";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
import { SqlClient } from "effect/sql";
|
|
7
|
+
import { reportAuthDiagnostic } from "@yielded/auth/Persistence";
|
|
7
8
|
//#region src/internal/sql-storage-validation.ts
|
|
8
9
|
/** Check declared mappings against physical keys before acquiring OAuth services. */
|
|
9
10
|
const validateSqlStorage = Effect.fnUntraced(function* (mapping) {
|
|
@@ -46,7 +47,7 @@ const validateSqlStorage = Effect.fnUntraced(function* (mapping) {
|
|
|
46
47
|
operation: "mapping",
|
|
47
48
|
cause: "Required SQL storage key is unavailable"
|
|
48
49
|
})));
|
|
49
|
-
});
|
|
50
|
+
}, Effect.tapError(() => reportAuthDiagnostic("persistence-validation", "mapping")));
|
|
50
51
|
//#endregion
|
|
51
52
|
export { validateSqlStorage };
|
|
52
53
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-storage-validation.mjs","names":[],"sources":["../../src/internal/sql-storage-validation.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport { Table } from \"./sql-table\";\nimport { storageKeyPlans } from \"./storage-plans\";\nimport { validateStorageBatch, type StorageValidation } from \"./storage-validation\";\n\n/** Check declared mappings against physical keys before acquiring OAuth services. */\nexport const validateSqlStorage = Effect.fnUntraced(function* (mapping: unknown) {\n
|
|
1
|
+
{"version":3,"file":"sql-storage-validation.mjs","names":[],"sources":["../../src/internal/sql-storage-validation.ts"],"sourcesContent":["import { reportAuthDiagnostic } from \"@yielded/auth/Persistence\";\nimport { Effect } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport { Table } from \"./sql-table\";\nimport { storageKeyPlans } from \"./storage-plans\";\nimport { validateStorageBatch, type StorageValidation } from \"./storage-validation\";\n\n/** Check declared mappings against physical keys before acquiring OAuth services. */\nexport const validateSqlStorage = Effect.fnUntraced(\n function* (mapping: unknown) {\n const plans = yield* Effect.try({\n try: () =>\n storageKeyPlans(mapping, (table) => {\n if (!(table instanceof Table)) throw new Error(\"Expected a SQL table\");\n\n return table.columns;\n }),\n catch: () =>\n PersistenceMappingError.make({\n operation: \"mapping\",\n cause: \"Invalid storage key mapping\",\n }),\n });\n\n const client = yield* SqlClient.SqlClient;\n\n const dialect = client.onDialectOrElse({\n pg: () => \"pg\" as const,\n sqlite: () => \"sqlite\" as const,\n orElse: () => undefined,\n });\n\n if (dialect === undefined)\n return yield* PersistenceMappingError.make({\n operation: \"mapping\",\n cause: \"OAuth persistence requires PostgreSQL or SQLite\",\n });\n const validations: StorageValidation[] = [];\n\n for (const plan of plans) {\n if (!(plan.table instanceof Table))\n return yield* PersistenceMappingError.make({\n operation: \"mapping\",\n cause: \"Expected a SQL table\",\n });\n validations.push({\n table: {\n name: plan.table.name,\n ...(plan.table.schema === undefined ? {} : { schema: plan.table.schema }),\n columns: Object.fromEntries(\n Object.entries(plan.table.columns).map(([key, column]) => [key, column.options]),\n ),\n },\n required: [plan.keys],\n });\n }\n yield* validateStorageBatch(dialect, validations).pipe(\n Effect.provideService(SqlClient.SqlClient, client),\n Effect.mapError(() =>\n PersistenceMappingError.make({\n operation: \"mapping\",\n cause: \"Required SQL storage key is unavailable\",\n }),\n ),\n );\n },\n Effect.tapError(() => reportAuthDiagnostic(\"persistence-validation\", \"mapping\")),\n);\n"],"mappings":";;;;;;;;;AAUA,MAAa,qBAAqB,OAAO,WACvC,WAAW,SAAkB;CAC3B,MAAM,QAAQ,OAAO,OAAO,IAAI;EAC9B,WACE,gBAAgB,UAAU,UAAU;GAClC,IAAI,EAAE,iBAAiB,QAAQ,MAAM,IAAI,MAAM,sBAAsB;GAErE,OAAO,MAAM;EACf,CAAC;EACH,aACE,wBAAwB,KAAK;GAC3B,WAAW;GACX,OAAO;EACT,CAAC;CACL,CAAC;CAED,MAAM,SAAS,OAAO,UAAU;CAEhC,MAAM,UAAU,OAAO,gBAAgB;EACrC,UAAU;EACV,cAAc;EACd,cAAc,KAAA;CAChB,CAAC;CAED,IAAI,YAAY,KAAA,GACd,OAAO,OAAO,wBAAwB,KAAK;EACzC,WAAW;EACX,OAAO;CACT,CAAC;CACH,MAAM,cAAmC,CAAC;CAE1C,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,EAAE,KAAK,iBAAiB,QAC1B,OAAO,OAAO,wBAAwB,KAAK;GACzC,WAAW;GACX,OAAO;EACT,CAAC;EACH,YAAY,KAAK;GACf,OAAO;IACL,MAAM,KAAK,MAAM;IACjB,GAAI,KAAK,MAAM,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,OAAO;IACvE,SAAS,OAAO,YACd,OAAO,QAAQ,KAAK,MAAM,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,KAAK,OAAO,OAAO,CAAC,CACjF;GACF;GACA,UAAU,CAAC,KAAK,IAAI;EACtB,CAAC;CACH;CACA,OAAO,qBAAqB,SAAS,WAAW,CAAC,CAAC,KAChD,OAAO,eAAe,UAAU,WAAW,MAAM,GACjD,OAAO,eACL,wBAAwB,KAAK;EAC3B,WAAW;EACX,OAAO;CACT,CAAC,CACH,CACF;AACF,GACA,OAAO,eAAe,qBAAqB,0BAA0B,SAAS,CAAC,CACjF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-table.mjs","names":[],"sources":["../../src/internal/sql-table.ts"],"sourcesContent":["import { Option, Schema, SchemaGetter } from \"effect\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\n\nconst SqlInteger = Schema.Union([\n Schema.Int,\n Schema.BigInt,\n Schema.String.check(Schema.isPattern(/^-?\\d+$/)),\n]).pipe(\n Schema.decodeTo(Schema.Int, {\n decode: SchemaGetter.transform(Number),\n encode: SchemaGetter.passthrough(),\n }),\n);\n\nconst decodeInteger = Schema.decodeUnknownOption(SqlInteger);\n\nexport type Dialect = \"pg\" | \"sqlite\";\nexport type Row = Readonly<Record<string, unknown>>;\n\nexport const identifier = (name: string) => '\"' + name.replaceAll('\"', '\"\"') + '\"';\n\nexport interface ColumnOptions {\n readonly name: string;\n readonly type: \"text\" | \"integer\" | \"boolean\";\n readonly nullable?: boolean;\n}\n\nexport class Column {\n constructor(\n readonly tableName: string,\n readonly options: ColumnOptions,\n readonly schema?: string,\n ) {}\n\n decode(value: unknown): unknown {\n if (value === null) return value;\n if (this.options.type === \"boolean\") {\n if (value === true || value === 1) return true;\n if (value === false || value === 0) return false;\n throw PersistenceMappingError.make({ operation: \"decode\", cause: \"Invalid SQL boolean\" });\n }\n if (this.options.type === \"integer\") {\n const number = decodeInteger(value);\n\n if (Option.isSome(number)) return number.value;\n throw PersistenceMappingError.make({\n operation: \"decode\",\n cause: \"SQL integer is outside the safe range\",\n });\n }\n\n return value;\n }\n}\n\n/** Plain SQL identifiers and column representations, independent of an ORM. */\nexport class Table {\n readonly columns: Readonly<Record<string, Column>>;\n\n constructor(\n readonly name: string,\n columns: Readonly<Record<string, ColumnOptions>>,\n readonly unique: ReadonlyArray<ReadonlyArray<string>>,\n readonly schema?: string,\n ) {\n this.columns = Object.fromEntries(\n Object.entries(columns).map(([key, options]) => [key, new Column(name, options, schema)]),\n );\n }\n}\n"],"mappings":";;;AAIA,MAAM,aAAa,OAAO,MAAM;CAC9B,OAAO;CACP,OAAO;CACP,OAAO,OAAO,MAAM,OAAO,UAAU,SAAS,CAAC;AACjD,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,OAAO,KAAK;CAC1B,QAAQ,aAAa,UAAU,MAAM;CACrC,QAAQ,aAAa,YAAY;AACnC,CAAC,CACH;AAEA,MAAM,gBAAgB,OAAO,oBAAoB,UAAU;AAK3D,MAAa,cAAc,SAAiB,OAAM,KAAK,WAAW,MAAK,MAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"sql-table.mjs","names":[],"sources":["../../src/internal/sql-table.ts"],"sourcesContent":["import { Option, Schema, SchemaGetter } from \"effect\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\n\nconst SqlInteger = Schema.Union([\n Schema.Int,\n Schema.BigInt,\n Schema.String.check(Schema.isPattern(/^-?\\d+$/)),\n]).pipe(\n Schema.decodeTo(Schema.Int, {\n decode: SchemaGetter.transform(Number),\n encode: SchemaGetter.passthrough(),\n }),\n);\n\nconst decodeInteger = Schema.decodeUnknownOption(SqlInteger);\n\nexport type Dialect = \"pg\" | \"sqlite\";\nexport type Row = Readonly<Record<string, unknown>>;\n\nexport const identifier = (name: string) => '\"' + name.replaceAll('\"', '\"\"') + '\"';\n\nexport interface ColumnOptions {\n readonly name: string;\n /** Driver value representation, not the database column's physical SQL type. */\n readonly type: \"text\" | \"integer\" | \"boolean\";\n readonly nullable?: boolean;\n}\n\nexport class Column {\n constructor(\n readonly tableName: string,\n readonly options: ColumnOptions,\n readonly schema?: string,\n ) {}\n\n decode(value: unknown): unknown {\n if (value === null) return value;\n if (this.options.type === \"boolean\") {\n if (value === true || value === 1) return true;\n if (value === false || value === 0) return false;\n throw PersistenceMappingError.make({ operation: \"decode\", cause: \"Invalid SQL boolean\" });\n }\n if (this.options.type === \"integer\") {\n const number = decodeInteger(value);\n\n if (Option.isSome(number)) return number.value;\n throw PersistenceMappingError.make({\n operation: \"decode\",\n cause: \"SQL integer is outside the safe range\",\n });\n }\n\n return value;\n }\n}\n\n/** Plain SQL identifiers and column representations, independent of an ORM. */\nexport class Table {\n readonly columns: Readonly<Record<string, Column>>;\n\n constructor(\n readonly name: string,\n columns: Readonly<Record<string, ColumnOptions>>,\n readonly unique: ReadonlyArray<ReadonlyArray<string>>,\n readonly schema?: string,\n ) {\n this.columns = Object.fromEntries(\n Object.entries(columns).map(([key, options]) => [key, new Column(name, options, schema)]),\n );\n }\n}\n"],"mappings":";;;AAIA,MAAM,aAAa,OAAO,MAAM;CAC9B,OAAO;CACP,OAAO;CACP,OAAO,OAAO,MAAM,OAAO,UAAU,SAAS,CAAC;AACjD,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,OAAO,KAAK;CAC1B,QAAQ,aAAa,UAAU,MAAM;CACrC,QAAQ,aAAa,YAAY;AACnC,CAAC,CACH;AAEA,MAAM,gBAAgB,OAAO,oBAAoB,UAAU;AAK3D,MAAa,cAAc,SAAiB,OAAM,KAAK,WAAW,MAAK,MAAI,IAAI;AAS/E,IAAa,SAAb,MAAoB;CAEP;CACA;CACA;CAHX,YACE,WACA,SACA,QACA;EAHS,KAAA,YAAA;EACA,KAAA,UAAA;EACA,KAAA,SAAA;CACR;CAEH,OAAO,OAAyB;EAC9B,IAAI,UAAU,MAAM,OAAO;EAC3B,IAAI,KAAK,QAAQ,SAAS,WAAW;GACnC,IAAI,UAAU,QAAQ,UAAU,GAAG,OAAO;GAC1C,IAAI,UAAU,SAAS,UAAU,GAAG,OAAO;GAC3C,MAAM,wBAAwB,KAAK;IAAE,WAAW;IAAU,OAAO;GAAsB,CAAC;EAC1F;EACA,IAAI,KAAK,QAAQ,SAAS,WAAW;GACnC,MAAM,SAAS,cAAc,KAAK;GAElC,IAAI,OAAO,OAAO,MAAM,GAAG,OAAO,OAAO;GACzC,MAAM,wBAAwB,KAAK;IACjC,WAAW;IACX,OAAO;GACT,CAAC;EACH;EAEA,OAAO;CACT;AACF;;AAGA,IAAa,QAAb,MAAmB;CAIN;CAEA;CACA;CANX;CAEA,YACE,MACA,SACA,QACA,QACA;EAJS,KAAA,OAAA;EAEA,KAAA,SAAA;EACA,KAAA,SAAA;EAET,KAAK,UAAU,OAAO,YACpB,OAAO,QAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,aAAa,CAAC,KAAK,IAAI,OAAO,MAAM,SAAS,MAAM,CAAC,CAAC,CAC1F;CACF;AACF"}
|
|
@@ -1,27 +1,40 @@
|
|
|
1
|
-
import { sameSessionRevision } from "./session-native-state.mjs";
|
|
1
|
+
import { assessSessionAt, sameSessionRevision } from "./session-native-state.mjs";
|
|
2
2
|
import { DateTime, Effect, Schema } from "effect";
|
|
3
|
-
import { AuthenticationEvidence, SessionMetadata, SessionStepUpIntent, SessionStepUpInvalid, SessionStepUpRequirement, SessionUnavailable } from "@yielded/auth/Sessions";
|
|
3
|
+
import { AuthenticationEvidence, AuthenticationProof, SessionMetadata, SessionStepUpIntent, SessionStepUpInvalid, SessionStepUpRequirement, SessionUnavailable } from "@yielded/auth/Sessions";
|
|
4
|
+
import { AuthenticationAssurance, AuthenticationClock } from "@yielded/auth/Operations";
|
|
4
5
|
//#region src/internal/step-up-state.ts
|
|
5
6
|
const stepUpIntentCodec = Schema.fromJsonString(SessionStepUpIntent);
|
|
6
7
|
const BoundedSnapshot = Schema.String.check(Schema.isMaxLength(131072));
|
|
7
8
|
const encodeStepUpIntent = (intent) => Schema.encodeEffect(stepUpIntentCodec)(intent).pipe(Effect.flatMap(Schema.decodeEffect(BoundedSnapshot)), Effect.mapError(() => SessionUnavailable.make({})));
|
|
8
9
|
const decodeStepUpIntent = (snapshot) => Schema.decodeEffect(BoundedSnapshot)(snapshot).pipe(Effect.flatMap(Schema.decodeEffect(stepUpIntentCodec)), Effect.mapError(() => SessionUnavailable.make({})));
|
|
9
|
-
const stepUpIntentLive = (intent, kind, now)
|
|
10
|
+
const stepUpIntentLive = Effect.fnUntraced(function* (intent, kind, now) {
|
|
11
|
+
const { futureToleranceMillis } = yield* AuthenticationClock;
|
|
10
12
|
const n = DateTime.toEpochMillis(now), authenticated = DateTime.toEpochMillis(intent.sourceAuthenticatedAt), idle = DateTime.toEpochMillis(intent.sourceExpiresAt), absolute = DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt), expires = DateTime.toEpochMillis(intent.expiresAt);
|
|
11
|
-
return intent.sourceKind === kind && authenticated
|
|
12
|
-
};
|
|
13
|
+
return intent.sourceKind === kind && authenticated - n <= futureToleranceMillis && authenticated < idle && n < expires && expires <= idle && idle <= absolute;
|
|
14
|
+
});
|
|
13
15
|
const evidenceEncoding = Schema.encodeSync(Schema.fromJsonString(AuthenticationEvidence));
|
|
14
16
|
const requirementEncoding = Schema.encodeSync(Schema.fromJsonString(SessionStepUpRequirement));
|
|
15
17
|
Schema.encodeSync(Schema.fromJsonString(SessionMetadata));
|
|
18
|
+
const proofEncoding = Schema.encodeSync(Schema.fromJsonString(Schema.Array(AuthenticationProof)));
|
|
19
|
+
const assuranceEncoding = Schema.encodeSync(Schema.fromJsonString(SessionMetadata.fields.assurance));
|
|
16
20
|
/** Validate core-owned plans before driver allocation/SQL expansion. No bearer or Claims decoding. */
|
|
17
21
|
const validateStepUpPlan = Effect.fn("DrizzleStepUp.validatePlan")(function* (plan) {
|
|
18
22
|
const { intent, source, replacement, evidence } = plan;
|
|
19
23
|
const original = source.inspection;
|
|
24
|
+
const issuedAt = DateTime.toEpochMillis(replacement.inspection.session.issuedAt);
|
|
25
|
+
if (issuedAt < DateTime.toEpochMillis(original.session.issuedAt) || evidence.proofs.some((proof) => DateTime.toEpochMillis(proof.verifiedAt) > issuedAt) || issuedAt >= DateTime.toEpochMillis(replacement.inspection.session.expiresAt)) return yield* SessionStepUpInvalid.make({});
|
|
20
26
|
if (requirementEncoding(plan.profileRequirement) !== requirementEncoding(intent.requirement) || replacement.inspection.session.subjectId !== evidence.revision.subjectId || replacement.inspection.session.securityRevision !== evidence.revision.securityRevision || intent.revision.credentials.some((item) => !evidence.revision.credentials.some((actual) => actual.credentialId === item.credentialId && actual.revision === item.revision))) return yield* SessionStepUpInvalid.make({});
|
|
21
|
-
if (intent.sourceKind !== source.guard._tag || intent.sourceKind !== replacement._tag || intent.sourceSessionId !== original.session.sessionId || intent.sourceCredentialVersion !== original.credentialVersion || !sameSessionRevision(intent.revision, original.provenance.evidence.revision) || intent.revision.subjectId !== evidence.revision.subjectId || intent.revision.securityRevision !== evidence.revision.securityRevision || intent.flowId !== evidence.flowId || intent.bindingDigest !== evidence.bindingDigest || DateTime.toEpochMillis(intent.sourceAuthenticatedAt) !== DateTime.toEpochMillis(original.session.assurance.authenticatedAt) || DateTime.toEpochMillis(intent.sourceExpiresAt) !== DateTime.toEpochMillis(original.session.expiresAt) || DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !== DateTime.toEpochMillis(original.session.absoluteExpiresAt) || DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !== DateTime.toEpochMillis(replacement.inspection.session.absoluteExpiresAt) || evidenceEncoding(replacement.inspection.provenance.evidence) !== evidenceEncoding(evidence)) return yield* SessionStepUpInvalid.make({});
|
|
27
|
+
if (intent.sourceKind !== source.guard._tag || intent.sourceKind !== replacement._tag || intent.sourceSessionId !== original.session.sessionId || intent.sourceCredentialVersion !== original.credentialVersion || !sameSessionRevision(intent.revision, original.provenance.evidence.revision) || intent.revision.subjectId !== evidence.revision.subjectId || intent.revision.securityRevision !== evidence.revision.securityRevision || intent.flowId !== evidence.flowId || intent.bindingDigest !== evidence.bindingDigest || DateTime.toEpochMillis(intent.sourceAuthenticatedAt) !== DateTime.toEpochMillis(original.session.assurance.authenticatedAt) || DateTime.toEpochMillis(intent.sourceExpiresAt) !== DateTime.toEpochMillis(original.session.expiresAt) || DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !== DateTime.toEpochMillis(original.session.absoluteExpiresAt) || DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !== DateTime.toEpochMillis(replacement.inspection.session.absoluteExpiresAt) || evidenceEncoding(replacement.inspection.provenance.evidence) !== evidenceEncoding(evidence) || proofEncoding(evidence.proofs.slice(0, original.provenance.evidence.proofs.length)) !== proofEncoding(original.provenance.evidence.proofs)) return yield* SessionStepUpInvalid.make({});
|
|
22
28
|
if (replacement._tag === "Stateful" && (source.guard._tag !== "Stateful" || source.guard.digest !== replacement.expectedDigest || replacement.inspection.session.sessionId !== intent.sourceSessionId)) return yield* SessionStepUpInvalid.make({});
|
|
23
29
|
if (replacement._tag === "StateAssistedSigned" && (replacement.tombstoneSessionId !== intent.sourceSessionId || DateTime.toEpochMillis(replacement.tombstoneUntil) !== DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt))) return yield* SessionStepUpInvalid.make({});
|
|
24
30
|
if (replacement._tag !== "Stateful" && replacement.inspection.session.sessionId === intent.sourceSessionId) return yield* SessionStepUpInvalid.make({});
|
|
31
|
+
const at = DateTime.toEpochMillis(plan.now);
|
|
32
|
+
const base = yield* assessSessionAt(evidence, plan.baseRequirement, at);
|
|
33
|
+
const profile = yield* assessSessionAt(evidence, plan.profileRequirement, at);
|
|
34
|
+
if (!base.satisfied || !profile.satisfied || assuranceEncoding(AuthenticationAssurance.make({
|
|
35
|
+
...base.assurance,
|
|
36
|
+
authenticatedAt: profile.assurance.authenticatedAt
|
|
37
|
+
})) !== assuranceEncoding(replacement.inspection.session.assurance)) return yield* SessionStepUpInvalid.make({});
|
|
25
38
|
});
|
|
26
39
|
//#endregion
|
|
27
40
|
export { decodeStepUpIntent, encodeStepUpIntent, stepUpIntentCodec, stepUpIntentLive, validateStepUpPlan };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-up-state.mjs","names":[],"sources":["../../src/internal/step-up-state.ts"],"sourcesContent":["import {\n SessionStepUpInvalid,\n SessionUnavailable,\n AuthenticationEvidence,\n SessionMetadata,\n SessionStepUpIntent,\n SessionStepUpRequirement,\n type SessionStepUpCompletionPlan,\n type StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Schema } from \"effect\";\n\nimport { sameSessionRevision } from \"./session-native-state\";\n\nexport const stepUpIntentCodec = Schema.fromJsonString(SessionStepUpIntent);\nconst BoundedSnapshot = Schema.String.check(Schema.isMaxLength(131072));\n\nexport const encodeStepUpIntent = (intent: SessionStepUpIntent) =>\n Schema.encodeEffect(stepUpIntentCodec)(intent).pipe(\n Effect.flatMap(Schema.decodeEffect(BoundedSnapshot)),\n Effect.mapError(() => SessionUnavailable.make({})),\n );\n\nexport const decodeStepUpIntent = (snapshot: string) =>\n Schema.decodeEffect(BoundedSnapshot)(snapshot).pipe(\n Effect.flatMap(Schema.decodeEffect(stepUpIntentCodec)),\n Effect.mapError(() => SessionUnavailable.make({})),\n );\n\nexport const stepUpIntentLive = (\n intent: Omit<SessionStepUpIntent, \"version\">,\n kind: SessionStepUpIntent[\"sourceKind\"],\n now: DateTime.Utc,\n) => {\n const n = DateTime.toEpochMillis(now),\n authenticated = DateTime.toEpochMillis(intent.sourceAuthenticatedAt),\n idle = DateTime.toEpochMillis(intent.sourceExpiresAt),\n absolute = DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt),\n expires = DateTime.toEpochMillis(intent.expiresAt);\n\n return (\n intent.sourceKind === kind &&\n authenticated <= n &&\n n < expires &&\n expires <= idle &&\n idle <= absolute\n );\n};\n\nconst evidenceEncoding = Schema.encodeSync(Schema.fromJsonString(AuthenticationEvidence));\nconst requirementEncoding = Schema.encodeSync(Schema.fromJsonString(SessionStepUpRequirement));\nconst metadataEncoding = Schema.encodeSync(Schema.fromJsonString(SessionMetadata));\n\n/** Validate core-owned plans before driver allocation/SQL expansion. No bearer or Claims decoding. */\nexport const validateStepUpPlan = Effect.fn(\"DrizzleStepUp.validatePlan\")(function* <Claims>(\n plan: SessionStepUpCompletionPlan<Claims>,\n) {\n const { intent, source, replacement, evidence } = plan;\n const original = source.inspection;\n\n if (\n requirementEncoding(plan.profileRequirement) !== requirementEncoding(intent.requirement) ||\n replacement.inspection.session.subjectId !== evidence.revision.subjectId ||\n replacement.inspection.session.securityRevision !== evidence.revision.securityRevision ||\n intent.revision.credentials.some(\n (item) =>\n !evidence.revision.credentials.some(\n (actual) =>\n actual.credentialId === item.credentialId && actual.revision === item.revision,\n ),\n )\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n intent.sourceKind !== source.guard._tag ||\n intent.sourceKind !== replacement._tag ||\n intent.sourceSessionId !== original.session.sessionId ||\n intent.sourceCredentialVersion !== original.credentialVersion ||\n !sameSessionRevision(intent.revision, original.provenance.evidence.revision) ||\n intent.revision.subjectId !== evidence.revision.subjectId ||\n intent.revision.securityRevision !== evidence.revision.securityRevision ||\n intent.flowId !== evidence.flowId ||\n intent.bindingDigest !== evidence.bindingDigest ||\n DateTime.toEpochMillis(intent.sourceAuthenticatedAt) !==\n DateTime.toEpochMillis(original.session.assurance.authenticatedAt) ||\n DateTime.toEpochMillis(intent.sourceExpiresAt) !==\n DateTime.toEpochMillis(original.session.expiresAt) ||\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !==\n DateTime.toEpochMillis(original.session.absoluteExpiresAt) ||\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !==\n DateTime.toEpochMillis(replacement.inspection.session.absoluteExpiresAt) ||\n evidenceEncoding(replacement.inspection.provenance.evidence) !== evidenceEncoding(evidence)\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n replacement._tag === \"Stateful\" &&\n (source.guard._tag !== \"Stateful\" ||\n source.guard.digest !== replacement.expectedDigest ||\n replacement.inspection.session.sessionId !== intent.sourceSessionId)\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n replacement._tag === \"StateAssistedSigned\" &&\n (replacement.tombstoneSessionId !== intent.sourceSessionId ||\n DateTime.toEpochMillis(replacement.tombstoneUntil) !==\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt))\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n replacement._tag !== \"Stateful\" &&\n replacement.inspection.session.sessionId === intent.sourceSessionId\n )\n return yield* SessionStepUpInvalid.make({});\n});\n\n/** Compare a consumer-decoded encoded update/readback with the intended rotation.\n * Claims are consumer-owned; their lossless round trip remains the codec contract. */\nexport const stepUpRotationMatches = <Claims>(\n plan: SessionStepUpCompletionPlan<Claims>,\n record: StatefulSessionRecord<Claims>,\n) =>\n plan.replacement._tag === \"Stateful\" &&\n record.digest === plan.replacement.nextDigest &&\n record.credentialVersion === plan.replacement.inspection.credentialVersion &&\n metadataEncoding(record) === metadataEncoding(plan.replacement.inspection.session) &&\n evidenceEncoding(record.provenance.evidence) === evidenceEncoding(plan.evidence);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"step-up-state.mjs","names":[],"sources":["../../src/internal/step-up-state.ts"],"sourcesContent":["import { AuthenticationAssurance, AuthenticationClock } from \"@yielded/auth/Operations\";\nimport {\n SessionStepUpInvalid,\n SessionUnavailable,\n AuthenticationEvidence,\n AuthenticationProof,\n SessionMetadata,\n SessionStepUpIntent,\n SessionStepUpRequirement,\n type SessionStepUpCompletionPlan,\n type StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Schema } from \"effect\";\n\nimport { assessSessionAt, sameSessionRevision } from \"./session-native-state\";\n\nexport const stepUpIntentCodec = Schema.fromJsonString(SessionStepUpIntent);\nconst BoundedSnapshot = Schema.String.check(Schema.isMaxLength(131072));\n\nexport const encodeStepUpIntent = (intent: SessionStepUpIntent) =>\n Schema.encodeEffect(stepUpIntentCodec)(intent).pipe(\n Effect.flatMap(Schema.decodeEffect(BoundedSnapshot)),\n Effect.mapError(() => SessionUnavailable.make({})),\n );\n\nexport const decodeStepUpIntent = (snapshot: string) =>\n Schema.decodeEffect(BoundedSnapshot)(snapshot).pipe(\n Effect.flatMap(Schema.decodeEffect(stepUpIntentCodec)),\n Effect.mapError(() => SessionUnavailable.make({})),\n );\n\nexport const stepUpIntentLive = Effect.fnUntraced(function* (\n intent: Omit<SessionStepUpIntent, \"version\">,\n kind: SessionStepUpIntent[\"sourceKind\"],\n now: DateTime.Utc,\n): Effect.fn.Return<boolean> {\n const { futureToleranceMillis } = yield* AuthenticationClock;\n\n const n = DateTime.toEpochMillis(now),\n authenticated = DateTime.toEpochMillis(intent.sourceAuthenticatedAt),\n idle = DateTime.toEpochMillis(intent.sourceExpiresAt),\n absolute = DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt),\n expires = DateTime.toEpochMillis(intent.expiresAt);\n\n return (\n intent.sourceKind === kind &&\n authenticated - n <= futureToleranceMillis &&\n authenticated < idle &&\n n < expires &&\n expires <= idle &&\n idle <= absolute\n );\n});\n\nconst evidenceEncoding = Schema.encodeSync(Schema.fromJsonString(AuthenticationEvidence));\nconst requirementEncoding = Schema.encodeSync(Schema.fromJsonString(SessionStepUpRequirement));\nconst metadataEncoding = Schema.encodeSync(Schema.fromJsonString(SessionMetadata));\nconst proofEncoding = Schema.encodeSync(Schema.fromJsonString(Schema.Array(AuthenticationProof)));\n\nconst assuranceEncoding = Schema.encodeSync(\n Schema.fromJsonString(SessionMetadata.fields.assurance),\n);\n\n/** Validate core-owned plans before driver allocation/SQL expansion. No bearer or Claims decoding. */\nexport const validateStepUpPlan = Effect.fn(\"DrizzleStepUp.validatePlan\")(function* <Claims>(\n plan: SessionStepUpCompletionPlan<Claims>,\n) {\n const { intent, source, replacement, evidence } = plan;\n const original = source.inspection;\n const issuedAt = DateTime.toEpochMillis(replacement.inspection.session.issuedAt);\n\n if (\n issuedAt < DateTime.toEpochMillis(original.session.issuedAt) ||\n evidence.proofs.some((proof) => DateTime.toEpochMillis(proof.verifiedAt) > issuedAt) ||\n issuedAt >= DateTime.toEpochMillis(replacement.inspection.session.expiresAt)\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n requirementEncoding(plan.profileRequirement) !== requirementEncoding(intent.requirement) ||\n replacement.inspection.session.subjectId !== evidence.revision.subjectId ||\n replacement.inspection.session.securityRevision !== evidence.revision.securityRevision ||\n intent.revision.credentials.some(\n (item) =>\n !evidence.revision.credentials.some(\n (actual) =>\n actual.credentialId === item.credentialId && actual.revision === item.revision,\n ),\n )\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n intent.sourceKind !== source.guard._tag ||\n intent.sourceKind !== replacement._tag ||\n intent.sourceSessionId !== original.session.sessionId ||\n intent.sourceCredentialVersion !== original.credentialVersion ||\n !sameSessionRevision(intent.revision, original.provenance.evidence.revision) ||\n intent.revision.subjectId !== evidence.revision.subjectId ||\n intent.revision.securityRevision !== evidence.revision.securityRevision ||\n intent.flowId !== evidence.flowId ||\n intent.bindingDigest !== evidence.bindingDigest ||\n DateTime.toEpochMillis(intent.sourceAuthenticatedAt) !==\n DateTime.toEpochMillis(original.session.assurance.authenticatedAt) ||\n DateTime.toEpochMillis(intent.sourceExpiresAt) !==\n DateTime.toEpochMillis(original.session.expiresAt) ||\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !==\n DateTime.toEpochMillis(original.session.absoluteExpiresAt) ||\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt) !==\n DateTime.toEpochMillis(replacement.inspection.session.absoluteExpiresAt) ||\n evidenceEncoding(replacement.inspection.provenance.evidence) !== evidenceEncoding(evidence) ||\n proofEncoding(evidence.proofs.slice(0, original.provenance.evidence.proofs.length)) !==\n proofEncoding(original.provenance.evidence.proofs)\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n replacement._tag === \"Stateful\" &&\n (source.guard._tag !== \"Stateful\" ||\n source.guard.digest !== replacement.expectedDigest ||\n replacement.inspection.session.sessionId !== intent.sourceSessionId)\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n replacement._tag === \"StateAssistedSigned\" &&\n (replacement.tombstoneSessionId !== intent.sourceSessionId ||\n DateTime.toEpochMillis(replacement.tombstoneUntil) !==\n DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt))\n )\n return yield* SessionStepUpInvalid.make({});\n if (\n replacement._tag !== \"Stateful\" &&\n replacement.inspection.session.sessionId === intent.sourceSessionId\n )\n return yield* SessionStepUpInvalid.make({});\n\n const at = DateTime.toEpochMillis(plan.now);\n const base = yield* assessSessionAt(evidence, plan.baseRequirement, at);\n\n const profile = yield* assessSessionAt(evidence, plan.profileRequirement, at);\n\n // Freshness can change metadata at commit; validate the exact preparation snapshot here.\n if (\n !base.satisfied ||\n !profile.satisfied ||\n assuranceEncoding(\n AuthenticationAssurance.make({\n ...base.assurance,\n authenticatedAt: profile.assurance.authenticatedAt,\n }),\n ) !== assuranceEncoding(replacement.inspection.session.assurance)\n )\n return yield* SessionStepUpInvalid.make({});\n});\n\n/** Compare a consumer-decoded encoded update/readback with the intended rotation.\n * Claims are consumer-owned; their lossless round trip remains the codec contract. */\nexport const stepUpRotationMatches = <Claims>(\n plan: SessionStepUpCompletionPlan<Claims>,\n record: StatefulSessionRecord<Claims>,\n) =>\n plan.replacement._tag === \"Stateful\" &&\n record.digest === plan.replacement.nextDigest &&\n record.credentialVersion === plan.replacement.inspection.credentialVersion &&\n metadataEncoding(record) === metadataEncoding(plan.replacement.inspection.session) &&\n evidenceEncoding(record.provenance.evidence) === evidenceEncoding(plan.evidence);\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
2
2
|
import { exactSqlText } from "./sql-change.mjs";
|
|
3
3
|
import { randomId } from "./crypto.mjs";
|
|
4
|
+
import { makeStorageClock } from "./storage-clock.mjs";
|
|
4
5
|
import { storageTables } from "./storage-tables.mjs";
|
|
5
6
|
import { requiredEmailAddressConstraints } from "./models/email-model.mjs";
|
|
6
7
|
import { requiredPasswordConstraints } from "./models/password-model.mjs";
|
|
7
8
|
import { requiredProofConstraints } from "./models/proof-model.mjs";
|
|
8
|
-
import { makeStorageClock } from "./storage-clock.mjs";
|
|
9
9
|
import { Crypto, DateTime, Effect, Redacted, Schema, Struct } from "effect";
|
|
10
10
|
import { SqlClient } from "effect/sql";
|
|
11
11
|
import { EmailCredentialSnapshot } from "@yielded/auth/Email";
|