@yielded/auth-persistence 0.1.0-beta.23 → 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 +12 -6
- package/dist/Adapter.mjs +8 -3
- 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/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 -47
- package/dist/internal/models/password-model.mjs +0 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +4 -0
- package/dist/internal/native-sql-table.mjs +7 -0
- package/dist/internal/native-sql-table.mjs.map +1 -1
- 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-kernel.d.mts +5 -5
- package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
- package/dist/internal/password-credentials.mjs +121 -0
- package/dist/internal/password-credentials.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +8 -24
- package/dist/internal/password-kernel.mjs +4 -42
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +1 -1
- package/dist/internal/persistence.mjs +2 -2
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/query-operations.d.mts +2 -0
- package/dist/internal/session-kernel.d.mts +2 -2
- package/dist/internal/session-kernel.mjs +74 -18
- 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-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 +1 -37
- 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 -56
- package/dist/internal/storage-tables.mjs +0 -17
- 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-kernel.mjs +3 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +89 -5
- package/src/OAuthPersistence.ts +427 -0
- package/src/index.ts +3 -0
- package/src/internal/configuration.ts +1 -3
- 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 -56
- package/src/internal/native-sql-table.ts +14 -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 +5 -98
- package/src/internal/persistence.ts +1 -1
- package/src/internal/query-operations.ts +2 -0
- package/src/internal/session-kernel.ts +143 -55
- package/src/internal/sql-oauth-connected-model.ts +145 -0
- package/src/internal/sql-oauth-model.ts +263 -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 -37
- package/src/internal/storage-plans.ts +218 -0
- package/src/internal/storage-tables.ts +0 -20
- package/src/internal/storage-validation.ts +58 -0
- package/src/internal/transaction-kernel.ts +6 -2
- package/dist/internal/password-attempts.mjs +0 -197
- package/dist/internal/password-attempts.mjs.map +0 -1
- package/src/internal/password-attempts.ts +0 -466
|
@@ -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 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\", \"passwordAttempts\", \"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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"}
|
|
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: {
|
|
@@ -6,7 +6,7 @@ import { NativeSqlTables } from "./native-sql-table.mjs";
|
|
|
6
6
|
import { Context, DateTime, Effect } from "effect";
|
|
7
7
|
import { SubjectId } from "@yielded/auth/Schema";
|
|
8
8
|
import { LifecycleHooks } from "@yielded/auth/Hooks";
|
|
9
|
-
import {
|
|
9
|
+
import { AuthenticationCapture, AuthenticationEvidence, PendingAuthentication, PendingAuthenticationInvalid, PendingConsumption, PrepareSessionCommit, SessionRepository, SessionStepUpPersistence, SessionUnavailable, SignedSessionValidity, StaleAuthentication, StatefulSessionPersistence } from "@yielded/auth/Sessions";
|
|
10
10
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
11
11
|
import * as SqlError$1 from "effect/sql/SqlError";
|
|
12
12
|
//#region src/internal/session-kernel.d.ts
|
|
@@ -40,7 +40,7 @@ export interface SessionSqlOptions {
|
|
|
40
40
|
}
|
|
41
41
|
export declare const makeSessionKernel: (operations: QueryOperations, nativeTables?: (client: SqlClient, database: object) => NativeSqlTables) => {
|
|
42
42
|
makeSqlAuthenticationAuthority: <Claims>(mapping: AuthenticationAuthorityMapping<Claims, any, any, any, any, any>, options: SessionSqlOptions) => Effect.Effect<{
|
|
43
|
-
readonly capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) => Effect.Effect<
|
|
43
|
+
readonly capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) => Effect.Effect<AuthenticationCapture, StaleAuthentication | SessionUnavailable>;
|
|
44
44
|
readonly requirements: (evidence: AuthenticationEvidence) => Effect.Effect<import("@yielded/auth/Sessions").AuthenticationRequirement, StaleAuthentication | SessionUnavailable>;
|
|
45
45
|
readonly approve: <A>(input: {
|
|
46
46
|
readonly evidence: AuthenticationEvidence;
|
|
@@ -3,11 +3,20 @@ import { makeNativeSqlTables, sqlMapping } from "./native-sql-table.mjs";
|
|
|
3
3
|
import { NativeDatabase } from "./transaction-kernel.mjs";
|
|
4
4
|
import { makeAuthoritySnapshot } from "./authority-snapshot.mjs";
|
|
5
5
|
import { decodeStepUpIntent, encodeStepUpIntent, sameStepUpRevision, stepUpIntentLive, stepUpRotationMatches, validateStepUpPlan } from "./step-up-state.mjs";
|
|
6
|
+
import { canJoinTextColumns } from "./storage-validation.mjs";
|
|
6
7
|
import { Cause, Context, DateTime, Effect, Option, Schema } from "effect";
|
|
7
8
|
import { CurrentCommitJournal, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
|
|
8
|
-
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";
|
|
9
10
|
import { reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
10
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
|
+
});
|
|
11
20
|
/** Native SQL authority for the current transaction or savepoint. */
|
|
12
21
|
var CurrentSessionSql = class extends Context.Service()("effect-auth/drizzle/CurrentSessionSql") {};
|
|
13
22
|
const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlTables(client)) => {
|
|
@@ -191,7 +200,10 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
191
200
|
const tables = yield* sqlMapping(() => nativeTables(sqlClient, root)).pipe(Effect.mapError(unavailable));
|
|
192
201
|
const readSnapshot = makeAuthoritySnapshot(tables, mapping);
|
|
193
202
|
const service = {
|
|
194
|
-
capture: (subjectId, credentialIds) => safeTransaction(readSnapshot(subjectId, credentialIds).pipe(Effect.
|
|
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
|
+
}))))),
|
|
195
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))),
|
|
196
208
|
approve: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
197
209
|
const journal = yield* CurrentCommitJournal;
|
|
@@ -323,6 +335,18 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
323
335
|
const tables = yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(Effect.mapError(unavailable));
|
|
324
336
|
const readSnapshot = makeAuthoritySnapshot(tables, mapping);
|
|
325
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
|
+
}));
|
|
326
350
|
const persistence = {
|
|
327
351
|
establish: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
328
352
|
const transaction = yield* CurrentSessionSql;
|
|
@@ -330,8 +354,9 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
330
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));
|
|
331
355
|
let flowInsert;
|
|
332
356
|
let pendingExpiresAt;
|
|
333
|
-
if (input.pending === void 0)
|
|
334
|
-
|
|
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 {
|
|
335
360
|
if (mapping.pending === void 0) return yield* invalidPending();
|
|
336
361
|
const pending = yield* validatePending(mapping, input.pending);
|
|
337
362
|
if (!preservesRevision(input.evidence, pending.evidence)) return yield* invalidPending();
|
|
@@ -356,7 +381,7 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
356
381
|
};
|
|
357
382
|
const receipt = prepare(record, journal);
|
|
358
383
|
if (flowInsert !== void 0) yield* transaction.insert(mapping.flow.table).values(flowInsert);
|
|
359
|
-
else yield* consumePending({
|
|
384
|
+
else if (input.pending !== void 0) yield* consumePending({
|
|
360
385
|
...mapping,
|
|
361
386
|
pending: mapping.pending
|
|
362
387
|
}, input.pending, input.session.absoluteExpiresAt);
|
|
@@ -368,14 +393,23 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
368
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();
|
|
369
394
|
return receipt;
|
|
370
395
|
}).pipe(normalizeMutation(mapping))),
|
|
371
|
-
verify: (input) => safeTransaction(
|
|
372
|
-
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];
|
|
373
406
|
if (row === void 0) return yield* invalidSession();
|
|
374
|
-
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);
|
|
375
410
|
const nativeSubjectId = yield* mapping.subjectId.toNative(record.subjectId);
|
|
376
|
-
const subject = (yield* readSubject(mapping, nativeSubjectId, false))[0];
|
|
377
411
|
const now = yield* freshNow;
|
|
378
|
-
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();
|
|
379
413
|
return record;
|
|
380
414
|
}))),
|
|
381
415
|
rotate: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
@@ -463,7 +497,7 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
463
497
|
return {
|
|
464
498
|
statefulSessionPersistence: {
|
|
465
499
|
establish: (input, prepare) => persistence.establish(input, prepare).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
466
|
-
verify: (input) => persistence.verify(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
500
|
+
verify: (input) => persistence.verify(input).pipe(Effect.provideService(NativeDatabase, root), Effect.provideService(LifecycleHooks, hooks)),
|
|
467
501
|
rotate: (input, prepare) => persistence.rotate(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
468
502
|
revokeDigest: (digest, prepare) => persistence.revokeDigest(digest, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
469
503
|
revoke: (input, prepare) => persistence.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
@@ -482,16 +516,38 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
482
516
|
sessionId: column(mapping.tombstone.table, mapping.tombstone.sessionId),
|
|
483
517
|
absoluteExpiresAt: column(mapping.tombstone.table, mapping.tombstone.absoluteExpiresAt)
|
|
484
518
|
};
|
|
519
|
+
const root = yield* NativeDatabase;
|
|
520
|
+
const tables = yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(Effect.mapError(unavailable));
|
|
485
521
|
const service = {
|
|
486
|
-
verify: (session, _now) => safeTransaction(
|
|
487
|
-
const
|
|
522
|
+
verify: (session, _now) => safeTransaction(withVerificationSavepoint(Effect.gen(function* () {
|
|
523
|
+
const sql = root.$client.withoutTransforms();
|
|
488
524
|
const nativeSubjectId = yield* mapping.subjectId.toNative(session.subjectId);
|
|
489
525
|
const nativeSessionId = yield* mapping.sessionId.toNative(session.sessionId);
|
|
490
|
-
const
|
|
491
|
-
|
|
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);
|
|
492
549
|
const authoritativeNow = yield* freshNow;
|
|
493
|
-
if (DateTime.toEpochMillis(authoritativeNow) >= DateTime.toEpochMillis(session.expiresAt) || DateTime.toEpochMillis(authoritativeNow) >= DateTime.toEpochMillis(session.absoluteExpiresAt)) return yield* invalidSession();
|
|
494
|
-
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();
|
|
495
551
|
}))),
|
|
496
552
|
revoke: (input, prepare) => owned(database, options, mapping.isConstraintConflict, Effect.gen(function* () {
|
|
497
553
|
const transaction = yield* CurrentSessionSql;
|
|
@@ -527,7 +583,7 @@ const makeSessionKernel = (operations, nativeTables = (client) => makeNativeSqlT
|
|
|
527
583
|
}))
|
|
528
584
|
};
|
|
529
585
|
return {
|
|
530
|
-
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)),
|
|
531
587
|
revoke: (input, prepare) => service.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
|
|
532
588
|
revokeAll: (input, prepare) => service.revokeAll(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks))
|
|
533
589
|
};
|