@yielded/auth-persistence 0.1.0-beta.22 → 0.1.0-beta.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/Adapter.d.mts +13 -5
- package/dist/Adapter.mjs +9 -2
- package/dist/OAuthPersistence.d.mts +87 -0
- package/dist/OAuthPersistence.mjs +125 -0
- package/dist/OAuthPersistence.mjs.map +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/internal/authority-snapshot.mjs +53 -0
- package/dist/internal/authority-snapshot.mjs.map +1 -0
- package/dist/internal/configuration.d.mts +1 -1
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/models/oauth-connected-model.d.mts +294 -0
- package/dist/internal/models/oauth-connected-model.mjs +22 -0
- package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
- package/dist/internal/models/oauth-model.d.mts +467 -0
- package/dist/internal/models/oauth-model.mjs +49 -0
- package/dist/internal/models/oauth-model.mjs.map +1 -0
- package/dist/internal/models/password-model.d.mts +4 -97
- package/dist/internal/models/password-model.mjs +0 -3
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +34 -0
- package/dist/internal/native-sql-table.mjs +111 -0
- package/dist/internal/native-sql-table.mjs.map +1 -0
- package/dist/internal/oauth/accounts.d.mts +8 -0
- package/dist/internal/oauth/accounts.mjs +379 -0
- package/dist/internal/oauth/accounts.mjs.map +1 -0
- package/dist/internal/oauth/connected-access.d.mts +9 -0
- package/dist/internal/oauth/connected-access.mjs +229 -0
- package/dist/internal/oauth/connected-access.mjs.map +1 -0
- package/dist/internal/oauth/connected-collection.d.mts +7 -0
- package/dist/internal/oauth/connected-collection.mjs +214 -0
- package/dist/internal/oauth/connected-collection.mjs.map +1 -0
- package/dist/internal/oauth/connected-custody.d.mts +6 -0
- package/dist/internal/oauth/connected-custody.mjs +165 -0
- package/dist/internal/oauth/connected-custody.mjs.map +1 -0
- package/dist/internal/oauth/connected-flow.d.mts +7 -0
- package/dist/internal/oauth/connected-flow.mjs +324 -0
- package/dist/internal/oauth/connected-flow.mjs.map +1 -0
- package/dist/internal/oauth/connected-input.mjs +122 -0
- package/dist/internal/oauth/connected-input.mjs.map +1 -0
- package/dist/internal/oauth/connected-keys.d.mts +2 -0
- package/dist/internal/oauth/connected-keys.mjs +22 -0
- package/dist/internal/oauth/connected-keys.mjs.map +1 -0
- package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
- package/dist/internal/oauth/connected-maintenance.mjs +226 -0
- package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
- package/dist/internal/oauth/connected-management.d.mts +9 -0
- package/dist/internal/oauth/connected-management.mjs +243 -0
- package/dist/internal/oauth/connected-management.mjs.map +1 -0
- package/dist/internal/oauth/connected-reference.mjs +63 -0
- package/dist/internal/oauth/connected-reference.mjs.map +1 -0
- package/dist/internal/oauth/connected-settlement.d.mts +8 -0
- package/dist/internal/oauth/connected-settlement.mjs +141 -0
- package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
- package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
- package/dist/internal/oauth/connected-sign-in.mjs +281 -0
- package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
- package/dist/internal/oauth/connected-state.d.mts +14 -0
- package/dist/internal/oauth/connected-state.mjs +469 -0
- package/dist/internal/oauth/connected-state.mjs.map +1 -0
- package/dist/internal/oauth/connected-target.d.mts +21 -0
- package/dist/internal/oauth/connected-target.mjs +117 -0
- package/dist/internal/oauth/connected-target.mjs.map +1 -0
- package/dist/internal/oauth/flow.d.mts +5 -0
- package/dist/internal/oauth/flow.mjs +279 -0
- package/dist/internal/oauth/flow.mjs.map +1 -0
- package/dist/internal/oauth/input.mjs +127 -0
- package/dist/internal/oauth/input.mjs.map +1 -0
- package/dist/internal/oauth/owner.d.mts +10 -0
- package/dist/internal/oauth/owner.mjs +10 -0
- package/dist/internal/oauth/owner.mjs.map +1 -0
- package/dist/internal/oauth/query-compiler.d.mts +7 -0
- package/dist/internal/oauth/query-compiler.mjs +9 -0
- package/dist/internal/oauth/query-compiler.mjs.map +1 -0
- package/dist/internal/oauth/registration.d.mts +7 -0
- package/dist/internal/oauth/registration.mjs +393 -0
- package/dist/internal/oauth/registration.mjs.map +1 -0
- package/dist/internal/oauth/state.mjs +107 -0
- package/dist/internal/oauth/state.mjs.map +1 -0
- package/dist/internal/oauth/target.d.mts +31 -0
- package/dist/internal/oauth/target.mjs +273 -0
- package/dist/internal/oauth/target.mjs.map +1 -0
- package/dist/internal/oauth-kernel.d.mts +1423 -0
- package/dist/internal/oauth-kernel.mjs +187 -0
- package/dist/internal/oauth-kernel.mjs.map +1 -0
- package/dist/internal/oauth-proxy.d.mts +31 -0
- package/dist/internal/oauth-proxy.mjs +129 -0
- package/dist/internal/oauth-proxy.mjs.map +1 -0
- package/dist/internal/passkey/target.d.mts +1 -1
- package/dist/internal/passkey-kernel.d.mts +5 -5
- package/dist/internal/passkeys.mjs +1 -1
- package/dist/internal/password-credentials.d.mts +11 -0
- package/dist/internal/password-credentials.mjs +121 -0
- package/dist/internal/password-credentials.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +15 -28
- package/dist/internal/password-kernel.mjs +35 -236
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +2 -0
- package/dist/internal/persistence.mjs +5 -5
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/query-operations.d.mts +2 -0
- package/dist/internal/session-kernel.d.mts +5 -3
- package/dist/internal/session-kernel.mjs +95 -29
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
- package/dist/internal/sql-oauth-connected-model.mjs +7 -0
- package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
- package/dist/internal/sql-oauth-model.d.mts +60 -0
- package/dist/internal/sql-oauth-model.mjs +10 -0
- package/dist/internal/sql-oauth-model.mjs.map +1 -0
- package/dist/internal/sql-persistence.mjs +3 -1
- package/dist/internal/sql-persistence.mjs.map +1 -1
- package/dist/internal/sql-query.d.mts +12 -0
- package/dist/internal/sql-query.mjs +15 -18
- package/dist/internal/sql-query.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +54 -0
- package/dist/internal/sql-storage-validation.mjs.map +1 -0
- package/dist/internal/storage-mapping.mjs +2 -67
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-plans.d.mts +7 -0
- package/dist/internal/storage-plans.mjs +189 -0
- package/dist/internal/storage-plans.mjs.map +1 -0
- package/dist/internal/storage-tables.d.mts +0 -99
- package/dist/internal/storage-tables.mjs +0 -43
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +6 -1
- package/dist/internal/storage-validation.mjs +42 -1
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-execution-kernel.d.mts +1 -1
- package/dist/internal/transaction-kernel.d.mts +1 -1
- package/dist/internal/transaction-kernel.mjs +3 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +97 -8
- package/src/OAuthPersistence.ts +427 -0
- package/src/index.ts +3 -0
- package/src/internal/authority-snapshot.ts +91 -0
- package/src/internal/configuration.ts +1 -8
- package/src/internal/models/oauth-connected-model.ts +396 -0
- package/src/internal/models/oauth-model.ts +708 -0
- package/src/internal/models/password-model.ts +0 -120
- package/src/internal/native-sql-table.ts +178 -0
- package/src/internal/oauth/accounts.ts +838 -0
- package/src/internal/oauth/connected-access.ts +388 -0
- package/src/internal/oauth/connected-collection.ts +345 -0
- package/src/internal/oauth/connected-custody.ts +308 -0
- package/src/internal/oauth/connected-flow.ts +562 -0
- package/src/internal/oauth/connected-input.ts +147 -0
- package/src/internal/oauth/connected-keys.ts +31 -0
- package/src/internal/oauth/connected-maintenance.ts +379 -0
- package/src/internal/oauth/connected-management.ts +391 -0
- package/src/internal/oauth/connected-reference.ts +135 -0
- package/src/internal/oauth/connected-settlement.ts +278 -0
- package/src/internal/oauth/connected-sign-in.ts +472 -0
- package/src/internal/oauth/connected-state.ts +924 -0
- package/src/internal/oauth/connected-target.ts +307 -0
- package/src/internal/oauth/flow.ts +505 -0
- package/src/internal/oauth/input.ts +201 -0
- package/src/internal/oauth/owner.ts +22 -0
- package/src/internal/oauth/query-compiler.ts +15 -0
- package/src/internal/oauth/registration.ts +676 -0
- package/src/internal/oauth/state.ts +218 -0
- package/src/internal/oauth/target.ts +803 -0
- package/src/internal/oauth-kernel.ts +294 -0
- package/src/internal/oauth-proxy.ts +176 -0
- package/src/internal/password-credentials.ts +245 -0
- package/src/internal/password-kernel.ts +98 -712
- package/src/internal/persistence.ts +5 -10
- package/src/internal/query-operations.ts +2 -0
- package/src/internal/session-kernel.ts +231 -81
- package/src/internal/sql-oauth-connected-model.ts +145 -0
- package/src/internal/sql-oauth-model.ts +263 -0
- package/src/internal/sql-persistence.ts +2 -0
- package/src/internal/sql-query.ts +16 -13
- package/src/internal/sql-storage-validation.ts +64 -0
- package/src/internal/storage-mapping.ts +0 -66
- package/src/internal/storage-plans.ts +218 -0
- package/src/internal/storage-tables.ts +0 -35
- package/src/internal/storage-validation.ts +58 -0
- package/src/internal/transaction-kernel.ts +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.mjs","names":[],"sources":["../../src/internal/persistence.ts"],"sourcesContent":["import { EmailAddressPersistence, EmailUnavailable } from \"@yielded/auth/Email\";\nimport { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport type { PasskeyConfig } from \"@yielded/auth/Passkey\";\nimport { PasswordPersistence, PasswordUnavailable } from \"@yielded/auth/Password\";\nimport { hooksLayer } from \"@yielded/auth/Persistence\";\nimport { PhoneAdmission, PhoneSignInTargets, PhoneOtpUnavailable } from \"@yielded/auth/PhoneOtp\";\nimport { ProofPersistence, ProofUnavailable } from \"@yielded/auth/Proofs\";\nimport { AuthenticationAuthority, SessionUnavailable } from \"@yielded/auth/Sessions\";\nimport { Context, type Crypto, Effect, Layer, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport {\n PersistenceConfigurationError,\n type ClaimsCodec,\n type ConfigId,\n type Definition,\n type PersistenceApi,\n type Ports,\n type Roles,\n type Provisioning,\n type ProvisioningRequirement,\n type StorageLayout,\n type SubjectMapping,\n type SubjectOptions,\n type PasskeyFeature,\n type PasskeyRequirement,\n type MappingInput,\n} from \"./configuration\";\nimport { randomId } from \"./crypto\";\nimport { makeEmailKernel, CurrentEmailSql, type EmailSqlDatabase } from \"./email-kernel\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport {\n makePasswordKernel,\n CurrentPasswordSql,\n type PasswordSqlDatabase,\n} from \"./password-kernel\";\nimport { makePhoneKernel, CurrentPhoneTransaction } from \"./phone-kernel\";\nimport {\n makeProofKernel,\n CurrentProofSql,\n type ProofSqlDatabase,\n type ProofSqlQuery,\n} from \"./proof-kernel\";\nimport type { QueryOperations } from \"./query-operations\";\nimport { makeRegistrationAuthority } from \"./registration\";\nimport { makeSessionKernel, CurrentSessionSql, type SessionSqlDatabase } from \"./session-kernel\";\nimport { requireStandalone } from \"./standalone\";\nimport { makeMappings } from \"./storage-mapping\";\nimport {\n tableDefinition,\n storageTables,\n type StorageRole,\n type StorageTable,\n} from \"./storage-tables\";\nimport {\n validateStorageBatch,\n withStorageValidation,\n type StorageValidation,\n} from \"./storage-validation\";\nimport { makeTransactionExecutionKernel } from \"./transaction-execution-kernel\";\nimport {\n makeTransactionKernel,\n NativeDatabase,\n type TransactionNativeDatabase,\n} from \"./transaction-kernel\";\n\nexport interface Backend<T extends object, R> {\n readonly makeTable: (definition: StorageTable) => T;\n readonly describe: (table: T) => StorageTable;\n readonly acquire: Effect.Effect<object, PersistenceConfigurationError, R | SqlClient.SqlClient>;\n readonly operations: QueryOperations;\n readonly passkeys: (input: {\n readonly storage: MappingInput;\n readonly namespace: string;\n readonly dialect: \"pg\" | \"sqlite\";\n readonly features: ReadonlyArray<PasskeyFeature>;\n readonly passwordModules: ReadonlyArray<string>;\n }) => Effect.Effect<\n Context.Context<never>,\n PersistenceConfigurationError,\n | PasskeyConfig\n | LifecycleHooks\n | SqlClient.SqlClient\n | NativeDatabase\n | CurrentProofSql\n | Crypto.Crypto\n >;\n}\n\nconst configError = (reason: string) => PersistenceConfigurationError.make({ reason });\n\nconst mappingError = (cause: unknown) =>\n PersistenceMappingError.make({ operation: \"mapping\", cause });\n\nconst timestampKeys = new Set([\n \"verifiedAt\",\n \"createdAt\",\n \"lastIssueAt\",\n \"issuedAt\",\n \"expiresAt\",\n \"absoluteExpiresAt\",\n \"retentionUntil\",\n \"claimDeadline\",\n \"retryAt\",\n \"admittedAt\",\n \"deadline\",\n \"occurredAt\",\n \"dedupUntil\",\n]);\n\nexport const createPersistence = <T extends object, R>(\n backend: Backend<T, R>,\n): PersistenceApi<T, R> => {\n const proofKernel = makeProofKernel(backend.operations);\n const passwordKernel = makePasswordKernel(backend.operations, proofKernel);\n const emailKernel = makeEmailKernel(backend.operations, proofKernel);\n const sessionKernel = makeSessionKernel(backend.operations);\n const transactionKernel = makeTransactionKernel(backend.operations);\n const executionKernel = makeTransactionExecutionKernel(transactionKernel);\n const phoneKernel = makePhoneKernel(backend.operations, transactionKernel);\n\n const make = <C extends ClaimsCodec, const Id extends string, const A extends Definition<C, Id>>(\n auth: A & Definition<C, Id>,\n ) => {\n const features = Object.values(auth.strategies).map((strategy) => strategy.persistence);\n const phone = features.some((feature) => feature?.kind === \"phone\");\n const password = features.some((feature) => feature?.kind === \"password\");\n\n const passkeys = features.filter(\n (feature): feature is PasskeyFeature => feature?.kind === \"passkey\",\n );\n\n const management = features.some(\n (feature) => feature?.kind === \"password\" && feature.management,\n );\n\n const email = features.some((feature) => feature?.kind === \"email\" && feature.addresses);\n const proofs = phone || management || email;\n\n const roles: StorageRole[] = [\"identifiers\", \"credentials\", \"sessions\", \"sessionFlows\"];\n\n if (password)\n roles.push(\n \"passwords\",\n \"passwordAttempts\",\n \"passwordScopes\",\n \"passwordCharges\",\n \"passwordCommands\",\n );\n if (management) roles.push(\"passwordRegistrations\");\n if (email) roles.push(\"emailCredentials\", \"emailCommands\");\n if (phone) roles.push(\"phoneState\");\n if (passkeys.length > 0)\n roles.push(\n \"passkeyCredentials\",\n \"passkeyOwnership\",\n \"passkeyHandles\",\n \"passkeyModules\",\n \"passkeyFlows\",\n \"passkeyAdmissions\",\n \"passkeyCharges\",\n );\n if (passkeys.some((feature) => feature.management)) roles.push(\"passkeyCommands\");\n if (proofs)\n roles.push(\n \"proofRequests\",\n \"proofSeries\",\n \"proofGenerations\",\n \"proofContinuations\",\n \"proofScopes\",\n \"proofAbuse\",\n \"proofFailures\",\n \"proofCommands\",\n );\n\n const ConfigKey = Context.Service<\n ConfigId<A[\"namespace\"]>,\n StorageLayout<T, Roles<C, Id, A>>\n >()(`@yielded/auth-persistence/${auth.namespace}/Config`);\n\n const Config = Object.assign(ConfigKey, {\n layer: (value: StorageLayout<T, Roles<C, Id, A>>) => Layer.succeed(ConfigKey, value),\n });\n\n const ProvisioningKey = Context.Service<ProvisioningRequirement<A>, Provisioning<A>>()(\n `@yielded/auth-persistence/${auth.namespace}/Provisioning`,\n );\n\n const layout = <N, Instant>(\n options: {\n readonly subjects: SubjectOptions<T, N>;\n readonly tables?: Partial<Record<StorageRole, T>>;\n readonly prefix?: string;\n readonly timestamps?: {\n readonly type: \"text\" | \"integer\";\n readonly codec: Schema.Codec<number, Instant>;\n };\n },\n managed: boolean,\n ): StorageLayout<T, Roles<C, Id, A>> => {\n const subject = options.subjects;\n\n if (passkeys.length > 0 && options.timestamps !== undefined)\n throw configError(\n \"Composed passkey storage uses integer milliseconds; use an explicit passkey adapter for a custom timestamp representation\",\n );\n const description = backend.describe(subject.table);\n const idColumn = description.columns[subject.id];\n\n for (const key of [subject.id, subject.status, subject.securityRevision]) {\n if (description.columns[key] === undefined)\n throw configError(`Missing subject column ${key}`);\n }\n if (subject.activeValue === undefined)\n throw configError(\"An explicit active subject value is required\");\n\n if (idColumn === undefined || idColumn.type === \"boolean\")\n throw configError(\"Subject ID must map a text or integer column\");\n\n const prefix = options.prefix;\n\n if (managed && (prefix === undefined || !/^[A-Za-z_][A-Za-z0-9_]{0,100}$/.test(prefix)))\n throw configError(\"Managed persistence requires an explicit SQL table prefix\");\n\n const schema: Partial<Record<StorageRole, T>> = { ...options.tables };\n\n for (const role of roles) {\n if (schema[role] === undefined && managed) {\n const definition = tableDefinition(role, `${prefix}_${role}`, idColumn.type);\n\n const adjusted =\n options.timestamps === undefined\n ? definition\n : {\n ...definition,\n columns: Object.fromEntries(\n Object.entries(definition.columns).map(([key, column]) => [\n key,\n timestampKeys.has(key)\n ? { ...column, type: options.timestamps?.type ?? column.type }\n : column,\n ]),\n ),\n };\n\n schema[role] = backend.makeTable(adjusted);\n }\n }\n for (const role of roles) {\n if (schema[role] === undefined) throw configError(`Missing ${role} table`);\n }\n const instantCodec = options.timestamps?.codec;\n\n const decodeInstant =\n instantCodec === undefined\n ? Schema.decodeUnknownEffect(Schema.Int)\n : Schema.decodeUnknownEffect(instantCodec);\n\n const decodeInstantSync =\n instantCodec === undefined\n ? Schema.decodeUnknownSync(Schema.Int)\n : Schema.decodeUnknownSync(instantCodec);\n\n const subjects: SubjectMapping = {\n table: subject.table,\n id: subject.id,\n status: subject.status,\n securityRevision: subject.securityRevision,\n activeValue: subject.activeValue,\n requirements: subject.requirements,\n actionRequirements: subject.actionRequirements ?? subject.requirements,\n toSubject: (id) =>\n Schema.decodeUnknownEffect(subject.idCodec)(id).pipe(Effect.mapError(mappingError)),\n toNative: (id) =>\n Schema.encodeEffect(subject.idCodec)(id).pipe(Effect.mapError(mappingError)),\n toSubjectSync: Schema.decodeUnknownSync(subject.idCodec),\n toNativeSync: Schema.encodeSync(subject.idCodec),\n };\n\n return Object.freeze({\n namespace: auth.namespace,\n schema: Object.freeze(schema) as Readonly<Record<Roles<C, Id, A>, T>>,\n tables: schema,\n subjects,\n encodeInstant:\n instantCodec === undefined ? (millis: number) => millis : Schema.encodeSync(instantCodec),\n decodeInstant: (value: unknown) => decodeInstant(value).pipe(Effect.mapError(mappingError)),\n decodeInstantSync,\n });\n };\n\n const services = Layer.effectContext(\n Effect.gen(function* () {\n const storage = yield* ConfigKey;\n const hooks = yield* LifecycleHooks;\n const client = yield* SqlClient.SqlClient;\n const database = yield* backend.acquire;\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* configError(\"Use an explicit adapter for this SQL dialect\");\n if (auth.sessionMode !== \"stateful\")\n return yield* configError(\"The composed layer currently requires stateful sessions\");\n if (\n features.some(\n (feature) =>\n feature === undefined ||\n (\"lifecycle\" in feature && feature.lifecycle) ||\n (feature.kind === \"email\" && !feature.addresses),\n )\n )\n return yield* configError(\n \"Use explicit services for strategies without composed persistence support\",\n );\n if (storage.namespace !== auth.namespace)\n return yield* configError(\n \"Persistence configuration belongs to a different Auth definition\",\n );\n const validations: StorageValidation[] = [];\n\n for (const role of roles) {\n const table = (storage.schema as Partial<Record<StorageRole, T>>)[role];\n\n if (table === undefined) return yield* configError(`Missing ${role} table`);\n const description = backend.describe(table);\n const columns = description.columns;\n\n for (const name of Object.keys(storageTables[role].columns)) {\n if (columns[name] === undefined)\n return yield* configError(`Missing ${role}.${name} column`);\n }\n validations.push({ table: description, required: storageTables[role].unique });\n }\n validations.push({\n table: backend.describe(storage.subjects.table as T),\n required: [[storage.subjects.id]],\n });\n yield* validateStorageBatch(dialect, validations);\n const mappings = yield* makeMappings(storage);\n\n const standalone = <E>(error: () => E) =>\n requireStandalone(error, client.transactionService);\n\n // Backend validation owns the foreign query-builder shape, never the decoded rows.\n const native = database as ProofSqlDatabase &\n PasswordSqlDatabase &\n EmailSqlDatabase &\n SessionSqlDatabase &\n TransactionNativeDatabase;\n\n const options = {\n mode: \"interactive\" as const,\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => SessionUnavailable.make({})),\n };\n\n const sessionMapping = mappings.sessions(auth.claims);\n\n const sessionServices = yield* sessionKernel\n .makeSqlStatefulSessions(sessionMapping, options)\n .pipe(\n Effect.provideService(CurrentSessionSql, native),\n Effect.provideService(NativeDatabase, native),\n );\n\n const authority = yield* sessionKernel\n .makeSqlAuthenticationAuthority<C[\"Type\"]>(\n { ...mappings.authority(), isConstraintConflict: () => false },\n options,\n )\n .pipe(\n Effect.provideService(CurrentSessionSql, native),\n Effect.provideService(NativeDatabase, native),\n );\n\n let context: Context.Context<never> = Context.make(AuthenticationAuthority, authority).pipe(\n Context.add(\n auth.sessions.StatefulSessionPersistence,\n sessionServices.statefulSessionPersistence,\n ),\n Context.add(auth.sessions.SessionRepository, sessionServices.sessionRepository),\n );\n\n const proofConfiguration = proofs\n ? {\n mapping: mappings.proofs(),\n configuration: {\n mode: \"interactive\" as const,\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => ProofUnavailable.make({})),\n insertIfAbsent: (query: ProofSqlQuery) => query.onConflictDoNothing(),\n },\n }\n : undefined;\n\n if (proofConfiguration !== undefined) {\n context = Context.add(\n context,\n ProofPersistence,\n yield* proofKernel\n .makeSqlProofPersistence(proofConfiguration.mapping, proofConfiguration.configuration)\n .pipe(\n Effect.provideService(CurrentProofSql, native),\n Effect.provideService(NativeDatabase, native),\n ),\n );\n }\n\n if (password) {\n const persistence = yield* passwordKernel\n .makeSqlPasswordPersistence(mappings.passwords(), {\n mode: \"interactive\",\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => PasswordUnavailable.make({})),\n insertIfAbsent: (query) => query.onConflictDoNothing(),\n ...(proofConfiguration === undefined ? {} : { proof: proofConfiguration }),\n })\n .pipe(\n Effect.provideService(CurrentPasswordSql, native),\n Effect.provideService(NativeDatabase, native),\n );\n\n context = Context.add(context, PasswordPersistence, persistence);\n }\n for (const [name, strategy] of Object.entries(auth.strategies)) {\n if (strategy.persistence?.kind !== \"password\" || !strategy.persistence.management)\n continue;\n const key = strategy.RegistrationAuthority;\n\n if (key === undefined)\n return yield* configError(`Missing subject provisioning for ${name}`);\n\n const registration = yield* makeRegistrationAuthority(\n mappings,\n backend.operations,\n standalone(() => PasswordUnavailable.make({})),\n ProvisioningKey,\n name,\n ).pipe(Effect.provideService(CurrentPasswordSql, native));\n\n context = Context.add(context, key, registration);\n }\n if (email) {\n context = Context.add(\n context,\n EmailAddressPersistence,\n yield* emailKernel\n .makeSqlEmailAddressPersistence(mappings.emails(), {\n mode: \"interactive\",\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => EmailUnavailable.make({})),\n ...(proofConfiguration === undefined ? {} : { proof: proofConfiguration }),\n })\n .pipe(\n Effect.provideService(CurrentEmailSql, native),\n Effect.provideService(NativeDatabase, native),\n ),\n );\n }\n if (phone) {\n const { column, eq } = backend.operations;\n\n const mapping = (moduleId: string) => ({\n moduleId,\n subject: {\n table: storage.subjects.table,\n id: storage.subjects.id,\n securityRevision: storage.subjects.securityRevision,\n activeCondition: eq(\n column(storage.subjects.table, storage.subjects.status),\n storage.subjects.activeValue,\n ),\n },\n subjectIds: {\n toNative: storage.subjects.toNativeSync,\n toSubject: storage.subjects.toSubjectSync,\n },\n identifier: {\n table: mappings.table(\"identifiers\"),\n namespace: \"namespace\",\n value: \"value\",\n subjectId: \"subjectId\",\n revision: \"revision\",\n verifiedAt: \"verifiedAt\",\n activeCondition: eq(column(mappings.table(\"identifiers\"), \"active\"), true),\n },\n credential: {\n table: mappings.table(\"credentials\"),\n id: \"credentialId\",\n subjectId: \"subjectId\",\n revision: \"revision\",\n activeCondition: eq(column(mappings.table(\"credentials\"), \"active\"), true),\n },\n state: {\n table: mappings.table(\"phoneState\"),\n scope: \"scope\",\n state: \"state\",\n version: \"version\",\n encodeInsert: (row: object) => row,\n },\n admission: {\n windowMillis: 60_000,\n networkRequests: 10,\n networkAttempts: 100,\n maximumMessages: 10,\n requestRetentionMillis: 86_400_000,\n },\n engineNowMillis: backend.operations\n .sql`${dialect === \"pg\" ? backend.operations.sql`cast(extract(epoch from clock_timestamp()) * 1000 as bigint)` : backend.operations.sql`cast(round((julianday('now') - 2440587.5) * 86400000) as integer)`}`,\n encodeInstant: storage.encodeInstant,\n });\n\n const execution = yield* executionKernel\n .makeTransactionExecution(\n CurrentPhoneTransaction,\n {\n mode: \"interactive\",\n dialect,\n locking: dialect === \"pg\",\n standaloneGuard: () => standalone(() => PhoneOtpUnavailable.make({})),\n },\n () => PhoneOtpUnavailable.make({}),\n randomId,\n )\n .pipe(Effect.provideService(NativeDatabase, native));\n\n const run = <Value, E, Requirements>(\n work: Effect.Effect<Value, E, Requirements>,\n mutation = true,\n ) =>\n execution.admit.pipe(\n Effect.andThen(execution.run(work, mutation)),\n Effect.provideService(LifecycleHooks, hooks),\n );\n\n const allowed = (moduleId: string) =>\n features.some((feature) => feature?.kind === \"phone\" && feature.moduleId === moduleId);\n\n const admission = PhoneAdmission.of({\n admit: (request) =>\n allowed(request.moduleId)\n ? run(phoneKernel.admitPhone(mapping(request.moduleId), request))\n : Effect.fail(PhoneOtpUnavailable.make({})),\n cleanup: (request) =>\n allowed(request.moduleId)\n ? run(phoneKernel.cleanupPhoneAdmission(mapping(request.moduleId), request))\n : Effect.fail(PhoneOtpUnavailable.make({})),\n });\n\n const targets = PhoneSignInTargets.of({\n lookup: (request) =>\n allowed(request.moduleId)\n ? run(phoneKernel.lookupPhone(mapping(request.moduleId), request), false)\n : Effect.fail(PhoneOtpUnavailable.make({})),\n });\n\n context = context.pipe(\n Context.add(PhoneAdmission, admission),\n Context.add(PhoneSignInTargets, targets),\n );\n }\n\n if (passkeys.length > 0) {\n // Auth capability metadata determines whether PasskeyConfig is required.\n const services = backend.passkeys({\n storage,\n namespace: auth.namespace,\n dialect,\n features: passkeys,\n passwordModules: features.flatMap((feature) =>\n feature?.kind === \"password\" ? [feature.moduleId] : [],\n ),\n }) as Effect.Effect<\n Context.Context<never>,\n PersistenceConfigurationError,\n | PasskeyRequirement<A>\n | Crypto.Crypto\n | LifecycleHooks\n | SqlClient.SqlClient\n | NativeDatabase\n | CurrentProofSql\n >;\n\n context = Context.merge(\n context,\n yield* services.pipe(\n Effect.provideService(NativeDatabase, native),\n Effect.provideService(CurrentProofSql, native),\n ),\n );\n }\n\n // The checked capability metadata above determines exactly these service keys.\n return context as Context.Context<Ports<C, Id, A>>;\n }).pipe(\n withStorageValidation,\n Effect.mapError((error) =>\n Schema.is(PersistenceConfigurationError)(error)\n ? error\n : configError(\"Cannot acquire SQL persistence\"),\n ),\n ),\n ).pipe(Layer.provide(hooksLayer));\n\n return {\n Config,\n Provisioning: ProvisioningKey,\n layer: services,\n managed: <N, Instant = number>(options: {\n readonly subjects: SubjectOptions<T, N>;\n readonly tables?: Partial<Record<Roles<C, Id, A>, T>>;\n readonly prefix?: string;\n readonly timestamps?: {\n readonly type: \"text\" | \"integer\";\n readonly codec: Schema.Codec<number, Instant>;\n };\n }) => layout(options, true),\n map: <N, Instant = number>(options: {\n readonly subjects: SubjectOptions<T, N>;\n readonly tables: Partial<Record<StorageRole, T>>;\n readonly timestamps?: {\n readonly type: \"text\" | \"integer\";\n readonly codec: Schema.Codec<number, Instant>;\n };\n }) => layout(options, false),\n };\n };\n\n return { make };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"persistence.mjs","names":[],"sources":["../../src/internal/persistence.ts"],"sourcesContent":["import { EmailAddressPersistence, EmailUnavailable } from \"@yielded/auth/Email\";\nimport { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport type { PasskeyConfig } from \"@yielded/auth/Passkey\";\nimport { PasswordPersistence, PasswordUnavailable } from \"@yielded/auth/Password\";\nimport { hooksLayer } from \"@yielded/auth/Persistence\";\nimport { PhoneAdmission, PhoneSignInTargets, PhoneOtpUnavailable } from \"@yielded/auth/PhoneOtp\";\nimport { ProofPersistence, ProofUnavailable } from \"@yielded/auth/Proofs\";\nimport { AuthenticationAuthority, SessionUnavailable } from \"@yielded/auth/Sessions\";\nimport { Context, type Crypto, Effect, Layer, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport {\n PersistenceConfigurationError,\n type ClaimsCodec,\n type ConfigId,\n type Definition,\n type PersistenceApi,\n type Ports,\n type Roles,\n type Provisioning,\n type ProvisioningRequirement,\n type StorageLayout,\n type SubjectMapping,\n type SubjectOptions,\n type PasskeyFeature,\n type PasskeyRequirement,\n type MappingInput,\n} from \"./configuration\";\nimport { randomId } from \"./crypto\";\nimport { makeEmailKernel, CurrentEmailSql, type EmailSqlDatabase } from \"./email-kernel\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport {\n makePasswordKernel,\n CurrentPasswordSql,\n type PasswordSqlDatabase,\n} from \"./password-kernel\";\nimport { makePhoneKernel, CurrentPhoneTransaction } from \"./phone-kernel\";\nimport {\n makeProofKernel,\n CurrentProofSql,\n type ProofSqlDatabase,\n type ProofSqlQuery,\n} from \"./proof-kernel\";\nimport type { QueryOperations } from \"./query-operations\";\nimport { makeRegistrationAuthority } from \"./registration\";\nimport { makeSessionKernel, CurrentSessionSql, type SessionSqlDatabase } from \"./session-kernel\";\nimport { requireStandalone } from \"./standalone\";\nimport { makeMappings } from \"./storage-mapping\";\nimport {\n tableDefinition,\n storageTables,\n type StorageRole,\n type StorageTable,\n} from \"./storage-tables\";\nimport {\n validateStorageBatch,\n withStorageValidation,\n type StorageValidation,\n} from \"./storage-validation\";\nimport { makeTransactionExecutionKernel } from \"./transaction-execution-kernel\";\nimport {\n makeTransactionKernel,\n NativeDatabase,\n type TransactionNativeDatabase,\n} from \"./transaction-kernel\";\n\nexport interface Backend<T extends object, R> {\n readonly makeTable: (definition: StorageTable) => T;\n readonly describe: (table: T) => StorageTable;\n readonly acquire: Effect.Effect<object, PersistenceConfigurationError, R | SqlClient.SqlClient>;\n readonly operations: QueryOperations;\n readonly nativeTables: (client: SqlClient.SqlClient, database: object) => NativeSqlTables;\n readonly passkeys: (input: {\n readonly storage: MappingInput;\n readonly namespace: string;\n readonly dialect: \"pg\" | \"sqlite\";\n readonly features: ReadonlyArray<PasskeyFeature>;\n readonly passwordModules: ReadonlyArray<string>;\n }) => Effect.Effect<\n Context.Context<never>,\n PersistenceConfigurationError,\n | PasskeyConfig\n | LifecycleHooks\n | SqlClient.SqlClient\n | NativeDatabase\n | CurrentProofSql\n | Crypto.Crypto\n >;\n}\n\nconst configError = (reason: string) => PersistenceConfigurationError.make({ reason });\n\nconst mappingError = (cause: unknown) =>\n PersistenceMappingError.make({ operation: \"mapping\", cause });\n\nconst timestampKeys = new Set([\n \"verifiedAt\",\n \"createdAt\",\n \"lastIssueAt\",\n \"issuedAt\",\n \"expiresAt\",\n \"absoluteExpiresAt\",\n \"retentionUntil\",\n \"claimDeadline\",\n \"retryAt\",\n \"admittedAt\",\n \"deadline\",\n \"occurredAt\",\n \"dedupUntil\",\n]);\n\nexport const createPersistence = <T extends object, R>(\n backend: Backend<T, R>,\n): PersistenceApi<T, R> => {\n const proofKernel = makeProofKernel(backend.operations);\n const passwordKernel = makePasswordKernel(backend.operations, proofKernel, backend.nativeTables);\n const emailKernel = makeEmailKernel(backend.operations, proofKernel);\n const sessionKernel = makeSessionKernel(backend.operations, backend.nativeTables);\n const transactionKernel = makeTransactionKernel(backend.operations);\n const executionKernel = makeTransactionExecutionKernel(transactionKernel);\n const phoneKernel = makePhoneKernel(backend.operations, transactionKernel);\n\n const make = <C extends ClaimsCodec, const Id extends string, const A extends Definition<C, Id>>(\n auth: A & Definition<C, Id>,\n ) => {\n const features = Object.values(auth.strategies).map((strategy) => strategy.persistence);\n const phone = features.some((feature) => feature?.kind === \"phone\");\n const password = features.some((feature) => feature?.kind === \"password\");\n\n const passkeys = features.filter(\n (feature): feature is PasskeyFeature => feature?.kind === \"passkey\",\n );\n\n const management = features.some(\n (feature) => feature?.kind === \"password\" && feature.management,\n );\n\n const email = features.some((feature) => feature?.kind === \"email\" && feature.addresses);\n const proofs = phone || management || email;\n\n const roles: StorageRole[] = [\"identifiers\", \"credentials\", \"sessions\", \"sessionFlows\"];\n\n if (password) roles.push(\"passwords\", \"passwordCommands\");\n if (management) roles.push(\"passwordRegistrations\");\n if (email) roles.push(\"emailCredentials\", \"emailCommands\");\n if (phone) roles.push(\"phoneState\");\n if (passkeys.length > 0)\n roles.push(\n \"passkeyCredentials\",\n \"passkeyOwnership\",\n \"passkeyHandles\",\n \"passkeyModules\",\n \"passkeyFlows\",\n \"passkeyAdmissions\",\n \"passkeyCharges\",\n );\n if (passkeys.some((feature) => feature.management)) roles.push(\"passkeyCommands\");\n if (proofs)\n roles.push(\n \"proofRequests\",\n \"proofSeries\",\n \"proofGenerations\",\n \"proofContinuations\",\n \"proofScopes\",\n \"proofAbuse\",\n \"proofFailures\",\n \"proofCommands\",\n );\n\n const ConfigKey = Context.Service<\n ConfigId<A[\"namespace\"]>,\n StorageLayout<T, Roles<C, Id, A>>\n >()(`@yielded/auth-persistence/${auth.namespace}/Config`);\n\n const Config = Object.assign(ConfigKey, {\n layer: (value: StorageLayout<T, Roles<C, Id, A>>) => Layer.succeed(ConfigKey, value),\n });\n\n const ProvisioningKey = Context.Service<ProvisioningRequirement<A>, Provisioning<A>>()(\n `@yielded/auth-persistence/${auth.namespace}/Provisioning`,\n );\n\n const layout = <N, Instant>(\n options: {\n readonly subjects: SubjectOptions<T, N>;\n readonly tables?: Partial<Record<StorageRole, T>>;\n readonly prefix?: string;\n readonly timestamps?: {\n readonly type: \"text\" | \"integer\";\n readonly codec: Schema.Codec<number, Instant>;\n };\n },\n managed: boolean,\n ): StorageLayout<T, Roles<C, Id, A>> => {\n const subject = options.subjects;\n\n if (passkeys.length > 0 && options.timestamps !== undefined)\n throw configError(\n \"Composed passkey storage uses integer milliseconds; use an explicit passkey adapter for a custom timestamp representation\",\n );\n const description = backend.describe(subject.table);\n const idColumn = description.columns[subject.id];\n\n for (const key of [subject.id, subject.status, subject.securityRevision]) {\n if (description.columns[key] === undefined)\n throw configError(`Missing subject column ${key}`);\n }\n if (subject.activeValue === undefined)\n throw configError(\"An explicit active subject value is required\");\n\n if (idColumn === undefined || idColumn.type === \"boolean\")\n throw configError(\"Subject ID must map a text or integer column\");\n\n const prefix = options.prefix;\n\n if (managed && (prefix === undefined || !/^[A-Za-z_][A-Za-z0-9_]{0,100}$/.test(prefix)))\n throw configError(\"Managed persistence requires an explicit SQL table prefix\");\n\n const schema: Partial<Record<StorageRole, T>> = { ...options.tables };\n\n for (const role of roles) {\n if (schema[role] === undefined && managed) {\n const definition = tableDefinition(role, `${prefix}_${role}`, idColumn.type);\n\n const adjusted =\n options.timestamps === undefined\n ? definition\n : {\n ...definition,\n columns: Object.fromEntries(\n Object.entries(definition.columns).map(([key, column]) => [\n key,\n timestampKeys.has(key)\n ? { ...column, type: options.timestamps?.type ?? column.type }\n : column,\n ]),\n ),\n };\n\n schema[role] = backend.makeTable(adjusted);\n }\n }\n for (const role of roles) {\n if (schema[role] === undefined) throw configError(`Missing ${role} table`);\n }\n const instantCodec = options.timestamps?.codec;\n\n const decodeInstant =\n instantCodec === undefined\n ? Schema.decodeUnknownEffect(Schema.Int)\n : Schema.decodeUnknownEffect(instantCodec);\n\n const decodeInstantSync =\n instantCodec === undefined\n ? Schema.decodeUnknownSync(Schema.Int)\n : Schema.decodeUnknownSync(instantCodec);\n\n const subjects: SubjectMapping = {\n table: subject.table,\n id: subject.id,\n status: subject.status,\n securityRevision: subject.securityRevision,\n activeValue: subject.activeValue,\n requirements: subject.requirements,\n actionRequirements: subject.actionRequirements ?? subject.requirements,\n toSubject: (id) =>\n Schema.decodeUnknownEffect(subject.idCodec)(id).pipe(Effect.mapError(mappingError)),\n toNative: (id) =>\n Schema.encodeEffect(subject.idCodec)(id).pipe(Effect.mapError(mappingError)),\n toSubjectSync: Schema.decodeUnknownSync(subject.idCodec),\n toNativeSync: Schema.encodeSync(subject.idCodec),\n };\n\n return Object.freeze({\n namespace: auth.namespace,\n schema: Object.freeze(schema) as Readonly<Record<Roles<C, Id, A>, T>>,\n tables: schema,\n subjects,\n encodeInstant:\n instantCodec === undefined ? (millis: number) => millis : Schema.encodeSync(instantCodec),\n decodeInstant: (value: unknown) => decodeInstant(value).pipe(Effect.mapError(mappingError)),\n decodeInstantSync,\n });\n };\n\n const services = Layer.effectContext(\n Effect.gen(function* () {\n const storage = yield* ConfigKey;\n const hooks = yield* LifecycleHooks;\n const client = yield* SqlClient.SqlClient;\n const database = yield* backend.acquire;\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* configError(\"Use an explicit adapter for this SQL dialect\");\n if (auth.sessionMode !== \"stateful\")\n return yield* configError(\"The composed layer currently requires stateful sessions\");\n if (\n features.some(\n (feature) =>\n feature === undefined ||\n (\"lifecycle\" in feature && feature.lifecycle) ||\n (feature.kind === \"email\" && !feature.addresses),\n )\n )\n return yield* configError(\n \"Use explicit services for strategies without composed persistence support\",\n );\n if (storage.namespace !== auth.namespace)\n return yield* configError(\n \"Persistence configuration belongs to a different Auth definition\",\n );\n const validations: StorageValidation[] = [];\n\n for (const role of roles) {\n const table = (storage.schema as Partial<Record<StorageRole, T>>)[role];\n\n if (table === undefined) return yield* configError(`Missing ${role} table`);\n const description = backend.describe(table);\n const columns = description.columns;\n\n for (const name of Object.keys(storageTables[role].columns)) {\n if (columns[name] === undefined)\n return yield* configError(`Missing ${role}.${name} column`);\n }\n validations.push({ table: description, required: storageTables[role].unique });\n }\n validations.push({\n table: backend.describe(storage.subjects.table as T),\n required: [[storage.subjects.id]],\n });\n yield* validateStorageBatch(dialect, validations);\n const mappings = yield* makeMappings(storage);\n\n const standalone = <E>(error: () => E) =>\n requireStandalone(error, client.transactionService);\n\n // Backend validation owns the foreign query-builder shape, never the decoded rows.\n const native = database as ProofSqlDatabase &\n PasswordSqlDatabase &\n EmailSqlDatabase &\n SessionSqlDatabase &\n TransactionNativeDatabase;\n\n const options = {\n mode: \"interactive\" as const,\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => SessionUnavailable.make({})),\n };\n\n const sessionMapping = mappings.sessions(auth.claims);\n\n const sessionServices = yield* sessionKernel\n .makeSqlStatefulSessions(sessionMapping, options)\n .pipe(\n Effect.provideService(CurrentSessionSql, native),\n Effect.provideService(NativeDatabase, native),\n );\n\n const authority = yield* sessionKernel\n .makeSqlAuthenticationAuthority<C[\"Type\"]>(\n { ...mappings.authority(), isConstraintConflict: () => false },\n options,\n )\n .pipe(\n Effect.provideService(CurrentSessionSql, native),\n Effect.provideService(NativeDatabase, native),\n );\n\n let context: Context.Context<never> = Context.make(AuthenticationAuthority, authority).pipe(\n Context.add(\n auth.sessions.StatefulSessionPersistence,\n sessionServices.statefulSessionPersistence,\n ),\n Context.add(auth.sessions.SessionRepository, sessionServices.sessionRepository),\n );\n\n const proofConfiguration = proofs\n ? {\n mapping: mappings.proofs(),\n configuration: {\n mode: \"interactive\" as const,\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => ProofUnavailable.make({})),\n insertIfAbsent: (query: ProofSqlQuery) => query.onConflictDoNothing(),\n },\n }\n : undefined;\n\n if (proofConfiguration !== undefined) {\n context = Context.add(\n context,\n ProofPersistence,\n yield* proofKernel\n .makeSqlProofPersistence(proofConfiguration.mapping, proofConfiguration.configuration)\n .pipe(\n Effect.provideService(CurrentProofSql, native),\n Effect.provideService(NativeDatabase, native),\n ),\n );\n }\n\n if (password) {\n const persistence = yield* passwordKernel\n .makeSqlPasswordPersistence(mappings.passwords(), {\n mode: \"interactive\",\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => PasswordUnavailable.make({})),\n insertIfAbsent: (query) => query.onConflictDoNothing(),\n ...(proofConfiguration === undefined ? {} : { proof: proofConfiguration }),\n })\n .pipe(\n Effect.provideService(CurrentPasswordSql, native),\n Effect.provideService(NativeDatabase, native),\n );\n\n context = Context.add(context, PasswordPersistence, persistence);\n }\n for (const [name, strategy] of Object.entries(auth.strategies)) {\n if (strategy.persistence?.kind !== \"password\" || !strategy.persistence.management)\n continue;\n const key = strategy.RegistrationAuthority;\n\n if (key === undefined)\n return yield* configError(`Missing subject provisioning for ${name}`);\n\n const registration = yield* makeRegistrationAuthority(\n mappings,\n backend.operations,\n standalone(() => PasswordUnavailable.make({})),\n ProvisioningKey,\n name,\n ).pipe(Effect.provideService(CurrentPasswordSql, native));\n\n context = Context.add(context, key, registration);\n }\n if (email) {\n context = Context.add(\n context,\n EmailAddressPersistence,\n yield* emailKernel\n .makeSqlEmailAddressPersistence(mappings.emails(), {\n mode: \"interactive\",\n locking: dialect === \"pg\",\n standaloneGuard: standalone(() => EmailUnavailable.make({})),\n ...(proofConfiguration === undefined ? {} : { proof: proofConfiguration }),\n })\n .pipe(\n Effect.provideService(CurrentEmailSql, native),\n Effect.provideService(NativeDatabase, native),\n ),\n );\n }\n if (phone) {\n const { column, eq } = backend.operations;\n\n const mapping = (moduleId: string) => ({\n moduleId,\n subject: {\n table: storage.subjects.table,\n id: storage.subjects.id,\n securityRevision: storage.subjects.securityRevision,\n activeCondition: eq(\n column(storage.subjects.table, storage.subjects.status),\n storage.subjects.activeValue,\n ),\n },\n subjectIds: {\n toNative: storage.subjects.toNativeSync,\n toSubject: storage.subjects.toSubjectSync,\n },\n identifier: {\n table: mappings.table(\"identifiers\"),\n namespace: \"namespace\",\n value: \"value\",\n subjectId: \"subjectId\",\n revision: \"revision\",\n verifiedAt: \"verifiedAt\",\n activeCondition: eq(column(mappings.table(\"identifiers\"), \"active\"), true),\n },\n credential: {\n table: mappings.table(\"credentials\"),\n id: \"credentialId\",\n subjectId: \"subjectId\",\n revision: \"revision\",\n activeCondition: eq(column(mappings.table(\"credentials\"), \"active\"), true),\n },\n state: {\n table: mappings.table(\"phoneState\"),\n scope: \"scope\",\n state: \"state\",\n version: \"version\",\n encodeInsert: (row: object) => row,\n },\n admission: {\n windowMillis: 60_000,\n networkRequests: 10,\n networkAttempts: 100,\n maximumMessages: 10,\n requestRetentionMillis: 86_400_000,\n },\n engineNowMillis: backend.operations\n .sql`${dialect === \"pg\" ? backend.operations.sql`cast(extract(epoch from clock_timestamp()) * 1000 as bigint)` : backend.operations.sql`cast(round((julianday('now') - 2440587.5) * 86400000) as integer)`}`,\n encodeInstant: storage.encodeInstant,\n });\n\n const execution = yield* executionKernel\n .makeTransactionExecution(\n CurrentPhoneTransaction,\n {\n mode: \"interactive\",\n dialect,\n locking: dialect === \"pg\",\n standaloneGuard: () => standalone(() => PhoneOtpUnavailable.make({})),\n },\n () => PhoneOtpUnavailable.make({}),\n randomId,\n )\n .pipe(Effect.provideService(NativeDatabase, native));\n\n const run = <Value, E, Requirements>(\n work: Effect.Effect<Value, E, Requirements>,\n mutation = true,\n ) =>\n execution.admit.pipe(\n Effect.andThen(execution.run(work, mutation)),\n Effect.provideService(LifecycleHooks, hooks),\n );\n\n const allowed = (moduleId: string) =>\n features.some((feature) => feature?.kind === \"phone\" && feature.moduleId === moduleId);\n\n const admission = PhoneAdmission.of({\n admit: (request) =>\n allowed(request.moduleId)\n ? run(phoneKernel.admitPhone(mapping(request.moduleId), request))\n : Effect.fail(PhoneOtpUnavailable.make({})),\n cleanup: (request) =>\n allowed(request.moduleId)\n ? run(phoneKernel.cleanupPhoneAdmission(mapping(request.moduleId), request))\n : Effect.fail(PhoneOtpUnavailable.make({})),\n });\n\n const targets = PhoneSignInTargets.of({\n lookup: (request) =>\n allowed(request.moduleId)\n ? run(phoneKernel.lookupPhone(mapping(request.moduleId), request), false)\n : Effect.fail(PhoneOtpUnavailable.make({})),\n });\n\n context = context.pipe(\n Context.add(PhoneAdmission, admission),\n Context.add(PhoneSignInTargets, targets),\n );\n }\n\n if (passkeys.length > 0) {\n // Auth capability metadata determines whether PasskeyConfig is required.\n const services = backend.passkeys({\n storage,\n namespace: auth.namespace,\n dialect,\n features: passkeys,\n passwordModules: features.flatMap((feature) =>\n feature?.kind === \"password\" ? [feature.moduleId] : [],\n ),\n }) as Effect.Effect<\n Context.Context<never>,\n PersistenceConfigurationError,\n | PasskeyRequirement<A>\n | Crypto.Crypto\n | LifecycleHooks\n | SqlClient.SqlClient\n | NativeDatabase\n | CurrentProofSql\n >;\n\n context = Context.merge(\n context,\n yield* services.pipe(\n Effect.provideService(NativeDatabase, native),\n Effect.provideService(CurrentProofSql, native),\n ),\n );\n }\n\n // The checked capability metadata above determines exactly these service keys.\n return context as Context.Context<Ports<C, Id, A>>;\n }).pipe(\n withStorageValidation,\n Effect.mapError((error) =>\n Schema.is(PersistenceConfigurationError)(error)\n ? error\n : configError(\"Cannot acquire SQL persistence\"),\n ),\n ),\n ).pipe(Layer.provide(hooksLayer));\n\n return {\n Config,\n Provisioning: ProvisioningKey,\n layer: services,\n managed: <N, Instant = number>(options: {\n readonly subjects: SubjectOptions<T, N>;\n readonly tables?: Partial<Record<Roles<C, Id, A>, T>>;\n readonly prefix?: string;\n readonly timestamps?: {\n readonly type: \"text\" | \"integer\";\n readonly codec: Schema.Codec<number, Instant>;\n };\n }) => layout(options, true),\n map: <N, Instant = number>(options: {\n readonly subjects: SubjectOptions<T, N>;\n readonly tables: Partial<Record<StorageRole, T>>;\n readonly timestamps?: {\n readonly type: \"text\" | \"integer\";\n readonly codec: Schema.Codec<number, Instant>;\n };\n }) => layout(options, false),\n };\n };\n\n return { make };\n};\n"],"mappings":"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"}
|
|
@@ -44,10 +44,12 @@ export interface QueryOperations<Fragment extends SqlFragment = SqlFragment, Col
|
|
|
44
44
|
readonly balancedD1And: Predicate$1<Fragment>;
|
|
45
45
|
readonly asc: (value: unknown) => Fragment;
|
|
46
46
|
readonly eq: Comparison<Fragment>;
|
|
47
|
+
readonly ne: Comparison<Fragment>;
|
|
47
48
|
readonly gt: Comparison<Fragment>;
|
|
48
49
|
readonly gte: Comparison<Fragment>;
|
|
49
50
|
readonly lte: Comparison<Fragment>;
|
|
50
51
|
readonly inArray: (value: unknown, values: ReadonlyArray<unknown>) => Fragment;
|
|
52
|
+
readonly isNotNull: (value: unknown) => Fragment;
|
|
51
53
|
readonly isNull: (value: unknown) => Fragment;
|
|
52
54
|
readonly notExists: (value: SqlExpression) => Fragment;
|
|
53
55
|
readonly sql: {
|
|
@@ -2,10 +2,12 @@ import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
|
2
2
|
import { QueryFailure, QueryOperations } from "./query-operations.mjs";
|
|
3
3
|
import { AuthenticationAuthorityMapping, PendingAuthenticationMapping, SignedSessionValidityMapping, StatefulSessionMapping } from "./models/session-model.mjs";
|
|
4
4
|
import { SessionStepUpMapping } from "./models/step-up-model.mjs";
|
|
5
|
+
import { NativeSqlTables } from "./native-sql-table.mjs";
|
|
5
6
|
import { Context, DateTime, Effect } from "effect";
|
|
6
7
|
import { SubjectId } from "@yielded/auth/Schema";
|
|
7
8
|
import { LifecycleHooks } from "@yielded/auth/Hooks";
|
|
8
|
-
import {
|
|
9
|
+
import { AuthenticationCapture, AuthenticationEvidence, PendingAuthentication, PendingAuthenticationInvalid, PendingConsumption, PrepareSessionCommit, SessionRepository, SessionStepUpPersistence, SessionUnavailable, SignedSessionValidity, StaleAuthentication, StatefulSessionPersistence } from "@yielded/auth/Sessions";
|
|
10
|
+
import { SqlClient } from "effect/sql/SqlClient";
|
|
9
11
|
import * as SqlError$1 from "effect/sql/SqlError";
|
|
10
12
|
//#region src/internal/session-kernel.d.ts
|
|
11
13
|
type CommitMode = "interactive" | "synchronous";
|
|
@@ -36,9 +38,9 @@ export interface SessionSqlOptions {
|
|
|
36
38
|
/** Target-specific ambient transaction preflight. It runs before IDs or writes. */
|
|
37
39
|
readonly standaloneGuard: Guard;
|
|
38
40
|
}
|
|
39
|
-
export declare const makeSessionKernel: (operations: QueryOperations) => {
|
|
41
|
+
export declare const makeSessionKernel: (operations: QueryOperations, nativeTables?: (client: SqlClient, database: object) => NativeSqlTables) => {
|
|
40
42
|
makeSqlAuthenticationAuthority: <Claims>(mapping: AuthenticationAuthorityMapping<Claims, any, any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<{
|
|
41
|
-
readonly capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) => Effect.Effect<
|
|
43
|
+
readonly capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) => Effect.Effect<AuthenticationCapture, StaleAuthentication | SessionUnavailable>;
|
|
42
44
|
readonly requirements: (evidence: AuthenticationEvidence) => Effect.Effect<import("@yielded/auth/Sessions").AuthenticationRequirement, StaleAuthentication | SessionUnavailable>;
|
|
43
45
|
readonly approve: <A>(input: {
|
|
44
46
|
readonly evidence: AuthenticationEvidence;
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { PersistenceMappingError, isMappedConstraintConflict } from "./mapping-error.mjs";
|
|
2
|
+
import { makeNativeSqlTables, sqlMapping } from "./native-sql-table.mjs";
|
|
3
|
+
import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
4
|
+
import { makeAuthoritySnapshot } from "./authority-snapshot.mjs";
|
|
2
5
|
import { decodeStepUpIntent, encodeStepUpIntent, sameStepUpRevision, stepUpIntentLive, stepUpRotationMatches, validateStepUpPlan } from "./step-up-state.mjs";
|
|
6
|
+
import { canJoinTextColumns } from "./storage-validation.mjs";
|
|
3
7
|
import { Cause, Context, DateTime, Effect, Option, Schema } from "effect";
|
|
4
8
|
import { CurrentCommitJournal, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
|
|
5
|
-
import { PendingAuthenticationInvalid, SessionConflict, SessionInvalid, SessionMetadata, SessionStepUpInvalid, SessionUnavailable, StaleAuthentication, assessAuthentication, pendingAuthenticationContext, snapshotPendingAuthenticationContext, snapshotSessionAuthenticationProvenance } from "@yielded/auth/Sessions";
|
|
9
|
+
import { AuthenticationCapture, PendingAuthenticationInvalid, SessionConflict, SessionInvalid, SessionMetadata, SessionStepUpInvalid, SessionUnavailable, StaleAuthentication, assessAuthentication, pendingAuthenticationContext, snapshotPendingAuthenticationContext, snapshotSessionAuthenticationProvenance } from "@yielded/auth/Sessions";
|
|
6
10
|
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
7
11
|
//#region src/internal/session-kernel.ts
|
|
12
|
+
const decodeRevocationFlag = Schema.decodeUnknownEffect(Schema.Literals([0, 1]));
|
|
13
|
+
/** Reads need no transaction of their own. A caller-owned transaction still
|
|
14
|
+
* needs a savepoint so catching an SQL failure does not leave it aborted. */
|
|
15
|
+
const withVerificationSavepoint = Effect.fnUntraced(function* (effect) {
|
|
16
|
+
const { $client } = yield* NativeDatabase;
|
|
17
|
+
const transaction = yield* Effect.serviceOption($client.transactionService);
|
|
18
|
+
return yield* Option.isSome(transaction) ? $client.withTransaction(effect) : effect;
|
|
19
|
+
});
|
|
8
20
|
/** Native SQL authority for the current transaction or savepoint. */
|
|
9
21
|
var CurrentSessionSql = class extends Context.Service()("effect-auth/drizzle/CurrentSessionSql") {};
|
|
10
|
-
const makeSessionKernel = (operations) => {
|
|
22
|
+
const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlTables(client)) => {
|
|
11
23
|
const { and, eq, gt, lte, inArray, or, sql, column, updateValues } = operations;
|
|
12
24
|
const unavailable = () => SessionUnavailable.make({});
|
|
13
25
|
const stale = () => StaleAuthentication.make({});
|
|
@@ -86,11 +98,11 @@ const makeSessionKernel = (operations) => {
|
|
|
86
98
|
}
|
|
87
99
|
};
|
|
88
100
|
});
|
|
89
|
-
const captureIn = Effect.fn("DrizzleSession.capture")(function* (mapping, subjectId, credentialIds, locking) {
|
|
90
|
-
return (yield* captureDetailsIn(mapping, subjectId, credentialIds, locking)).revision;
|
|
91
|
-
});
|
|
92
101
|
const validateEvidenceIn = Effect.fn("DrizzleSession.validateEvidence")(function* (mapping, evidence, locking) {
|
|
93
102
|
const captured = yield* captureDetailsIn(mapping, evidence.revision.subjectId, evidence.revision.credentials.map((item) => item.credentialId), locking);
|
|
103
|
+
return yield* validateCapturedEvidence(mapping, evidence, captured);
|
|
104
|
+
});
|
|
105
|
+
const validateCapturedEvidence = Effect.fnUntraced(function* (mapping, evidence, captured) {
|
|
94
106
|
if (captured.revision.securityRevision !== evidence.revision.securityRevision || captured.revision.credentials.length !== evidence.revision.credentials.length || captured.revision.credentials.some((item, index) => {
|
|
95
107
|
const expected = [...evidence.revision.credentials].sort((a, b) => a.credentialId.localeCompare(b.credentialId))[index];
|
|
96
108
|
return expected === void 0 || expected.credentialId !== item.credentialId || expected.revision !== item.revision;
|
|
@@ -183,12 +195,19 @@ const makeSessionKernel = (operations) => {
|
|
|
183
195
|
const database = yield* CurrentSessionSql;
|
|
184
196
|
if (!options.coordinated) yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(Effect.mapError(unavailable));
|
|
185
197
|
const hooks = yield* LifecycleHooks;
|
|
198
|
+
const root = yield* NativeDatabase;
|
|
199
|
+
const sqlClient = root.$client.withoutTransforms();
|
|
200
|
+
const tables = yield* sqlMapping(() => nativeTables(sqlClient, root)).pipe(Effect.mapError(unavailable));
|
|
201
|
+
const readSnapshot = makeAuthoritySnapshot(tables, mapping);
|
|
186
202
|
const service = {
|
|
187
|
-
capture: (subjectId, credentialIds) => safeTransaction(
|
|
188
|
-
|
|
203
|
+
capture: (subjectId, credentialIds) => safeTransaction(readSnapshot(subjectId, credentialIds).pipe(Effect.flatMap((value) => Effect.flatMap(mapping.subject.decodeRequirement(value.subject), (requirement) => Schema.decodeEffect(AuthenticationCapture)({
|
|
204
|
+
revision: value.revision,
|
|
205
|
+
requirement
|
|
206
|
+
}))))),
|
|
207
|
+
requirements: (evidence) => safeTransaction(readSnapshot(evidence.revision.subjectId, evidence.revision.credentials.map((item) => item.credentialId)).pipe(Effect.flatMap((captured) => validateCapturedEvidence(mapping, evidence, captured)), Effect.map((value) => value.requirement))),
|
|
189
208
|
approve: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
190
209
|
const journal = yield* CurrentCommitJournal;
|
|
191
|
-
const { requirement } = yield* validateEvidenceIn(mapping, input.evidence, true);
|
|
210
|
+
const { requirement } = yield* options.locking ? validateEvidenceIn(mapping, input.evidence, true) : Effect.flatMap(readSnapshot(input.evidence.revision.subjectId, input.evidence.revision.credentials.map((item) => item.credentialId)), (captured) => validateCapturedEvidence(mapping, input.evidence, captured));
|
|
192
211
|
if (input.pending === void 0) {
|
|
193
212
|
const assessed = yield* assessAuthentication(input.evidence, requirement).pipe(Effect.mapError(unavailable));
|
|
194
213
|
const now = yield* freshNow;
|
|
@@ -211,9 +230,9 @@ const makeSessionKernel = (operations) => {
|
|
|
211
230
|
}))
|
|
212
231
|
};
|
|
213
232
|
return {
|
|
214
|
-
capture: (subjectId, credentialIds) => service.capture(subjectId, credentialIds).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
215
|
-
requirements: (evidence) => service.requirements(evidence).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
216
|
-
approve: (input, prepare) => service.approve(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks))
|
|
233
|
+
capture: (subjectId, credentialIds) => service.capture(subjectId, credentialIds).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
234
|
+
requirements: (evidence) => service.requirements(evidence).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
235
|
+
approve: (input, prepare) => service.approve(input, prepare).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks))
|
|
217
236
|
};
|
|
218
237
|
});
|
|
219
238
|
/** The same locked security checks serve full consumption reads and the
|
|
@@ -312,16 +331,32 @@ const makeSessionKernel = (operations) => {
|
|
|
312
331
|
const database = yield* CurrentSessionSql;
|
|
313
332
|
if (!options.coordinated) yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(Effect.mapError(unavailable));
|
|
314
333
|
const hooks = yield* LifecycleHooks;
|
|
334
|
+
const root = yield* NativeDatabase;
|
|
335
|
+
const tables = yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(Effect.mapError(unavailable));
|
|
336
|
+
const readSnapshot = makeAuthoritySnapshot(tables, mapping);
|
|
315
337
|
const c = sessionColumns(mapping);
|
|
338
|
+
const verification = yield* safeTransaction(Effect.gen(function* () {
|
|
339
|
+
const { session, subject } = yield* sqlMapping(() => ({
|
|
340
|
+
session: tables(mapping.session.table).as("auth_session"),
|
|
341
|
+
subject: tables(mapping.subject.table).as("auth_subject")
|
|
342
|
+
}));
|
|
343
|
+
const [sessionOwner, subjectOwner] = yield* sqlMapping(() => [session.unencodedTextColumn?.(mapping.session.subjectId), subject.unencodedTextColumn?.(mapping.subject.id)]);
|
|
344
|
+
return {
|
|
345
|
+
session,
|
|
346
|
+
subject,
|
|
347
|
+
canJoinOwner: yield* canJoinTextColumns(sessionOwner, subjectOwner)
|
|
348
|
+
};
|
|
349
|
+
}));
|
|
316
350
|
const persistence = {
|
|
317
351
|
establish: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
318
352
|
const transaction = yield* CurrentSessionSql;
|
|
319
353
|
const journal = yield* CurrentCommitJournal;
|
|
320
|
-
const { nativeSubjectId, requirement } = yield* validateEvidenceIn(mapping, input.evidence, true);
|
|
354
|
+
const { nativeSubjectId, requirement } = yield* options.locking ? validateEvidenceIn(mapping, input.evidence, true) : Effect.flatMap(readSnapshot(input.evidence.revision.subjectId, input.evidence.revision.credentials.map((item) => item.credentialId)), (captured) => validateCapturedEvidence(mapping, input.evidence, captured));
|
|
321
355
|
let flowInsert;
|
|
322
356
|
let pendingExpiresAt;
|
|
323
|
-
if (input.pending === void 0)
|
|
324
|
-
|
|
357
|
+
if (input.pending === void 0) {
|
|
358
|
+
if (input.fresh !== true || input.handoffSourceSessionId !== void 0) flowInsert = yield* ensureDirectFlowAvailable(mapping, input.evidence, nativeSubjectId, input.session.absoluteExpiresAt);
|
|
359
|
+
} else {
|
|
325
360
|
if (mapping.pending === void 0) return yield* invalidPending();
|
|
326
361
|
const pending = yield* validatePending(mapping, input.pending);
|
|
327
362
|
if (!preservesRevision(input.evidence, pending.evidence)) return yield* invalidPending();
|
|
@@ -346,7 +381,7 @@ const makeSessionKernel = (operations) => {
|
|
|
346
381
|
};
|
|
347
382
|
const receipt = prepare(record, journal);
|
|
348
383
|
if (flowInsert !== void 0) yield* transaction.insert(mapping.flow.table).values(flowInsert);
|
|
349
|
-
else yield* consumePending({
|
|
384
|
+
else if (input.pending !== void 0) yield* consumePending({
|
|
350
385
|
...mapping,
|
|
351
386
|
pending: mapping.pending
|
|
352
387
|
}, input.pending, input.session.absoluteExpiresAt);
|
|
@@ -358,14 +393,23 @@ const makeSessionKernel = (operations) => {
|
|
|
358
393
|
if (DateTime.toEpochMillis(commitNow) < DateTime.toEpochMillis(record.issuedAt) || DateTime.toEpochMillis(commitNow) >= DateTime.toEpochMillis(record.expiresAt) || !(yield* assessAuthentication(input.evidence, requirement)).satisfied) return yield* stale();
|
|
359
394
|
return receipt;
|
|
360
395
|
}).pipe(normalizeMutation(mapping))),
|
|
361
|
-
verify: (input) => safeTransaction(
|
|
362
|
-
const
|
|
396
|
+
verify: (input) => safeTransaction(withVerificationSavepoint(Effect.gen(function* () {
|
|
397
|
+
const sql = root.$client.withoutTransforms();
|
|
398
|
+
const { session, subject, canJoinOwner } = verification;
|
|
399
|
+
const owner = canJoinOwner ? void 0 : (yield* database.select({ subjectId: c.subjectId }).from(mapping.session.table).where(eq(c.digest, input.digest)).limit(1))[0];
|
|
400
|
+
if (!canJoinOwner && owner === void 0) return yield* invalidSession();
|
|
401
|
+
const row = (yield* sqlMapping(() => sql`select ${session.fields("v_")}, ${subject.fields("s_")}
|
|
402
|
+
from ${session.name} inner join ${subject.name} on
|
|
403
|
+
${subject.column(mapping.subject.id)} = ${owner === void 0 ? session.column(mapping.session.subjectId) : subject.value(mapping.subject.id, owner.subjectId)}
|
|
404
|
+
where ${session.column(mapping.session.digest)} = ${session.value(mapping.session.digest, input.digest)}
|
|
405
|
+
limit 1`).pipe(Effect.flatten))[0];
|
|
363
406
|
if (row === void 0) return yield* invalidSession();
|
|
364
|
-
const
|
|
407
|
+
const sessionRow = yield* sqlMapping(() => session.decode(row, "v_"));
|
|
408
|
+
const subjectRow = yield* sqlMapping(() => subject.decode(row, "s_"));
|
|
409
|
+
const record = yield* mapping.session.decode(sessionRow);
|
|
365
410
|
const nativeSubjectId = yield* mapping.subjectId.toNative(record.subjectId);
|
|
366
|
-
const subject = (yield* readSubject(mapping, nativeSubjectId, false))[0];
|
|
367
411
|
const now = yield* freshNow;
|
|
368
|
-
if (
|
|
412
|
+
if (!mapping.subjectId.equals(nativeSubjectId, sessionRow[mapping.session.subjectId]) || !mapping.subjectId.equals(nativeSubjectId, subjectRow[mapping.subject.id]) || !mapping.subject.isActiveStatus(subjectRow[mapping.subject.status]) || subjectRow[mapping.subject.securityRevision] !== record.securityRevision || DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.expiresAt) || DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.absoluteExpiresAt)) return yield* invalidSession();
|
|
369
413
|
return record;
|
|
370
414
|
}))),
|
|
371
415
|
rotate: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
@@ -452,8 +496,8 @@ const makeSessionKernel = (operations) => {
|
|
|
452
496
|
}))) };
|
|
453
497
|
return {
|
|
454
498
|
statefulSessionPersistence: {
|
|
455
|
-
establish: (input, prepare) => persistence.establish(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
456
|
-
verify: (input) => persistence.verify(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
499
|
+
establish: (input, prepare) => persistence.establish(input, prepare).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
500
|
+
verify: (input) => persistence.verify(input).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
457
501
|
rotate: (input, prepare) => persistence.rotate(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
458
502
|
revokeDigest: (digest, prepare) => persistence.revokeDigest(digest, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
459
503
|
revoke: (input, prepare) => persistence.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
@@ -472,16 +516,38 @@ const makeSessionKernel = (operations) => {
|
|
|
472
516
|
sessionId: column(mapping.tombstone.table, mapping.tombstone.sessionId),
|
|
473
517
|
absoluteExpiresAt: column(mapping.tombstone.table, mapping.tombstone.absoluteExpiresAt)
|
|
474
518
|
};
|
|
519
|
+
const root = yield* NativeDatabase;
|
|
520
|
+
const tables = yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(Effect.mapError(unavailable));
|
|
475
521
|
const service = {
|
|
476
|
-
verify: (session, _now) => safeTransaction(
|
|
477
|
-
const
|
|
522
|
+
verify: (session, _now) => safeTransaction(withVerificationSavepoint(Effect.gen(function* () {
|
|
523
|
+
const sql = root.$client.withoutTransforms();
|
|
478
524
|
const nativeSubjectId = yield* mapping.subjectId.toNative(session.subjectId);
|
|
479
525
|
const nativeSessionId = yield* mapping.sessionId.toNative(session.sessionId);
|
|
480
|
-
const
|
|
481
|
-
|
|
526
|
+
const readNow = yield* freshNow;
|
|
527
|
+
const { subjectTable, rows } = yield* sqlMapping(() => {
|
|
528
|
+
const subjectTable = tables(mapping.subject.table).as("auth_subject");
|
|
529
|
+
const revoked = tables(mapping.tombstone.table).as("auth_revocation");
|
|
530
|
+
return {
|
|
531
|
+
subjectTable,
|
|
532
|
+
rows: sql`
|
|
533
|
+
select ${subjectTable.fields("s_")},
|
|
534
|
+
case when exists (
|
|
535
|
+
select 1 from ${revoked.name}
|
|
536
|
+
where ${revoked.column(mapping.tombstone.subjectId)} = ${revoked.value(mapping.tombstone.subjectId, nativeSubjectId)}
|
|
537
|
+
and ${revoked.column(mapping.tombstone.sessionId)} = ${revoked.value(mapping.tombstone.sessionId, nativeSessionId)}
|
|
538
|
+
and ${revoked.column(mapping.tombstone.absoluteExpiresAt)} > ${revoked.value(mapping.tombstone.absoluteExpiresAt, mapping.tombstone.encodeInstant(readNow))}
|
|
539
|
+
) then 1 else 0 end as auth_revoked
|
|
540
|
+
from ${subjectTable.name}
|
|
541
|
+
where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeSubjectId)}
|
|
542
|
+
limit 1`
|
|
543
|
+
};
|
|
544
|
+
});
|
|
545
|
+
const result = (yield* rows)[0];
|
|
546
|
+
if (result === void 0) return yield* invalidSession();
|
|
547
|
+
const row = yield* sqlMapping(() => subjectTable.decode(result, "s_"));
|
|
548
|
+
const revoked = yield* decodeRevocationFlag(result.auth_revoked);
|
|
482
549
|
const authoritativeNow = yield* freshNow;
|
|
483
|
-
if (DateTime.toEpochMillis(authoritativeNow) >= DateTime.toEpochMillis(session.expiresAt) || DateTime.toEpochMillis(authoritativeNow) >= DateTime.toEpochMillis(session.absoluteExpiresAt)) return yield* invalidSession();
|
|
484
|
-
if ((yield* read.select({ sessionId: tombstone.sessionId }).from(mapping.tombstone.table).where(and(eq(tombstone.subjectId, nativeSubjectId), eq(tombstone.sessionId, nativeSessionId), gt(tombstone.absoluteExpiresAt, mapping.tombstone.encodeInstant(authoritativeNow)))).limit(1)).length > 0) return yield* invalidSession();
|
|
550
|
+
if (!mapping.subjectId.equals(nativeSubjectId, row[mapping.subject.id]) || !mapping.subject.isActiveStatus(row[mapping.subject.status]) || row[mapping.subject.securityRevision] !== session.securityRevision || revoked === 1 || DateTime.toEpochMillis(authoritativeNow) >= DateTime.toEpochMillis(session.expiresAt) || DateTime.toEpochMillis(authoritativeNow) >= DateTime.toEpochMillis(session.absoluteExpiresAt)) return yield* invalidSession();
|
|
485
551
|
}))),
|
|
486
552
|
revoke: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
487
553
|
const transaction = yield* CurrentSessionSql;
|
|
@@ -517,7 +583,7 @@ const makeSessionKernel = (operations) => {
|
|
|
517
583
|
}))
|
|
518
584
|
};
|
|
519
585
|
return {
|
|
520
|
-
verify: (session, now) => service.verify(session, now).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
586
|
+
verify: (session, now) => service.verify(session, now).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
521
587
|
revoke: (input, prepare) => service.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
522
588
|
revokeAll: (input, prepare) => service.revokeAll(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks))
|
|
523
589
|
};
|