@yielded/auth-persistence 0.1.0-beta.30 → 0.1.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -6
- package/dist/Adapter.d.mts +1 -1
- package/dist/internal/configuration.d.mts +18 -8
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/models/password-model.d.mts +2 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/passkey-managed.mjs +4 -2
- package/dist/internal/passkey-managed.mjs.map +1 -1
- package/dist/internal/password-native.mjs +14 -0
- package/dist/internal/password-native.mjs.map +1 -1
- package/dist/internal/password-registration-native.d.mts +1 -1
- package/dist/internal/persistence.d.mts +3 -0
- package/dist/internal/persistence.mjs +4 -6
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/registration.mjs +17 -8
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +2 -1
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.mjs +2 -1
- package/dist/internal/session-native-login.mjs.map +1 -1
- package/dist/internal/session-native-pending-read.mjs +1 -1
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +21 -8
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.mjs +2 -1
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.mjs +4 -2
- package/dist/internal/session-native-step-up.mjs.map +1 -1
- package/dist/internal/sqlite-policy-guard.mjs +23 -0
- package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
- package/dist/internal/storage-mapping.d.mts +9 -5
- package/dist/internal/storage-mapping.mjs +16 -4
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/package.json +1 -1
- package/src/internal/configuration.ts +26 -8
- package/src/internal/models/password-model.ts +2 -1
- package/src/internal/passkey-managed.ts +8 -2
- package/src/internal/password-native.ts +28 -2
- package/src/internal/persistence.ts +174 -164
- package/src/internal/registration.ts +53 -40
- package/src/internal/session-native-authority.ts +3 -1
- package/src/internal/session-native-login.ts +3 -1
- package/src/internal/session-native-pending-read.ts +1 -1
- package/src/internal/session-native-state.ts +29 -10
- package/src/internal/session-native-stateful.ts +3 -1
- package/src/internal/session-native-step-up.ts +5 -2
- package/src/internal/sqlite-policy-guard.ts +34 -0
- package/src/internal/storage-mapping.ts +40 -4
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./mapping-error.mjs";
|
|
2
2
|
import { AnyPasswordRegistrationMapping } from "./models/password-model.mjs";
|
|
3
3
|
import { NativeSqlTables } from "./native-sql-table.mjs";
|
|
4
|
-
import { PasswordRegistrationAuthority } from "./registration-contract.mjs";
|
|
5
4
|
import "./sql-commit.mjs";
|
|
5
|
+
import { PasswordRegistrationAuthority } from "./registration-contract.mjs";
|
|
6
6
|
import { Cause, Crypto, Effect, Schema } from "effect";
|
|
7
7
|
import { Fragment } from "effect/sql/Statement";
|
|
8
8
|
import { CurrentCommitJournal, LifecycleHooks } from "@yielded/auth/Hooks";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StorageTable } from "./storage-tables.mjs";
|
|
2
2
|
import { PersistenceApi, PersistenceConfigurationError } from "./configuration.mjs";
|
|
3
3
|
import { NativeSqlTables } from "./native-sql-table.mjs";
|
|
4
|
+
import { SqlBatchCommit } from "./sql-commit.mjs";
|
|
4
5
|
import { Effect } from "effect";
|
|
5
6
|
import { SqlClient } from "effect/sql";
|
|
6
7
|
//#region src/internal/persistence.d.ts
|
|
@@ -9,6 +10,8 @@ export interface Backend<T extends object, R, Database extends object = object>
|
|
|
9
10
|
readonly describe: (table: T) => StorageTable;
|
|
10
11
|
readonly acquire: Effect.Effect<Database, PersistenceConfigurationError, R | SqlClient.SqlClient>;
|
|
11
12
|
readonly nativeTables: (database: Database) => NativeSqlTables;
|
|
13
|
+
/** Fixed atomic batches replace interactive transactions on backends such as D1. */
|
|
14
|
+
readonly batch?: (database: Database) => NonNullable<SqlBatchCommit["Service"]>;
|
|
12
15
|
}
|
|
13
16
|
export declare const createPersistence: <T extends object, R, Database extends object = object>(backend: Backend<T, R, Database>) => PersistenceApi<T, R>;
|
|
14
17
|
//#endregion
|
|
@@ -122,11 +122,9 @@ const createPersistence = (backend) => {
|
|
|
122
122
|
decodeInstantSync
|
|
123
123
|
});
|
|
124
124
|
};
|
|
125
|
-
const services = Layer.effectContext(Effect.gen(function* () {
|
|
125
|
+
const services = Layer.effectContext(Effect.flatMap(backend.acquire, (database) => Effect.gen(function* () {
|
|
126
126
|
const storage = yield* ConfigKey;
|
|
127
|
-
const
|
|
128
|
-
const database = yield* backend.acquire;
|
|
129
|
-
const dialect = client.onDialectOrElse({
|
|
127
|
+
const dialect = (yield* SqlClient.SqlClient).onDialectOrElse({
|
|
130
128
|
pg: () => "pg",
|
|
131
129
|
sqlite: () => "sqlite",
|
|
132
130
|
orElse: () => void 0
|
|
@@ -152,8 +150,8 @@ const createPersistence = (backend) => {
|
|
|
152
150
|
required: [[storage.subjects.id]]
|
|
153
151
|
});
|
|
154
152
|
yield* validateStorageBatch(dialect, validations);
|
|
155
|
-
const mappings = yield* makeMappings(storage);
|
|
156
153
|
const nativeTables = backend.nativeTables(database);
|
|
154
|
+
const mappings = yield* makeMappings(storage, nativeTables);
|
|
157
155
|
const sessionMapping = mappings.sessions(auth.claims, auth.sessions.moduleId);
|
|
158
156
|
const pendingMapping = mappings.pending(auth.claims, auth.sessions.moduleId);
|
|
159
157
|
const sessionServices = yield* makeNativeStatefulSessionServices(nativeTables, sessionMapping);
|
|
@@ -200,7 +198,7 @@ const createPersistence = (backend) => {
|
|
|
200
198
|
context = Context.merge(context, yield* services);
|
|
201
199
|
}
|
|
202
200
|
return context;
|
|
203
|
-
}).pipe(Effect.provideService(SqlBatchCommit,
|
|
201
|
+
}).pipe(Effect.provideService(SqlBatchCommit, backend.batch?.(database)))).pipe(withStorageValidation, Effect.mapError((error) => Schema.is(PersistenceConfigurationError)(error) ? error : configError("Cannot acquire SQL persistence")))).pipe(Layer.provide(hooksLayer));
|
|
204
202
|
return {
|
|
205
203
|
Config,
|
|
206
204
|
Provisioning: ProvisioningKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.mjs","names":[],"sources":["../../src/internal/persistence.ts"],"sourcesContent":["import { EmailAddressPersistence, EmailUnavailable } from \"@yielded/auth/Email\";\nimport type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { hasCommitScope } from \"@yielded/auth/Hooks\";\nimport {\n PasskeyCredentials,\n PasskeyManagementPersistence,\n PasskeyPersistence,\n PasskeyUnavailable,\n} from \"@yielded/auth/Passkey\";\nimport { PasswordPersistence, PasswordUnavailable } from \"@yielded/auth/Password\";\nimport { hooksLayer } from \"@yielded/auth/Persistence\";\nimport { 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} from \"./configuration\";\nimport { makeNativeEmailAddressServices } from \"./email-native\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeManagedPasskeys } from \"./passkey-managed\";\nimport { makeNativePasswordServices } from \"./password-native\";\nimport { makeComposedPhoneTargets } from \"./phone-composed\";\nimport { makeNativeProofServices } from \"./proof-native\";\nimport { makeRegistrationAuthority } from \"./registration\";\nimport type { PasswordRegistrationAuthority } from \"./registration-contract\";\nimport { makeNativeAuthenticationAuthorityServices } from \"./session-native-authority\";\nimport { makeNativeSessionCleanupServices } from \"./session-native-cleanup\";\nimport { makeNativePendingAuthenticationServices } from \"./session-native-login\";\nimport { makeNativeStatefulSessionServices } from \"./session-native-stateful\";\nimport { makeNativeSessionStepUpServices } from \"./session-native-step-up\";\nimport { SqlBatchCommit } from \"./sql-commit\";\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\";\n\nexport interface Backend<T extends object, R, Database extends object = object> {\n readonly makeTable: (definition: StorageTable) => T;\n readonly describe: (table: T) => StorageTable;\n readonly acquire: Effect.Effect<Database, PersistenceConfigurationError, R | SqlClient.SqlClient>;\n readonly nativeTables: (database: Database) => NativeSqlTables;\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, Database extends object = object>(\n backend: Backend<T, R, Database>,\n): PersistenceApi<T, R> => {\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\", \"pending\"];\n\n if (password) roles.push(\"passwords\");\n if (email) roles.push(\"emailCredentials\");\n if (passkeys.length > 0) roles.push(\"passkeyCredentials\", \"passkeyFlows\");\n if (proofs) roles.push(\"proofs\");\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 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 nativeTables = backend.nativeTables(database);\n const sessionMapping = mappings.sessions(auth.claims, auth.sessions.moduleId);\n const pendingMapping = mappings.pending(auth.claims, auth.sessions.moduleId);\n\n const sessionServices = yield* makeNativeStatefulSessionServices(\n nativeTables,\n sessionMapping,\n );\n\n const { authenticationAuthority } = yield* makeNativeAuthenticationAuthorityServices(\n nativeTables,\n sessionMapping,\n );\n\n const { pendingAuthentication } = yield* makeNativePendingAuthenticationServices(\n nativeTables,\n pendingMapping,\n );\n\n const { sessionStepUpPersistence } = yield* makeNativeSessionStepUpServices(nativeTables, {\n ...pendingMapping,\n source: {\n kind: \"Stateful\",\n session: sessionMapping.session,\n sessionId: sessionMapping.sessionId,\n constraints: { sessionDigest: \"unique(session.digest)\" },\n },\n });\n\n const { sessionCleanup } = yield* makeNativeSessionCleanupServices(\n nativeTables,\n pendingMapping,\n );\n\n let context: Context.Context<never> = Context.make(\n AuthenticationAuthority,\n authenticationAuthority,\n ).pipe(\n Context.add(\n auth.sessions.StatefulSessionPersistence,\n sessionServices.statefulSessionPersistence,\n ),\n Context.add(auth.sessions.SessionRepository, sessionServices.sessionRepository),\n Context.add(auth.sessions.PendingAuthentication, pendingAuthentication),\n Context.add(auth.sessions.SessionStepUpPersistence, sessionStepUpPersistence),\n Context.add(auth.sessions.SessionCleanup, sessionCleanup),\n );\n\n const proofMapping = proofs ? mappings.proofs() : undefined;\n\n if (proofMapping !== undefined) {\n const services = yield* makeNativeProofServices(\n backend.nativeTables(database),\n proofMapping,\n );\n\n context = Context.add(context, ProofPersistence, services.proofPersistence);\n }\n\n if (password) {\n const mapping = mappings.passwords();\n\n const services = yield* makeNativePasswordServices(\n backend.nativeTables(database),\n mapping,\n proofMapping,\n );\n\n context = Context.add(context, PasswordPersistence, services.passwordPersistence);\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 backend.nativeTables(database),\n mappings.passwords(),\n ProvisioningKey,\n name,\n );\n\n context = Context.add(context, key, registration);\n }\n if (email)\n context = Context.add(\n context,\n EmailAddressPersistence,\n (yield* makeNativeEmailAddressServices(\n backend.nativeTables(database),\n mappings.emails(),\n proofMapping,\n )).emailAddressPersistence,\n );\n\n if (phone)\n context = Context.add(\n context,\n PhoneSignInTargets,\n yield* makeComposedPhoneTargets(\n backend.nativeTables(database),\n storage,\n mappings.proofs(),\n features.flatMap((feature) => (feature?.kind === \"phone\" ? [feature.moduleId] : [])),\n ),\n );\n\n if (passkeys.length > 0) {\n // Auth capability metadata determines whether PasskeyConfig is required.\n const services = makeManagedPasskeys(\n {\n storage,\n namespace: auth.namespace,\n dialect,\n features: passkeys,\n passwordModules: features.flatMap((feature) =>\n feature?.kind === \"password\" ? [feature.moduleId] : [],\n ),\n },\n backend.nativeTables(database),\n ) as Effect.Effect<\n Context.Context<never>,\n PersistenceConfigurationError,\n PasskeyRequirement<A> | Crypto.Crypto | LifecycleHooks | SqlClient.SqlClient\n >;\n\n context = Context.merge(context, yield* services);\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 Effect.provideService(SqlBatchCommit, undefined),\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 const layer = Layer.effectContext(\n Effect.gen(function* () {\n const scope = yield* Effect.scope;\n const captured = yield* Effect.context<Layer.Services<typeof services>>();\n const client = Context.get(captured, SqlClient.SqlClient);\n\n // Construction uses the layer's exact environment and lifetime, never the\n // first operation's SQL transaction, service overrides, or request scope.\n // Cache the full exit: concurrent callers share failures/interruption too,\n // and cannot retry partially completed setup within this acquired layer.\n const initialize = yield* Effect.cached(\n Layer.buildWithScope(services, scope).pipe(Effect.setContext(captured)),\n );\n\n const service = Effect.fnUntraced(function* <I, S, Failure>(\n key: Context.Key<I, S>,\n unavailable: () => Failure,\n standalone = true,\n ): Effect.fn.Return<S, Failure> {\n // Reject unsupported ownership before setup can perform any writes.\n // Session/authority reads retain their existing ambient-read semantics.\n if (standalone) {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* requireStandalone(unavailable, client.transactionService);\n }\n const acquired = yield* initialize.pipe(Effect.mapError(unavailable));\n const value = Context.getOrUndefined(acquired, key);\n\n return value === undefined ? yield* Effect.fail(unavailable()) : value;\n });\n\n const sessionUnavailable = () => SessionUnavailable.make({});\n const authority = service(AuthenticationAuthority, sessionUnavailable, false);\n const authorityMutation = service(AuthenticationAuthority, sessionUnavailable);\n\n const sessions = service(\n auth.sessions.StatefulSessionPersistence,\n sessionUnavailable,\n false,\n );\n\n const sessionMutations = service(\n auth.sessions.StatefulSessionPersistence,\n sessionUnavailable,\n );\n\n const repository = service(auth.sessions.SessionRepository, sessionUnavailable, false);\n const pendingRead = service(auth.sessions.PendingAuthentication, sessionUnavailable, false);\n const pendingMutation = service(auth.sessions.PendingAuthentication, sessionUnavailable);\n\n const stepUpRead = service(\n auth.sessions.SessionStepUpPersistence,\n sessionUnavailable,\n false,\n );\n\n const stepUpMutation = service(auth.sessions.SessionStepUpPersistence, sessionUnavailable);\n const cleanup = service(auth.sessions.SessionCleanup, sessionUnavailable);\n\n let context: Context.Context<never> = Context.make(AuthenticationAuthority, {\n capture: (subjectId, credentialIds) =>\n Effect.flatMap(authority, (s) => s.capture(subjectId, credentialIds)),\n requirements: (evidence) => Effect.flatMap(authority, (s) => s.requirements(evidence)),\n approve: (input, prepare) =>\n Effect.flatMap(authorityMutation, (s) => s.approve(input, prepare)),\n }).pipe(\n Context.add(auth.sessions.StatefulSessionPersistence, {\n establish: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.establish(input, prepare)),\n verify: (input) => Effect.flatMap(sessions, (s) => s.verify(input)),\n rotate: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.rotate(input, prepare)),\n revokeDigest: (digest, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.revokeDigest(digest, prepare)),\n revoke: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.revoke(input, prepare)),\n revokeAll: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.revokeAll(input, prepare)),\n }),\n Context.add(auth.sessions.SessionRepository, {\n list: (input) => Effect.flatMap(repository, (s) => s.list(input)),\n }),\n Context.add(auth.sessions.PendingAuthentication, {\n create: (input, now, prepare) =>\n Effect.flatMap(pendingMutation, (s) => s.create(input, now, prepare)),\n read: (input) => Effect.flatMap(pendingRead, (s) => s.read(input)),\n reject: (input, prepare) =>\n Effect.flatMap(pendingMutation, (s) => s.reject(input, prepare)),\n }),\n Context.add(auth.sessions.SessionStepUpPersistence, {\n create: (input, now, prepare) =>\n Effect.flatMap(stepUpMutation, (s) => s.create(input, now, prepare)),\n read: (input) => Effect.flatMap(stepUpRead, (s) => s.read(input)),\n reject: (input, prepare) =>\n Effect.flatMap(stepUpMutation, (s) => s.reject(input, prepare)),\n complete: (plan, prepare) =>\n Effect.flatMap(stepUpMutation, (s) => s.complete(plan, prepare)),\n }),\n Context.add(auth.sessions.SessionCleanup, {\n cleanup: (input) => Effect.flatMap(cleanup, (s) => s.cleanup(input)),\n }),\n );\n\n if (proofs) {\n const persistence = service(ProofPersistence, () => ProofUnavailable.make({}));\n\n context = Context.add(context, ProofPersistence, {\n issue: (input, prepare) => Effect.flatMap(persistence, (s) => s.issue(input, prepare)),\n redeem: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.redeem(input, prepare)),\n cancel: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.cancel(input, prepare)),\n cleanup: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.cleanup(input, prepare)),\n });\n }\n if (password) {\n const persistence = service(PasswordPersistence, () => PasswordUnavailable.make({}));\n\n context = Context.add(context, PasswordPersistence, {\n findCredential: (input) => Effect.flatMap(persistence, (s) => s.findCredential(input)),\n rehashIfCurrent: (input) =>\n Effect.flatMap(persistence, (s) => s.rehashIfCurrent(input)),\n readForSubject: (input) => Effect.flatMap(persistence, (s) => s.readForSubject(input)),\n recoveryTarget: (input) => Effect.flatMap(persistence, (s) => s.recoveryTarget(input)),\n addIfAbsent: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.addIfAbsent(input, prepare)),\n replaceIfCurrent: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.replaceIfCurrent(input, prepare)),\n resetWithProof: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.resetWithProof(input, prepare)),\n });\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 // The heterogeneous strategy table erases the registration input type;\n // its decoded value is forwarded unchanged to the same authority key.\n const registration = service(\n key as Context.Key<unknown, PasswordRegistrationAuthority<unknown>>,\n () => PasswordUnavailable.make({}),\n );\n\n context = Context.add(context, key, {\n register: (input, prepare) =>\n Effect.flatMap(registration, (s) => s.register(input, prepare)),\n } satisfies PasswordRegistrationAuthority<unknown>);\n }\n if (email) {\n const persistence = service(EmailAddressPersistence, () => EmailUnavailable.make({}));\n\n context = Context.add(context, EmailAddressPersistence, {\n target: (input) => Effect.flatMap(persistence, (s) => s.target(input)),\n verifyWithProof: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.verifyWithProof(input, prepare)),\n changeWithProof: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.changeWithProof(input, prepare)),\n });\n }\n if (phone) {\n const unavailable = () => PhoneOtpUnavailable.make({});\n const targets = service(PhoneSignInTargets, unavailable);\n\n context = context.pipe(\n Context.add(PhoneSignInTargets, {\n lookup: (input) => Effect.flatMap(targets, (s) => s.lookup(input)),\n }),\n );\n }\n if (passkeys.length > 0) {\n const unavailable = () => PasskeyUnavailable.make({});\n const credentials = service(PasskeyCredentials, unavailable);\n const persistence = service(PasskeyPersistence, unavailable);\n\n context = context.pipe(\n Context.add(PasskeyCredentials, {\n lookup: (input) => Effect.flatMap(credentials, (s) => s.lookup(input)),\n listForSubject: (input) =>\n Effect.flatMap(credentials, (s) => s.listForSubject(input)),\n }),\n Context.add(PasskeyPersistence, {\n issue: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.issue(input, prepare)),\n context: (input) => Effect.flatMap(persistence, (s) => s.context(input)),\n consume: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.consume(input, prepare)),\n cleanup: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.cleanup(input, prepare)),\n }),\n );\n if (passkeys.some((feature) => feature.management)) {\n const manager = service(PasskeyManagementPersistence, unavailable);\n\n context = context.pipe(\n Context.add(PasskeyManagementPersistence, {\n list: (input) => Effect.flatMap(manager, (s) => s.list(input)),\n completeEnrollment: (input, prepare) =>\n Effect.flatMap(manager, (s) => s.completeEnrollment(input, prepare)),\n inspectRemove: (input) => Effect.flatMap(manager, (s) => s.inspectRemove(input)),\n remove: (input, prepare) =>\n Effect.flatMap(manager, (s) => s.remove(input, prepare)),\n rename: (input, prepare) =>\n Effect.flatMap(manager, (s) => s.rename(input, prepare)),\n }),\n );\n }\n }\n\n // Match the same capability-selected service keys as the acquired graph.\n return context as Context.Context<Ports<C, Id, A>>;\n }),\n );\n\n return {\n Config,\n Provisioning: ProvisioningKey,\n layer,\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 type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { hasCommitScope } from \"@yielded/auth/Hooks\";\nimport {\n PasskeyCredentials,\n PasskeyManagementPersistence,\n PasskeyPersistence,\n PasskeyUnavailable,\n} from \"@yielded/auth/Passkey\";\nimport { PasswordPersistence, PasswordUnavailable } from \"@yielded/auth/Password\";\nimport { hooksLayer } from \"@yielded/auth/Persistence\";\nimport { 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} from \"./configuration\";\nimport { makeNativeEmailAddressServices } from \"./email-native\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeManagedPasskeys } from \"./passkey-managed\";\nimport { makeNativePasswordServices } from \"./password-native\";\nimport { makeComposedPhoneTargets } from \"./phone-composed\";\nimport { makeNativeProofServices } from \"./proof-native\";\nimport { makeRegistrationAuthority } from \"./registration\";\nimport type { PasswordRegistrationAuthority } from \"./registration-contract\";\nimport { makeNativeAuthenticationAuthorityServices } from \"./session-native-authority\";\nimport { makeNativeSessionCleanupServices } from \"./session-native-cleanup\";\nimport { makeNativePendingAuthenticationServices } from \"./session-native-login\";\nimport { makeNativeStatefulSessionServices } from \"./session-native-stateful\";\nimport { makeNativeSessionStepUpServices } from \"./session-native-step-up\";\nimport { SqlBatchCommit } from \"./sql-commit\";\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\";\n\nexport interface Backend<T extends object, R, Database extends object = object> {\n readonly makeTable: (definition: StorageTable) => T;\n readonly describe: (table: T) => StorageTable;\n readonly acquire: Effect.Effect<Database, PersistenceConfigurationError, R | SqlClient.SqlClient>;\n readonly nativeTables: (database: Database) => NativeSqlTables;\n /** Fixed atomic batches replace interactive transactions on backends such as D1. */\n readonly batch?: (database: Database) => NonNullable<SqlBatchCommit[\"Service\"]>;\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, Database extends object = object>(\n backend: Backend<T, R, Database>,\n): PersistenceApi<T, R> => {\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\", \"pending\"];\n\n if (password) roles.push(\"passwords\");\n if (email) roles.push(\"emailCredentials\");\n if (passkeys.length > 0) roles.push(\"passkeyCredentials\", \"passkeyFlows\");\n if (proofs) roles.push(\"proofs\");\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.flatMap(backend.acquire, (database) =>\n Effect.gen(function* () {\n const storage = yield* ConfigKey;\n const client = yield* SqlClient.SqlClient;\n\n const dialect = client.onDialectOrElse({\n pg: () => \"pg\" as const,\n sqlite: () => \"sqlite\" as const,\n orElse: () => undefined,\n });\n\n if (dialect === undefined)\n return yield* 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 nativeTables = backend.nativeTables(database);\n const mappings = yield* makeMappings(storage, nativeTables);\n const sessionMapping = mappings.sessions(auth.claims, auth.sessions.moduleId);\n const pendingMapping = mappings.pending(auth.claims, auth.sessions.moduleId);\n\n const sessionServices = yield* makeNativeStatefulSessionServices(\n nativeTables,\n sessionMapping,\n );\n\n const { authenticationAuthority } = yield* makeNativeAuthenticationAuthorityServices(\n nativeTables,\n sessionMapping,\n );\n\n const { pendingAuthentication } = yield* makeNativePendingAuthenticationServices(\n nativeTables,\n pendingMapping,\n );\n\n const { sessionStepUpPersistence } = yield* makeNativeSessionStepUpServices(\n nativeTables,\n {\n ...pendingMapping,\n source: {\n kind: \"Stateful\",\n session: sessionMapping.session,\n sessionId: sessionMapping.sessionId,\n constraints: { sessionDigest: \"unique(session.digest)\" },\n },\n },\n );\n\n const { sessionCleanup } = yield* makeNativeSessionCleanupServices(\n nativeTables,\n pendingMapping,\n );\n\n let context: Context.Context<never> = Context.make(\n AuthenticationAuthority,\n authenticationAuthority,\n ).pipe(\n Context.add(\n auth.sessions.StatefulSessionPersistence,\n sessionServices.statefulSessionPersistence,\n ),\n Context.add(auth.sessions.SessionRepository, sessionServices.sessionRepository),\n Context.add(auth.sessions.PendingAuthentication, pendingAuthentication),\n Context.add(auth.sessions.SessionStepUpPersistence, sessionStepUpPersistence),\n Context.add(auth.sessions.SessionCleanup, sessionCleanup),\n );\n\n const proofMapping = proofs ? mappings.proofs() : undefined;\n\n if (proofMapping !== undefined) {\n const services = yield* makeNativeProofServices(\n backend.nativeTables(database),\n proofMapping,\n );\n\n context = Context.add(context, ProofPersistence, services.proofPersistence);\n }\n\n if (password) {\n const mapping = mappings.passwords();\n\n const services = yield* makeNativePasswordServices(\n backend.nativeTables(database),\n mapping,\n proofMapping,\n );\n\n context = Context.add(context, PasswordPersistence, services.passwordPersistence);\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 backend.nativeTables(database),\n mappings.passwords(),\n ProvisioningKey,\n name,\n );\n\n context = Context.add(context, key, registration);\n }\n if (email)\n context = Context.add(\n context,\n EmailAddressPersistence,\n (yield* makeNativeEmailAddressServices(\n backend.nativeTables(database),\n mappings.emails(),\n proofMapping,\n )).emailAddressPersistence,\n );\n\n if (phone)\n context = Context.add(\n context,\n PhoneSignInTargets,\n yield* makeComposedPhoneTargets(\n backend.nativeTables(database),\n storage,\n mappings.proofs(),\n features.flatMap((feature) =>\n feature?.kind === \"phone\" ? [feature.moduleId] : [],\n ),\n ),\n );\n\n if (passkeys.length > 0) {\n // Auth capability metadata determines whether PasskeyConfig is required.\n const services = makeManagedPasskeys(\n {\n storage,\n namespace: auth.namespace,\n dialect,\n features: passkeys,\n passwordModules: features.flatMap((feature) =>\n feature?.kind === \"password\" ? [feature.moduleId] : [],\n ),\n },\n backend.nativeTables(database),\n ) as Effect.Effect<\n Context.Context<never>,\n PersistenceConfigurationError,\n | PasskeyRequirement<A>\n | Crypto.Crypto\n | LifecycleHooks\n | SqlClient.SqlClient\n | SqlBatchCommit\n >;\n\n context = Context.merge(context, yield* services);\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(Effect.provideService(SqlBatchCommit, backend.batch?.(database))),\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 const layer = Layer.effectContext(\n Effect.gen(function* () {\n const scope = yield* Effect.scope;\n const captured = yield* Effect.context<Layer.Services<typeof services>>();\n const client = Context.get(captured, SqlClient.SqlClient);\n\n // Construction uses the layer's exact environment and lifetime, never the\n // first operation's SQL transaction, service overrides, or request scope.\n // Cache the full exit: concurrent callers share failures/interruption too,\n // and cannot retry partially completed setup within this acquired layer.\n const initialize = yield* Effect.cached(\n Layer.buildWithScope(services, scope).pipe(Effect.setContext(captured)),\n );\n\n const service = Effect.fnUntraced(function* <I, S, Failure>(\n key: Context.Key<I, S>,\n unavailable: () => Failure,\n standalone = true,\n ): Effect.fn.Return<S, Failure> {\n // Reject unsupported ownership before setup can perform any writes.\n // Session/authority reads retain their existing ambient-read semantics.\n if (standalone) {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* requireStandalone(unavailable, client.transactionService);\n }\n const acquired = yield* initialize.pipe(Effect.mapError(unavailable));\n const value = Context.getOrUndefined(acquired, key);\n\n return value === undefined ? yield* Effect.fail(unavailable()) : value;\n });\n\n const sessionUnavailable = () => SessionUnavailable.make({});\n const authority = service(AuthenticationAuthority, sessionUnavailable, false);\n const authorityMutation = service(AuthenticationAuthority, sessionUnavailable);\n\n const sessions = service(\n auth.sessions.StatefulSessionPersistence,\n sessionUnavailable,\n false,\n );\n\n const sessionMutations = service(\n auth.sessions.StatefulSessionPersistence,\n sessionUnavailable,\n );\n\n const repository = service(auth.sessions.SessionRepository, sessionUnavailable, false);\n const pendingRead = service(auth.sessions.PendingAuthentication, sessionUnavailable, false);\n const pendingMutation = service(auth.sessions.PendingAuthentication, sessionUnavailable);\n\n const stepUpRead = service(\n auth.sessions.SessionStepUpPersistence,\n sessionUnavailable,\n false,\n );\n\n const stepUpMutation = service(auth.sessions.SessionStepUpPersistence, sessionUnavailable);\n const cleanup = service(auth.sessions.SessionCleanup, sessionUnavailable);\n\n let context: Context.Context<never> = Context.make(AuthenticationAuthority, {\n capture: (subjectId, credentialIds) =>\n Effect.flatMap(authority, (s) => s.capture(subjectId, credentialIds)),\n requirements: (evidence) => Effect.flatMap(authority, (s) => s.requirements(evidence)),\n approve: (input, prepare) =>\n Effect.flatMap(authorityMutation, (s) => s.approve(input, prepare)),\n }).pipe(\n Context.add(auth.sessions.StatefulSessionPersistence, {\n establish: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.establish(input, prepare)),\n verify: (input) => Effect.flatMap(sessions, (s) => s.verify(input)),\n rotate: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.rotate(input, prepare)),\n revokeDigest: (digest, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.revokeDigest(digest, prepare)),\n revoke: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.revoke(input, prepare)),\n revokeAll: (input, prepare) =>\n Effect.flatMap(sessionMutations, (s) => s.revokeAll(input, prepare)),\n }),\n Context.add(auth.sessions.SessionRepository, {\n list: (input) => Effect.flatMap(repository, (s) => s.list(input)),\n }),\n Context.add(auth.sessions.PendingAuthentication, {\n create: (input, now, prepare) =>\n Effect.flatMap(pendingMutation, (s) => s.create(input, now, prepare)),\n read: (input) => Effect.flatMap(pendingRead, (s) => s.read(input)),\n reject: (input, prepare) =>\n Effect.flatMap(pendingMutation, (s) => s.reject(input, prepare)),\n }),\n Context.add(auth.sessions.SessionStepUpPersistence, {\n create: (input, now, prepare) =>\n Effect.flatMap(stepUpMutation, (s) => s.create(input, now, prepare)),\n read: (input) => Effect.flatMap(stepUpRead, (s) => s.read(input)),\n reject: (input, prepare) =>\n Effect.flatMap(stepUpMutation, (s) => s.reject(input, prepare)),\n complete: (plan, prepare) =>\n Effect.flatMap(stepUpMutation, (s) => s.complete(plan, prepare)),\n }),\n Context.add(auth.sessions.SessionCleanup, {\n cleanup: (input) => Effect.flatMap(cleanup, (s) => s.cleanup(input)),\n }),\n );\n\n if (proofs) {\n const persistence = service(ProofPersistence, () => ProofUnavailable.make({}));\n\n context = Context.add(context, ProofPersistence, {\n issue: (input, prepare) => Effect.flatMap(persistence, (s) => s.issue(input, prepare)),\n redeem: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.redeem(input, prepare)),\n cancel: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.cancel(input, prepare)),\n cleanup: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.cleanup(input, prepare)),\n });\n }\n if (password) {\n const persistence = service(PasswordPersistence, () => PasswordUnavailable.make({}));\n\n context = Context.add(context, PasswordPersistence, {\n findCredential: (input) => Effect.flatMap(persistence, (s) => s.findCredential(input)),\n rehashIfCurrent: (input) =>\n Effect.flatMap(persistence, (s) => s.rehashIfCurrent(input)),\n readForSubject: (input) => Effect.flatMap(persistence, (s) => s.readForSubject(input)),\n recoveryTarget: (input) => Effect.flatMap(persistence, (s) => s.recoveryTarget(input)),\n addIfAbsent: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.addIfAbsent(input, prepare)),\n replaceIfCurrent: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.replaceIfCurrent(input, prepare)),\n resetWithProof: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.resetWithProof(input, prepare)),\n });\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 // The heterogeneous strategy table erases the registration input type;\n // its decoded value is forwarded unchanged to the same authority key.\n const registration = service(\n key as Context.Key<unknown, PasswordRegistrationAuthority<unknown>>,\n () => PasswordUnavailable.make({}),\n );\n\n context = Context.add(context, key, {\n register: (input, prepare) =>\n Effect.flatMap(registration, (s) => s.register(input, prepare)),\n } satisfies PasswordRegistrationAuthority<unknown>);\n }\n if (email) {\n const persistence = service(EmailAddressPersistence, () => EmailUnavailable.make({}));\n\n context = Context.add(context, EmailAddressPersistence, {\n target: (input) => Effect.flatMap(persistence, (s) => s.target(input)),\n verifyWithProof: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.verifyWithProof(input, prepare)),\n changeWithProof: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.changeWithProof(input, prepare)),\n });\n }\n if (phone) {\n const unavailable = () => PhoneOtpUnavailable.make({});\n const targets = service(PhoneSignInTargets, unavailable);\n\n context = context.pipe(\n Context.add(PhoneSignInTargets, {\n lookup: (input) => Effect.flatMap(targets, (s) => s.lookup(input)),\n }),\n );\n }\n if (passkeys.length > 0) {\n const unavailable = () => PasskeyUnavailable.make({});\n const credentials = service(PasskeyCredentials, unavailable);\n const persistence = service(PasskeyPersistence, unavailable);\n\n context = context.pipe(\n Context.add(PasskeyCredentials, {\n lookup: (input) => Effect.flatMap(credentials, (s) => s.lookup(input)),\n listForSubject: (input) =>\n Effect.flatMap(credentials, (s) => s.listForSubject(input)),\n }),\n Context.add(PasskeyPersistence, {\n issue: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.issue(input, prepare)),\n context: (input) => Effect.flatMap(persistence, (s) => s.context(input)),\n consume: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.consume(input, prepare)),\n cleanup: (input, prepare) =>\n Effect.flatMap(persistence, (s) => s.cleanup(input, prepare)),\n }),\n );\n if (passkeys.some((feature) => feature.management)) {\n const manager = service(PasskeyManagementPersistence, unavailable);\n\n context = context.pipe(\n Context.add(PasskeyManagementPersistence, {\n list: (input) => Effect.flatMap(manager, (s) => s.list(input)),\n completeEnrollment: (input, prepare) =>\n Effect.flatMap(manager, (s) => s.completeEnrollment(input, prepare)),\n inspectRemove: (input) => Effect.flatMap(manager, (s) => s.inspectRemove(input)),\n remove: (input, prepare) =>\n Effect.flatMap(manager, (s) => s.remove(input, prepare)),\n rename: (input, prepare) =>\n Effect.flatMap(manager, (s) => s.rename(input, prepare)),\n }),\n );\n }\n }\n\n // Match the same capability-selected service keys as the acquired graph.\n return context as Context.Context<Ports<C, Id, A>>;\n }),\n );\n\n return {\n Config,\n Provisioning: ProvisioningKey,\n layer,\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,5 +1,5 @@
|
|
|
1
1
|
import { PersistenceConfigurationError } from "./configuration.mjs";
|
|
2
|
-
import { CurrentSqlCommit, makeSqlCommitExecutor } from "./sql-commit.mjs";
|
|
2
|
+
import { CurrentSqlCommit, SqlBatchCommit, makeSqlCommitExecutor } from "./sql-commit.mjs";
|
|
3
3
|
import { randomId } from "./crypto.mjs";
|
|
4
4
|
import { PasswordIdentifierTaken, makeNativePasswordRegistrationStore } from "./password-registration-native.mjs";
|
|
5
5
|
import { Cause, Crypto, Effect, Option, Schema } from "effect";
|
|
@@ -7,6 +7,7 @@ import { PasswordUnavailable } from "@yielded/auth/Password";
|
|
|
7
7
|
import { SecurityRevision } from "@yielded/auth/Sessions";
|
|
8
8
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
9
9
|
//#region src/internal/registration.ts
|
|
10
|
+
const SubjectValues = Schema.Record(Schema.String, Schema.Unknown);
|
|
10
11
|
/** Application provisioning and the unverified identifier/password binding share
|
|
11
12
|
* one physical owner. Identifier uniqueness arbitrates registration; correlation
|
|
12
13
|
* request IDs never become a retained credential-issuance receipt. */
|
|
@@ -15,28 +16,36 @@ const makeRegistrationAuthority = Effect.fnUntraced(function* (tables, mapping,
|
|
|
15
16
|
const crypto = yield* Crypto.Crypto;
|
|
16
17
|
const executor = yield* makeSqlCommitExecutor(() => PasswordUnavailable.make({}));
|
|
17
18
|
const parent = yield* Effect.serviceOption(CurrentSqlCommit);
|
|
18
|
-
const
|
|
19
|
+
const batch = yield* SqlBatchCommit;
|
|
20
|
+
const store = yield* makeNativePasswordRegistrationStore(tables, mapping, batch !== void 0);
|
|
19
21
|
const createSubject = (yield* provisioning)[strategy];
|
|
20
22
|
if (createSubject === void 0) return yield* PersistenceConfigurationError.make({ reason: `Missing subject provisioning for ${strategy}` });
|
|
23
|
+
if (batch !== void 0 && typeof createSubject === "function") return yield* PersistenceConfigurationError.make({ reason: `D1 subject provisioning for ${strategy} requires { values }; return the new subject row instead of inserting it` });
|
|
21
24
|
const s = mapping.subject;
|
|
22
25
|
const subject = tables(s.table);
|
|
23
26
|
const lock = sql.onDialectOrElse({
|
|
24
27
|
sqlite: () => sql``,
|
|
25
28
|
orElse: () => sql`for update`
|
|
26
29
|
});
|
|
27
|
-
const run = (work, suppressed) => Option.isSome(parent) ? executor.run(work) : executor.operation(work.pipe(Effect.mapError((error) => error instanceof PasswordIdentifierTaken ? error : PasswordUnavailable.make({})))).pipe(Effect.catchCause((cause) => cause.reasons.length > 0 && cause.reasons.every((reason) => Cause.isFailReason(reason) && reason.error instanceof PasswordIdentifierTaken) ? executor.run(suppressed, "statement") : Effect.failCause(cause)), Effect.mapError(() => PasswordUnavailable.make({})));
|
|
30
|
+
const run = (work, suppressed) => batch !== void 0 ? executor.batch(work).pipe(Effect.provideService(SqlBatchCommit, batch)) : Option.isSome(parent) ? executor.run(work) : executor.operation(work.pipe(Effect.mapError((error) => error instanceof PasswordIdentifierTaken ? error : PasswordUnavailable.make({})))).pipe(Effect.catchCause((cause) => cause.reasons.length > 0 && cause.reasons.every((reason) => Cause.isFailReason(reason) && reason.error instanceof PasswordIdentifierTaken) ? executor.run(suppressed, "statement") : Effect.failCause(cause)), Effect.mapError(() => PasswordUnavailable.make({})));
|
|
28
31
|
return { register: (input, project) => executor.read(store.available(input.identifier)).pipe(Effect.flatMap((existing) => existing.length !== 0 ? executor.run(store.prepare({ _tag: "Suppressed" }, project), "statement") : run(Effect.gen(function* () {
|
|
29
|
-
const
|
|
32
|
+
const provisioningInput = {
|
|
30
33
|
requestId: input.requestId,
|
|
31
34
|
identifier: input.identifier,
|
|
32
35
|
registration: input.registration
|
|
36
|
+
};
|
|
37
|
+
const row = yield* Effect.gen(function* () {
|
|
38
|
+
if (typeof createSubject !== "function") return yield* createSubject.values(provisioningInput).pipe(Effect.flatMap(Schema.decodeEffect(SubjectValues)));
|
|
39
|
+
const subjectId = yield* createSubject(provisioningInput);
|
|
40
|
+
const nativeId = yield* mapping.subjectId.toNative(subjectId);
|
|
41
|
+
const rows = yield* sql`select ${subject.fields("registration_subject_")} from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, nativeId)} limit 2 ${lock}`;
|
|
42
|
+
if (rows.length !== 1) return yield* PasswordUnavailable.make({});
|
|
43
|
+
return subject.decode(rows[0], "registration_subject_");
|
|
33
44
|
});
|
|
34
|
-
const nativeId = yield* mapping.subjectId.toNative(subjectId);
|
|
35
|
-
const rows = yield* sql`select ${subject.fields("registration_subject_")} from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, nativeId)} limit 2 ${lock}`;
|
|
36
|
-
if (rows.length !== 1) return yield* PasswordUnavailable.make({});
|
|
37
|
-
const row = subject.decode(rows[0], "registration_subject_");
|
|
38
45
|
if (!s.isActiveStatus(row[s.status])) return yield* PasswordUnavailable.make({});
|
|
46
|
+
const subjectId = yield* mapping.subjectId.toSubject(row[s.id]);
|
|
39
47
|
const securityRevision = yield* Schema.decodeUnknownEffect(SecurityRevision)(row[s.securityRevision]);
|
|
48
|
+
if (typeof createSubject !== "function") yield* store.stage(sql`${subject.insert(row)}`);
|
|
40
49
|
const identifierRevision = SecurityRevision.make(yield* randomId);
|
|
41
50
|
const credentialId = yield* randomId;
|
|
42
51
|
const credentialRevision = SecurityRevision.make(yield* randomId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration.mjs","names":[],"sources":["../../src/internal/registration.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport
|
|
1
|
+
{"version":3,"file":"registration.mjs","names":[],"sources":["../../src/internal/registration.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { PasswordUnavailable } from \"@yielded/auth/Password\";\nimport { SecurityRevision } from \"@yielded/auth/Sessions\";\nimport { type Context, Crypto, Effect, Schema, Option, Cause } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { PersistenceConfigurationError, type SubjectProvisioning } from \"./configuration\";\nimport { randomId } from \"./crypto\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport {\n makeNativePasswordRegistrationStore,\n PasswordIdentifierTaken,\n} from \"./password-registration-native\";\nimport type { PasswordRegistrationAuthority } from \"./registration-contract\";\nimport { makeSqlCommitExecutor, CurrentSqlCommit, SqlBatchCommit } from \"./sql-commit\";\n\nconst SubjectValues = Schema.Record(Schema.String, Schema.Unknown);\n\n/** Application provisioning and the unverified identifier/password binding share\n * one physical owner. Identifier uniqueness arbitrates registration; correlation\n * request IDs never become a retained credential-issuance receipt. */\nexport const makeRegistrationAuthority = Effect.fnUntraced(function* <R>(\n tables: NativeSqlTables,\n mapping: AnyPasswordPersistenceMapping,\n provisioning: Context.Key<R, object>,\n strategy: string,\n): Effect.fn.Return<\n PasswordRegistrationAuthority<unknown>,\n PersistenceConfigurationError,\n LifecycleHooks | SqlClient | Crypto.Crypto | SqlBatchCommit | R\n> {\n const sql = (yield* SqlClient).withoutTransforms();\n const crypto = yield* Crypto.Crypto;\n const executor = yield* makeSqlCommitExecutor(() => PasswordUnavailable.make({}));\n const parent = yield* Effect.serviceOption(CurrentSqlCommit);\n const batch = yield* SqlBatchCommit;\n const store = yield* makeNativePasswordRegistrationStore(tables, mapping, batch !== undefined);\n // The strategy has already decoded its registration Schema. Only its callback\n // signature is erased by the heterogeneous strategy service map.\n const creators = (yield* provisioning) as Readonly<Record<string, SubjectProvisioning<unknown>>>;\n const createSubject = creators[strategy];\n\n if (createSubject === undefined)\n return yield* PersistenceConfigurationError.make({\n reason: `Missing subject provisioning for ${strategy}`,\n });\n if (batch !== undefined && typeof createSubject === \"function\")\n return yield* PersistenceConfigurationError.make({\n reason: `D1 subject provisioning for ${strategy} requires { values }; return the new subject row instead of inserting it`,\n });\n const s = mapping.subject;\n const subject = tables(s.table);\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n\n const run = <A, E, R2>(work: Effect.Effect<A, E, R2>, suppressed: Effect.Effect<A, E, R2>) =>\n batch !== undefined\n ? executor.batch(work).pipe(Effect.provideService(SqlBatchCommit, batch))\n : Option.isSome(parent)\n ? executor.run(work)\n : executor\n .operation(\n work.pipe(\n Effect.mapError((error) =>\n error instanceof PasswordIdentifierTaken ? error : PasswordUnavailable.make({}),\n ),\n ),\n )\n .pipe(\n Effect.catchCause((cause) =>\n cause.reasons.length > 0 &&\n cause.reasons.every(\n (reason) =>\n Cause.isFailReason(reason) && reason.error instanceof PasswordIdentifierTaken,\n )\n ? executor.run(suppressed, \"statement\")\n : Effect.failCause(cause),\n ),\n Effect.mapError(() => PasswordUnavailable.make({})),\n );\n\n return {\n register: (input, project) =>\n executor.read(store.available(input.identifier)).pipe(\n Effect.flatMap((existing) =>\n existing.length !== 0\n ? executor.run(store.prepare({ _tag: \"Suppressed\" }, project), \"statement\")\n : run(\n Effect.gen(function* () {\n const provisioningInput = {\n requestId: input.requestId,\n identifier: input.identifier,\n registration: input.registration,\n };\n\n const row = yield* Effect.gen(function* () {\n if (typeof createSubject !== \"function\")\n return yield* createSubject\n .values(provisioningInput)\n .pipe(Effect.flatMap(Schema.decodeEffect(SubjectValues)));\n const subjectId = yield* createSubject(provisioningInput);\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"registration_subject_\")} from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, nativeId)} limit 2 ${lock}`;\n\n if (rows.length !== 1) return yield* PasswordUnavailable.make({});\n\n return subject.decode(rows[0]!, \"registration_subject_\");\n });\n\n if (!s.isActiveStatus(row[s.status])) return yield* PasswordUnavailable.make({});\n const subjectId = yield* mapping.subjectId.toSubject(row[s.id]);\n\n const securityRevision = yield* Schema.decodeUnknownEffect(SecurityRevision)(\n row[s.securityRevision],\n );\n\n if (typeof createSubject !== \"function\")\n yield* store.stage(sql`${subject.insert(row)}`);\n\n const identifierRevision = SecurityRevision.make(yield* randomId);\n const credentialId = yield* randomId;\n const credentialRevision = SecurityRevision.make(yield* randomId);\n const verifierVersion = SecurityRevision.make(yield* randomId);\n\n yield* store.bind({\n ...input,\n subjectId,\n securityRevision,\n identifierRevision,\n credentialId,\n credentialRevision,\n verifierVersion,\n });\n\n return yield* store.prepare({ _tag: \"Created\", subjectId }, project);\n }).pipe(Effect.provideService(Crypto.Crypto, crypto)),\n store.prepare({ _tag: \"Suppressed\" }, project),\n ),\n ),\n ),\n };\n});\n"],"mappings":"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"}
|
|
@@ -59,7 +59,8 @@ const makeNativeAuthenticationAuthorityServices = Effect.fnUntraced(function* (t
|
|
|
59
59
|
if (current.now >= expires || expires > absolute) return yield* StaleAuthentication.make({});
|
|
60
60
|
const pendingExpiry = input.pending === void 0 ? void 0 : yield* consume(input.pending, input.evidence, current.native);
|
|
61
61
|
const deadline = Math.min(assessment.validUntil, expires, pendingExpiry ?? Infinity);
|
|
62
|
-
|
|
62
|
+
yield* state.guardPolicy(current);
|
|
63
|
+
const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;
|
|
63
64
|
if (batch !== void 0) {
|
|
64
65
|
yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));
|
|
65
66
|
yield* registerSqlBatchPostcondition({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-authority.mjs","names":[],"sources":["../../src/internal/session-native-authority.ts"],"sourcesContent":["import { type LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n type AuthenticationAuthority,\n StaleAuthentication,\n PendingAuthenticationInvalid,\n type AuthenticationEvidence,\n type PendingConsumption,\n type AuthenticationRequirement,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AuthenticationAuthorityMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeNativeSessionPending, prepareNativeSession } from \"./session-native-pending\";\nimport {\n makeNativeSessionAuthorityState,\n normalizeSessionOperation,\n sessionUnavailable,\n sameSessionRevision,\n assessSessionAt,\n} from \"./session-native-state\";\nimport { preservesRevision } from \"./session-policy\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n appendSqlBatchStatement,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeAuthenticationAuthorityMapping<Claims> = AuthenticationAuthorityMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown\n>;\n\nexport const sessionEvidenceDeadline = Effect.fnUntraced(function* (\n evidence: AuthenticationEvidence,\n requirement: AuthenticationRequirement,\n now: number,\n) {\n const assessed = yield* assessSessionAt(evidence, requirement, now);\n\n if (!assessed.satisfied) return yield* StaleAuthentication.make({});\n\n const boundaries = [\n ...new Set(\n evidence.proofs.map(\n (proof) => DateTime.toEpochMillis(proof.verifiedAt) + requirement.maximumAgeMillis,\n ),\n ),\n ]\n .filter((n) => n > now)\n .sort((a, b) => a - b);\n\n for (const boundary of boundaries) {\n const after = yield* assessSessionAt(evidence, requirement, boundary).pipe(Effect.result);\n\n if (after._tag === \"Failure\" || !after.success.satisfied)\n return { assessed, validUntil: boundary };\n }\n\n return yield* StaleAuthentication.make({});\n});\n\nexport const makeNativeAuthenticationAuthorityServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeAuthenticationAuthorityMapping<Claims>,\n): Effect.fn.Return<\n { readonly authenticationAuthority: AuthenticationAuthority[\"Service\"] },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const state = yield* makeNativeSessionAuthorityState(tables, mapping, batch !== undefined);\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const pending =\n mapping.pending === undefined\n ? undefined\n : yield* makeNativeSessionPending(\n tables,\n { ...mapping, ...mapping.pending },\n batch !== undefined,\n );\n\n const { sql, now } = state;\n\n const readEvidence = Effect.fnUntraced(function* (\n evidence: AuthenticationEvidence,\n locking: boolean,\n ) {\n const current = yield* state.read(evidence.revision.subjectId, locking);\n\n return current !== undefined && sameSessionRevision(current.revision, evidence.revision)\n ? current\n : undefined;\n });\n\n const consume = Effect.fnUntraced(function* (\n input: PendingConsumption,\n evidence: AuthenticationEvidence,\n native: unknown,\n ) {\n if (pending === undefined || mapping.pending === undefined)\n return yield* PendingAuthenticationInvalid.make({});\n const found = yield* pending.read(\"Login\", input.digest);\n\n if (\n found === undefined ||\n found.record.version !== input.version ||\n found.record.flowId !== input.flowId ||\n found.record.bindingDigest !== input.bindingDigest ||\n found.record.subjectId !== evidence.revision.subjectId\n )\n return yield* PendingAuthenticationInvalid.make({});\n const original = yield* mapping.pending.login.decode(found.record.snapshot);\n\n if (\n original.digest !== input.digest ||\n original.version !== input.version ||\n !preservesRevision(evidence, original.evidence) ||\n DateTime.toEpochMillis(original.expiresAt) !== found.record.expiresAtMillis ||\n original.attemptLimit !== found.record.attemptLimit\n )\n return yield* PendingAuthenticationInvalid.make({});\n yield* pending.consume(\"Login\", input, native);\n\n return found.record.expiresAtMillis;\n });\n\n const native = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work))\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const authenticationAuthority: AuthenticationAuthority[\"Service\"] = {\n capture: (subjectId, credentialIds) =>\n executor\n .read(state.read(subjectId))\n .pipe(\n Effect.flatMap((current) =>\n current === undefined ||\n new Set(credentialIds).size !== credentialIds.length ||\n credentialIds.some(\n (id) => !current.revision.credentials.some((f) => f.credentialId === id),\n )\n ? Effect.fail(StaleAuthentication.make({}))\n : Effect.succeed({ revision: current.revision, requirement: current.requirement }),\n ),\n ),\n requirements: (evidence) =>\n executor\n .read(readEvidence(evidence, false))\n .pipe(\n Effect.flatMap((current) =>\n current === undefined\n ? Effect.fail(StaleAuthentication.make({}))\n : Effect.succeed(current.requirement),\n ),\n ),\n approve: (input, project) =>\n native(\n Effect.gen(function* () {\n const current = yield* readEvidence(input.evidence, true);\n\n if (current === undefined) return yield* StaleAuthentication.make({});\n\n const assessment = yield* sessionEvidenceDeadline(\n input.evidence,\n current.requirement,\n current.now,\n );\n\n const expires = DateTime.toEpochMillis(input.expiresAt),\n absolute = DateTime.toEpochMillis(input.absoluteExpiresAt);\n\n if (current.now >= expires || expires > absolute)\n return yield* StaleAuthentication.make({});\n\n const pendingExpiry =\n input.pending === undefined\n ? undefined\n : yield* consume(input.pending, input.evidence, current.native);\n\n const deadline = Math.min(assessment.validUntil, expires, pendingExpiry ?? Infinity);\n const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${state.policyCondition(current.row, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n if (batch !== undefined) {\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n yield* registerSqlBatchPostcondition({\n name: \"signed-session-issuance-authority\",\n statement: sqlBatchAssertion(\n sql,\n input.pending === undefined || pending === undefined\n ? condition\n : sql`${condition} and ${pending.consumedCondition(\"Login\", input.pending, current.native)}`,\n ),\n });\n } else if (Option.isSome(external)) {\n yield* registerSqlPostcondition({\n name: \"signed-session-issuance-authority\",\n check: Effect.gen(function* () {\n const final = yield* readEvidence(input.evidence, false);\n\n if (final === undefined || final.now < current.now || final.now >= deadline)\n return yield* StaleAuthentication.make({});\n yield* sessionEvidenceDeadline(input.evidence, final.requirement, final.now);\n if (input.pending !== undefined && pending !== undefined) {\n const rows =\n yield* sql`select 1 where ${pending.consumedCondition(\"Login\", input.pending, current.native)}`;\n\n if (rows.length !== 1) return yield* PendingAuthenticationInvalid.make({});\n }\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n } else {\n // A signed credential has no session INSERT that can enforce the\n // original evidence deadline. Check that deadline at this owner.\n const rows =\n yield* sql`select 1 where ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n if (rows.length !== 1) return yield* StaleAuthentication.make({});\n }\n\n return yield* prepareNativeSession(undefined, project);\n }),\n ),\n };\n\n return { authenticationAuthority };\n});\n"],"mappings":";;;;;;;;;;AA2CA,MAAa,0BAA0B,OAAO,WAAW,WACvD,UACA,aACA,KACA;CACA,MAAM,WAAW,OAAO,gBAAgB,UAAU,aAAa,GAAG;CAElE,IAAI,CAAC,SAAS,WAAW,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;CAElE,MAAM,aAAa,CACjB,GAAG,IAAI,IACL,SAAS,OAAO,KACb,UAAU,SAAS,cAAc,MAAM,UAAU,IAAI,YAAY,gBACpE,CACF,CACF,CAAC,CACE,QAAQ,MAAM,IAAI,GAAG,CAAC,CACtB,MAAM,GAAG,MAAM,IAAI,CAAC;CAEvB,KAAK,MAAM,YAAY,YAAY;EACjC,MAAM,QAAQ,OAAO,gBAAgB,UAAU,aAAa,QAAQ,CAAC,CAAC,KAAK,OAAO,MAAM;EAExF,IAAI,MAAM,SAAS,aAAa,CAAC,MAAM,QAAQ,WAC7C,OAAO;GAAE;GAAU,YAAY;EAAS;CAC5C;CAEA,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,MAAa,4CAA4C,OAAO,WAAW,WACzE,QACA,SAKA;CACA,MAAM,QAAQ,OAAO;CAErB,MAAM,QAAQ,OAAO,gCAAgC,QAAQ,SAAS,UAAU,KAAA,CAAS;CACzF,MAAM,WAAW,OAAO,sBAAsB,kBAAkB;CAChE,MAAM,WAAW,OAAO,OAAO,cAAc,gBAAgB;CAE7D,MAAM,UACJ,QAAQ,YAAY,KAAA,IAChB,KAAA,IACA,OAAO,yBACL,QACA;EAAE,GAAG;EAAS,GAAG,QAAQ;CAAQ,GACjC,UAAU,KAAA,CACZ;CAEN,MAAM,EAAE,KAAK,QAAQ;CAErB,MAAM,eAAe,OAAO,WAAW,WACrC,UACA,SACA;EACA,MAAM,UAAU,OAAO,MAAM,KAAK,SAAS,SAAS,WAAW,OAAO;EAEtE,OAAO,YAAY,KAAA,KAAa,oBAAoB,QAAQ,UAAU,SAAS,QAAQ,IACnF,UACA,KAAA;CACN,CAAC;CAED,MAAM,UAAU,OAAO,WAAW,WAChC,OACA,UACA,QACA;EACA,IAAI,YAAY,KAAA,KAAa,QAAQ,YAAY,KAAA,GAC/C,OAAO,OAAO,6BAA6B,KAAK,CAAC,CAAC;EACpD,MAAM,QAAQ,OAAO,QAAQ,KAAK,SAAS,MAAM,MAAM;EAEvD,IACE,UAAU,KAAA,KACV,MAAM,OAAO,YAAY,MAAM,WAC/B,MAAM,OAAO,WAAW,MAAM,UAC9B,MAAM,OAAO,kBAAkB,MAAM,iBACrC,MAAM,OAAO,cAAc,SAAS,SAAS,WAE7C,OAAO,OAAO,6BAA6B,KAAK,CAAC,CAAC;EACpD,MAAM,WAAW,OAAO,QAAQ,QAAQ,MAAM,OAAO,MAAM,OAAO,QAAQ;EAE1E,IACE,SAAS,WAAW,MAAM,UAC1B,SAAS,YAAY,MAAM,WAC3B,CAAC,kBAAkB,UAAU,SAAS,QAAQ,KAC9C,SAAS,cAAc,SAAS,SAAS,MAAM,MAAM,OAAO,mBAC5D,SAAS,iBAAiB,MAAM,OAAO,cAEvC,OAAO,OAAO,6BAA6B,KAAK,CAAC,CAAC;EACpD,OAAO,QAAQ,QAAQ,SAAS,OAAO,MAAM;EAE7C,OAAO,MAAM,OAAO;CACtB,CAAC;CAED,MAAM,UAAmB,SACvB,UAAU,KAAA,IACN,SAAS,UAAU,0BAA0B,IAAI,CAAC,IAClD,SACG,eAAe,0BAA0B,IAAI,CAAC,CAAC,CAC/C,KAAK,OAAO,eAAe,gBAAgB,KAAK,CAAC;CAoG1D,OAAO,EAAE,yBAAA;EAjGP,UAAU,WAAW,kBACnB,SACG,KAAK,MAAM,KAAK,SAAS,CAAC,CAAC,CAC3B,KACC,OAAO,SAAS,YACd,YAAY,KAAA,KACZ,IAAI,IAAI,aAAa,CAAC,CAAC,SAAS,cAAc,UAC9C,cAAc,MACX,OAAO,CAAC,QAAQ,SAAS,YAAY,MAAM,MAAM,EAAE,iBAAiB,EAAE,CACzE,IACI,OAAO,KAAK,oBAAoB,KAAK,CAAC,CAAC,CAAC,IACxC,OAAO,QAAQ;GAAE,UAAU,QAAQ;GAAU,aAAa,QAAQ;EAAY,CAAC,CACrF,CACF;EACJ,eAAe,aACb,SACG,KAAK,aAAa,UAAU,KAAK,CAAC,CAAC,CACnC,KACC,OAAO,SAAS,YACd,YAAY,KAAA,IACR,OAAO,KAAK,oBAAoB,KAAK,CAAC,CAAC,CAAC,IACxC,OAAO,QAAQ,QAAQ,WAAW,CACxC,CACF;EACJ,UAAU,OAAO,YACf,OACE,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,aAAa,MAAM,UAAU,IAAI;GAExD,IAAI,YAAY,KAAA,GAAW,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;GAEpE,MAAM,aAAa,OAAO,wBACxB,MAAM,UACN,QAAQ,aACR,QAAQ,GACV;GAEA,MAAM,UAAU,SAAS,cAAc,MAAM,SAAS,GACpD,WAAW,SAAS,cAAc,MAAM,iBAAiB;GAE3D,IAAI,QAAQ,OAAO,WAAW,UAAU,UACtC,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;GAE3C,MAAM,gBACJ,MAAM,YAAY,KAAA,IACd,KAAA,IACA,OAAO,QAAQ,MAAM,SAAS,MAAM,UAAU,QAAQ,MAAM;GAElE,MAAM,WAAW,KAAK,IAAI,WAAW,YAAY,SAAS,iBAAiB,QAAQ;GACnF,MAAM,YAAY,GAAG,GAAG,MAAM,mBAAmB,QAAQ,UAAU,QAAQ,MAAM,EAAE,OAAO,MAAM,gBAAgB,QAAQ,KAAK,QAAQ,MAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,IAAI,OAAO,IAAI,KAAK;GAE1L,IAAI,UAAU,KAAA,GAAW;IACvB,OAAO,wBAAwB,kBAAkB,KAAK,SAAS,CAAC;IAChE,OAAO,8BAA8B;KACnC,MAAM;KACN,WAAW,kBACT,KACA,MAAM,YAAY,KAAA,KAAa,YAAY,KAAA,IACvC,YACA,GAAG,GAAG,UAAU,OAAO,QAAQ,kBAAkB,SAAS,MAAM,SAAS,QAAQ,MAAM,GAC7F;IACF,CAAC;GACH,OAAO,IAAI,OAAO,OAAO,QAAQ,GAC/B,OAAO,yBAAyB;IAC9B,MAAM;IACN,OAAO,OAAO,IAAI,aAAa;KAC7B,MAAM,QAAQ,OAAO,aAAa,MAAM,UAAU,KAAK;KAEvD,IAAI,UAAU,KAAA,KAAa,MAAM,MAAM,QAAQ,OAAO,MAAM,OAAO,UACjE,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;KAC3C,OAAO,wBAAwB,MAAM,UAAU,MAAM,aAAa,MAAM,GAAG;KAC3E,IAAI,MAAM,YAAY,KAAA,KAAa,YAAY,KAAA,GAIzC;WAAA,OAFK,GAAG,kBAAkB,QAAQ,kBAAkB,SAAS,MAAM,SAAS,QAAQ,MAAM,IAAA,CAErF,WAAW,GAAG,OAAO,OAAO,6BAA6B,KAAK,CAAC,CAAC;KAAA;IAE7E,CAAC,CAAC,CAAC,KACD,OAAO,UAAU,UACf,wBAAwB,KAAK;KAAE,WAAW;KAAU;IAAM,CAAC,CAC7D,CACF;GACF,CAAC;QAOD,KAAI,OAFK,GAAG,kBAAkB,IAAI,MAAM,QAAQ,IAAI,OAAO,IAAI,KAAK,WAAA,CAE3D,WAAW,GAAG,OAAO,OAAO,oBAAoB,KAAK,CAAC,CAAC;GAGlE,OAAO,OAAO,qBAAqB,KAAA,GAAW,OAAO;EACvD,CAAC,CACH;CAG2B,EAAE;AACnC,CAAC"}
|
|
1
|
+
{"version":3,"file":"session-native-authority.mjs","names":[],"sources":["../../src/internal/session-native-authority.ts"],"sourcesContent":["import { type LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n type AuthenticationAuthority,\n StaleAuthentication,\n PendingAuthenticationInvalid,\n type AuthenticationEvidence,\n type PendingConsumption,\n type AuthenticationRequirement,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AuthenticationAuthorityMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeNativeSessionPending, prepareNativeSession } from \"./session-native-pending\";\nimport {\n makeNativeSessionAuthorityState,\n normalizeSessionOperation,\n sessionUnavailable,\n sameSessionRevision,\n assessSessionAt,\n} from \"./session-native-state\";\nimport { preservesRevision } from \"./session-policy\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n appendSqlBatchStatement,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeAuthenticationAuthorityMapping<Claims> = AuthenticationAuthorityMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown\n>;\n\nexport const sessionEvidenceDeadline = Effect.fnUntraced(function* (\n evidence: AuthenticationEvidence,\n requirement: AuthenticationRequirement,\n now: number,\n) {\n const assessed = yield* assessSessionAt(evidence, requirement, now);\n\n if (!assessed.satisfied) return yield* StaleAuthentication.make({});\n\n const boundaries = [\n ...new Set(\n evidence.proofs.map(\n (proof) => DateTime.toEpochMillis(proof.verifiedAt) + requirement.maximumAgeMillis,\n ),\n ),\n ]\n .filter((n) => n > now)\n .sort((a, b) => a - b);\n\n for (const boundary of boundaries) {\n const after = yield* assessSessionAt(evidence, requirement, boundary).pipe(Effect.result);\n\n if (after._tag === \"Failure\" || !after.success.satisfied)\n return { assessed, validUntil: boundary };\n }\n\n return yield* StaleAuthentication.make({});\n});\n\nexport const makeNativeAuthenticationAuthorityServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativeAuthenticationAuthorityMapping<Claims>,\n): Effect.fn.Return<\n { readonly authenticationAuthority: AuthenticationAuthority[\"Service\"] },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const state = yield* makeNativeSessionAuthorityState(tables, mapping, batch !== undefined);\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const pending =\n mapping.pending === undefined\n ? undefined\n : yield* makeNativeSessionPending(\n tables,\n { ...mapping, ...mapping.pending },\n batch !== undefined,\n );\n\n const { sql, now } = state;\n\n const readEvidence = Effect.fnUntraced(function* (\n evidence: AuthenticationEvidence,\n locking: boolean,\n ) {\n const current = yield* state.read(evidence.revision.subjectId, locking);\n\n return current !== undefined && sameSessionRevision(current.revision, evidence.revision)\n ? current\n : undefined;\n });\n\n const consume = Effect.fnUntraced(function* (\n input: PendingConsumption,\n evidence: AuthenticationEvidence,\n native: unknown,\n ) {\n if (pending === undefined || mapping.pending === undefined)\n return yield* PendingAuthenticationInvalid.make({});\n const found = yield* pending.read(\"Login\", input.digest);\n\n if (\n found === undefined ||\n found.record.version !== input.version ||\n found.record.flowId !== input.flowId ||\n found.record.bindingDigest !== input.bindingDigest ||\n found.record.subjectId !== evidence.revision.subjectId\n )\n return yield* PendingAuthenticationInvalid.make({});\n const original = yield* mapping.pending.login.decode(found.record.snapshot);\n\n if (\n original.digest !== input.digest ||\n original.version !== input.version ||\n !preservesRevision(evidence, original.evidence) ||\n DateTime.toEpochMillis(original.expiresAt) !== found.record.expiresAtMillis ||\n original.attemptLimit !== found.record.attemptLimit\n )\n return yield* PendingAuthenticationInvalid.make({});\n yield* pending.consume(\"Login\", input, native);\n\n return found.record.expiresAtMillis;\n });\n\n const native = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work))\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const authenticationAuthority: AuthenticationAuthority[\"Service\"] = {\n capture: (subjectId, credentialIds) =>\n executor\n .read(state.read(subjectId))\n .pipe(\n Effect.flatMap((current) =>\n current === undefined ||\n new Set(credentialIds).size !== credentialIds.length ||\n credentialIds.some(\n (id) => !current.revision.credentials.some((f) => f.credentialId === id),\n )\n ? Effect.fail(StaleAuthentication.make({}))\n : Effect.succeed({ revision: current.revision, requirement: current.requirement }),\n ),\n ),\n requirements: (evidence) =>\n executor\n .read(readEvidence(evidence, false))\n .pipe(\n Effect.flatMap((current) =>\n current === undefined\n ? Effect.fail(StaleAuthentication.make({}))\n : Effect.succeed(current.requirement),\n ),\n ),\n approve: (input, project) =>\n native(\n Effect.gen(function* () {\n const current = yield* readEvidence(input.evidence, true);\n\n if (current === undefined) return yield* StaleAuthentication.make({});\n\n const assessment = yield* sessionEvidenceDeadline(\n input.evidence,\n current.requirement,\n current.now,\n );\n\n const expires = DateTime.toEpochMillis(input.expiresAt),\n absolute = DateTime.toEpochMillis(input.absoluteExpiresAt);\n\n if (current.now >= expires || expires > absolute)\n return yield* StaleAuthentication.make({});\n\n const pendingExpiry =\n input.pending === undefined\n ? undefined\n : yield* consume(input.pending, input.evidence, current.native);\n\n const deadline = Math.min(assessment.validUntil, expires, pendingExpiry ?? Infinity);\n\n yield* state.guardPolicy(current);\n const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n if (batch !== undefined) {\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n yield* registerSqlBatchPostcondition({\n name: \"signed-session-issuance-authority\",\n statement: sqlBatchAssertion(\n sql,\n input.pending === undefined || pending === undefined\n ? condition\n : sql`${condition} and ${pending.consumedCondition(\"Login\", input.pending, current.native)}`,\n ),\n });\n } else if (Option.isSome(external)) {\n yield* registerSqlPostcondition({\n name: \"signed-session-issuance-authority\",\n check: Effect.gen(function* () {\n const final = yield* readEvidence(input.evidence, false);\n\n if (final === undefined || final.now < current.now || final.now >= deadline)\n return yield* StaleAuthentication.make({});\n yield* sessionEvidenceDeadline(input.evidence, final.requirement, final.now);\n if (input.pending !== undefined && pending !== undefined) {\n const rows =\n yield* sql`select 1 where ${pending.consumedCondition(\"Login\", input.pending, current.native)}`;\n\n if (rows.length !== 1) return yield* PendingAuthenticationInvalid.make({});\n }\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n } else {\n // A signed credential has no session INSERT that can enforce the\n // original evidence deadline. Check that deadline at this owner.\n const rows =\n yield* sql`select 1 where ${now} >= ${current.now} and ${now} < ${deadline}`;\n\n if (rows.length !== 1) return yield* StaleAuthentication.make({});\n }\n\n return yield* prepareNativeSession(undefined, project);\n }),\n ),\n };\n\n return { authenticationAuthority };\n});\n"],"mappings":"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"}
|
|
@@ -43,7 +43,8 @@ const makeNativePendingAuthenticationServices = Effect.fnUntraced(function* (tab
|
|
|
43
43
|
version
|
|
44
44
|
};
|
|
45
45
|
const snapshot = yield* mapping.login.encode(record);
|
|
46
|
-
|
|
46
|
+
yield* state.guardPolicy(current);
|
|
47
|
+
const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${expires}`;
|
|
47
48
|
if (batch !== void 0) yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));
|
|
48
49
|
yield* pending.insert({
|
|
49
50
|
moduleId: mapping.moduleId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-login.mjs","names":[],"sources":["../../src/internal/session-native-login.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n PendingAuthenticationInvalid,\n StaleAuthentication,\n SessionConflict,\n type PendingAuthentication,\n type PendingAuthenticationRecord,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { PendingAuthenticationMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { prepareNativeSession } from \"./session-native-pending\";\nimport { makeNativeSessionPendingReader } from \"./session-native-pending-read\";\nimport {\n normalizeSessionOperation,\n sessionUnavailable,\n sameSessionRevision,\n assessSessionAt,\n sessionInvariant,\n} from \"./session-native-state\";\nimport { allocateSessionValue } from \"./session-policy\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n appendSqlBatchStatement,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativePendingAuthenticationMapping<Claims> = PendingAuthenticationMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown\n>;\n\nexport const makeNativePendingAuthenticationServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativePendingAuthenticationMapping<Claims>,\n): Effect.fn.Return<\n { readonly pendingAuthentication: PendingAuthentication<Claims> },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const {\n state,\n pending,\n read: readPending,\n } = yield* makeNativeSessionPendingReader(tables, mapping, batch !== undefined);\n\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const { sql, now } = state,\n { p, table } = pending;\n\n const native = <A, E, R>(\n work: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), mode)\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const verifyPayload = Effect.fnUntraced(function* (\n stored: import(\"./session-native-pending\").StoredSessionPending,\n ) {\n const record = yield* mapping.login.decode(stored.snapshot);\n\n sessionInvariant(\n record.digest === stored.digest &&\n record.version === stored.version &&\n record.evidence.flowId === stored.flowId &&\n record.evidence.bindingDigest === stored.bindingDigest &&\n record.evidence.revision.subjectId === stored.subjectId &&\n DateTime.toEpochMillis(record.expiresAt) === stored.expiresAtMillis &&\n record.attemptLimit === stored.attemptLimit,\n );\n\n return record;\n });\n\n const read = Effect.fnUntraced(function* (\n digest: Parameters<PendingAuthentication<Claims>[\"read\"]>[0][\"digest\"],\n ) {\n const selected = yield* readPending(\"Login\", digest);\n\n if (selected === undefined) return undefined;\n const record = yield* verifyPayload(selected.record);\n\n if (!sameSessionRevision(selected.authority.revision, record.evidence.revision))\n return undefined;\n\n return { record, requirement: selected.authority.requirement };\n });\n\n const pendingAuthentication: PendingAuthentication<Claims> = {\n create: (input, _now, project) =>\n native(\n Effect.gen(function* () {\n const current = yield* state.read(input.evidence.revision.subjectId, true);\n\n if (\n current === undefined ||\n !sameSessionRevision(current.revision, input.evidence.revision)\n )\n return yield* StaleAuthentication.make({});\n yield* assessSessionAt(input.evidence, current.requirement, current.now);\n const expires = DateTime.toEpochMillis(input.expiresAt);\n\n if (expires <= current.now) return yield* StaleAuthentication.make({});\n\n const version = yield* allocateSessionValue(\n \"interactive\",\n mapping.pending.allocateVersion,\n mapping.pending.allocateVersionSync,\n );\n\n const record: PendingAuthenticationRecord<Claims> = { ...input, version };\n const snapshot = yield* mapping.login.encode(record);\n const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${state.policyCondition(current.row, current.native)} and ${now} >= ${current.now} and ${now} < ${expires}`;\n\n if (batch !== undefined)\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n yield* pending\n .insert(\n {\n moduleId: mapping.moduleId,\n kind: \"Login\",\n digest: record.digest,\n version,\n flowId: record.evidence.flowId,\n subjectId: current.native,\n bindingDigest: record.evidence.bindingDigest,\n snapshot,\n expiresAt: record.expiresAt,\n attemptLimit: record.attemptLimit,\n },\n condition,\n )\n .pipe(\n Effect.mapError((error) =>\n isMappedConstraintConflict(mapping.isConstraintConflict, error)\n ? SessionConflict.make({})\n : error,\n ),\n );\n const final = sql`${condition} and exists(select 1 from ${table.name} where ${pending.predicate(table, \"Login\", record.digest)} and ${state.exact(table, p.version, version)} and ${state.exact(table, p.snapshot, snapshot)})`;\n\n if (batch !== undefined)\n yield* registerSqlBatchPostcondition({\n name: \"pending-login-issuance\",\n statement: sqlBatchAssertion(sql, final),\n });\n else if (Option.isSome(external))\n yield* registerSqlPostcondition({\n name: \"pending-login-issuance\",\n check: Effect.gen(function* () {\n const rows = yield* sql`select 1 where ${final}`;\n\n sessionInvariant(rows.length === 1);\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepareNativeSession(record, project);\n }),\n ),\n read: ({ digest }) =>\n executor\n .read(read(digest))\n .pipe(\n Effect.flatMap((result) =>\n result === undefined\n ? Effect.fail(PendingAuthenticationInvalid.make({}))\n : Effect.succeed(result),\n ),\n ),\n reject: ({ digest }, project) =>\n native(\n Effect.gen(function* () {\n yield* pending.reject(\"Login\", digest);\n\n return yield* prepareNativeSession({ _tag: \"Rejected\" } as const, project);\n }),\n \"statement\",\n ),\n };\n\n return { pendingAuthentication };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-native-login.mjs","names":[],"sources":["../../src/internal/session-native-login.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport {\n PendingAuthenticationInvalid,\n StaleAuthentication,\n SessionConflict,\n type PendingAuthentication,\n type PendingAuthenticationRecord,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { PendingAuthenticationMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { prepareNativeSession } from \"./session-native-pending\";\nimport { makeNativeSessionPendingReader } from \"./session-native-pending-read\";\nimport {\n normalizeSessionOperation,\n sessionUnavailable,\n sameSessionRevision,\n assessSessionAt,\n sessionInvariant,\n} from \"./session-native-state\";\nimport { allocateSessionValue } from \"./session-policy\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n SqlBatchCommit,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n appendSqlBatchStatement,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativePendingAuthenticationMapping<Claims> = PendingAuthenticationMapping<\n Claims,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown\n>;\n\nexport const makeNativePendingAuthenticationServices = Effect.fnUntraced(function* <Claims>(\n tables: NativeSqlTables,\n mapping: NativePendingAuthenticationMapping<Claims>,\n): Effect.fn.Return<\n { readonly pendingAuthentication: PendingAuthentication<Claims> },\n never,\n SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const {\n state,\n pending,\n read: readPending,\n } = yield* makeNativeSessionPendingReader(tables, mapping, batch !== undefined);\n\n const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const { sql, now } = state,\n { p, table } = pending;\n\n const native = <A, E, R>(\n work: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.operation(normalizeSessionOperation(work), mode)\n : executor\n .operationBatch(normalizeSessionOperation(work))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const verifyPayload = Effect.fnUntraced(function* (\n stored: import(\"./session-native-pending\").StoredSessionPending,\n ) {\n const record = yield* mapping.login.decode(stored.snapshot);\n\n sessionInvariant(\n record.digest === stored.digest &&\n record.version === stored.version &&\n record.evidence.flowId === stored.flowId &&\n record.evidence.bindingDigest === stored.bindingDigest &&\n record.evidence.revision.subjectId === stored.subjectId &&\n DateTime.toEpochMillis(record.expiresAt) === stored.expiresAtMillis &&\n record.attemptLimit === stored.attemptLimit,\n );\n\n return record;\n });\n\n const read = Effect.fnUntraced(function* (\n digest: Parameters<PendingAuthentication<Claims>[\"read\"]>[0][\"digest\"],\n ) {\n const selected = yield* readPending(\"Login\", digest);\n\n if (selected === undefined) return undefined;\n const record = yield* verifyPayload(selected.record);\n\n if (!sameSessionRevision(selected.authority.revision, record.evidence.revision))\n return undefined;\n\n return { record, requirement: selected.authority.requirement };\n });\n\n const pendingAuthentication: PendingAuthentication<Claims> = {\n create: (input, _now, project) =>\n native(\n Effect.gen(function* () {\n const current = yield* state.read(input.evidence.revision.subjectId, true);\n\n if (\n current === undefined ||\n !sameSessionRevision(current.revision, input.evidence.revision)\n )\n return yield* StaleAuthentication.make({});\n yield* assessSessionAt(input.evidence, current.requirement, current.now);\n const expires = DateTime.toEpochMillis(input.expiresAt);\n\n if (expires <= current.now) return yield* StaleAuthentication.make({});\n\n const version = yield* allocateSessionValue(\n \"interactive\",\n mapping.pending.allocateVersion,\n mapping.pending.allocateVersionSync,\n );\n\n const record: PendingAuthenticationRecord<Claims> = { ...input, version };\n const snapshot = yield* mapping.login.encode(record);\n\n yield* state.guardPolicy(current);\n const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${expires}`;\n\n if (batch !== undefined)\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n yield* pending\n .insert(\n {\n moduleId: mapping.moduleId,\n kind: \"Login\",\n digest: record.digest,\n version,\n flowId: record.evidence.flowId,\n subjectId: current.native,\n bindingDigest: record.evidence.bindingDigest,\n snapshot,\n expiresAt: record.expiresAt,\n attemptLimit: record.attemptLimit,\n },\n condition,\n )\n .pipe(\n Effect.mapError((error) =>\n isMappedConstraintConflict(mapping.isConstraintConflict, error)\n ? SessionConflict.make({})\n : error,\n ),\n );\n const final = sql`${condition} and exists(select 1 from ${table.name} where ${pending.predicate(table, \"Login\", record.digest)} and ${state.exact(table, p.version, version)} and ${state.exact(table, p.snapshot, snapshot)})`;\n\n if (batch !== undefined)\n yield* registerSqlBatchPostcondition({\n name: \"pending-login-issuance\",\n statement: sqlBatchAssertion(sql, final),\n });\n else if (Option.isSome(external))\n yield* registerSqlPostcondition({\n name: \"pending-login-issuance\",\n check: Effect.gen(function* () {\n const rows = yield* sql`select 1 where ${final}`;\n\n sessionInvariant(rows.length === 1);\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepareNativeSession(record, project);\n }),\n ),\n read: ({ digest }) =>\n executor\n .read(read(digest))\n .pipe(\n Effect.flatMap((result) =>\n result === undefined\n ? Effect.fail(PendingAuthenticationInvalid.make({}))\n : Effect.succeed(result),\n ),\n ),\n reject: ({ digest }, project) =>\n native(\n Effect.gen(function* () {\n yield* pending.reject(\"Login\", digest);\n\n return yield* prepareNativeSession({ _tag: \"Rejected\" } as const, project);\n }),\n \"statement\",\n ),\n };\n\n return { pendingAuthentication };\n});\n"],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ const makeNativeSessionPendingReader = Effect.fnUntraced(function* (tables, mapp
|
|
|
20
20
|
if (rows[0] === void 0) return void 0;
|
|
21
21
|
const record = yield* pending.decode(kind, pt.decode(rows[0], "p_"));
|
|
22
22
|
const owner = yield* mapping.subjectId.toNative(record.subjectId);
|
|
23
|
-
const authority = yield* state.decode(record.subjectId, owner,
|
|
23
|
+
const authority = yield* state.decode(record.subjectId, owner, rows[0], rows.map((row) => ct.decode(row, "c_")), rows[0].engine_now);
|
|
24
24
|
return authority === void 0 ? void 0 : {
|
|
25
25
|
record,
|
|
26
26
|
authority
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-native-pending-read.mjs","names":[],"sources":["../../src/internal/session-native-pending-read.ts"],"sourcesContent":["import type { TokenDigest } from \"@yielded/auth/Schema\";\nimport type { PendingAuthenticationKind } from \"@yielded/auth/Sessions\";\nimport { Effect } from \"effect\";\n\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeNativeSessionPending } from \"./session-native-pending\";\nimport {\n makeNativeSessionAuthorityState,\n type NativeSessionAuthorityMapping,\n} from \"./session-native-state\";\nimport { exactSqlText } from \"./sql-change\";\n\n/** Both semantic pending ports use the same kind-bound physical snapshot.\n * Distinct native ID encodings use their own bound reads; joined rows still\n * validate each decoded owner before returning current policy. */\nexport const makeNativeSessionPendingReader = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: NativeSessionAuthorityMapping & Parameters<typeof makeNativeSessionPending>[1],\n batch = false,\n) {\n const state = yield* makeNativeSessionAuthorityState(tables, mapping, batch);\n const pending = yield* makeNativeSessionPending(tables, mapping, batch);\n\n const read = Effect.fnUntraced(function* (kind: PendingAuthenticationKind, digest: TokenDigest) {\n const { sql, now } = state,\n { p, table } = pending;\n\n const compatible =\n table.unencodedTextColumn?.(p.subjectId) !== undefined &&\n state.subject.unencodedTextColumn?.(mapping.subject.id) !== undefined &&\n state.credential.unencodedTextColumn?.(mapping.credential.subjectId) !== undefined;\n\n if (compatible) {\n const pt = table.as(\"authentication_pending\"),\n st = state.joinedSubject,\n ct = state.joinedCredential,\n s = mapping.subject,\n c = mapping.credential;\n\n const rows =\n yield* sql`select ${pt.fields(\"p_\")}, ${st.fields(\"s_\")}, ${ct.fields(\"c_\")}, ${now} as engine_now from ${pt.name} join ${st.name} on ${exactSqlText(sql, st.column(s.id), pt.column(p.subjectId))} and ${state.activeSubject(st)} left join ${ct.name} on ${exactSqlText(sql, ct.column(c.subjectId), pt.column(p.subjectId))} and ${state.activeCredential(ct)} where ${pending.predicate(pt, kind, digest)} limit 65`;\n\n if (rows[0] === undefined) return undefined;\n const record = yield* pending.decode(kind, pt.decode(rows[0], \"p_\"));\n const owner = yield* mapping.subjectId.toNative(record.subjectId);\n\n const authority = yield* state.decode(\n record.subjectId,\n owner,\n
|
|
1
|
+
{"version":3,"file":"session-native-pending-read.mjs","names":[],"sources":["../../src/internal/session-native-pending-read.ts"],"sourcesContent":["import type { TokenDigest } from \"@yielded/auth/Schema\";\nimport type { PendingAuthenticationKind } from \"@yielded/auth/Sessions\";\nimport { Effect } from \"effect\";\n\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeNativeSessionPending } from \"./session-native-pending\";\nimport {\n makeNativeSessionAuthorityState,\n type NativeSessionAuthorityMapping,\n} from \"./session-native-state\";\nimport { exactSqlText } from \"./sql-change\";\n\n/** Both semantic pending ports use the same kind-bound physical snapshot.\n * Distinct native ID encodings use their own bound reads; joined rows still\n * validate each decoded owner before returning current policy. */\nexport const makeNativeSessionPendingReader = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: NativeSessionAuthorityMapping & Parameters<typeof makeNativeSessionPending>[1],\n batch = false,\n) {\n const state = yield* makeNativeSessionAuthorityState(tables, mapping, batch);\n const pending = yield* makeNativeSessionPending(tables, mapping, batch);\n\n const read = Effect.fnUntraced(function* (kind: PendingAuthenticationKind, digest: TokenDigest) {\n const { sql, now } = state,\n { p, table } = pending;\n\n const compatible =\n table.unencodedTextColumn?.(p.subjectId) !== undefined &&\n state.subject.unencodedTextColumn?.(mapping.subject.id) !== undefined &&\n state.credential.unencodedTextColumn?.(mapping.credential.subjectId) !== undefined;\n\n if (compatible) {\n const pt = table.as(\"authentication_pending\"),\n st = state.joinedSubject,\n ct = state.joinedCredential,\n s = mapping.subject,\n c = mapping.credential;\n\n const rows =\n yield* sql`select ${pt.fields(\"p_\")}, ${st.fields(\"s_\")}, ${ct.fields(\"c_\")}, ${now} as engine_now from ${pt.name} join ${st.name} on ${exactSqlText(sql, st.column(s.id), pt.column(p.subjectId))} and ${state.activeSubject(st)} left join ${ct.name} on ${exactSqlText(sql, ct.column(c.subjectId), pt.column(p.subjectId))} and ${state.activeCredential(ct)} where ${pending.predicate(pt, kind, digest)} limit 65`;\n\n if (rows[0] === undefined) return undefined;\n const record = yield* pending.decode(kind, pt.decode(rows[0], \"p_\"));\n const owner = yield* mapping.subjectId.toNative(record.subjectId);\n\n const authority = yield* state.decode(\n record.subjectId,\n owner,\n rows[0],\n rows.map((row) => ct.decode(row, \"c_\")),\n rows[0].engine_now,\n );\n\n return authority === undefined ? undefined : { record, authority };\n }\n const selected = yield* pending.read(kind, digest);\n\n if (selected === undefined) return undefined;\n const authority = yield* state.read(selected.record.subjectId);\n\n return authority === undefined || authority.now >= selected.record.expiresAtMillis\n ? undefined\n : { record: selected.record, authority };\n });\n\n return { state, pending, read };\n});\n"],"mappings":";;;;;;;;AAeA,MAAa,iCAAiC,OAAO,WAAW,WAC9D,QACA,SACA,QAAQ,OACR;CACA,MAAM,QAAQ,OAAO,gCAAgC,QAAQ,SAAS,KAAK;CAC3E,MAAM,UAAU,OAAO,yBAAyB,QAAQ,SAAS,KAAK;CA6CtE,OAAO;EAAE;EAAO;EAAS,MA3CZ,OAAO,WAAW,WAAW,MAAiC,QAAqB;GAC9F,MAAM,EAAE,KAAK,QAAQ,OACnB,EAAE,GAAG,UAAU;GAOjB,IAJE,MAAM,sBAAsB,EAAE,SAAS,MAAM,KAAA,KAC7C,MAAM,QAAQ,sBAAsB,QAAQ,QAAQ,EAAE,MAAM,KAAA,KAC5D,MAAM,WAAW,sBAAsB,QAAQ,WAAW,SAAS,MAAM,KAAA,GAE3D;IACd,MAAM,KAAK,MAAM,GAAG,wBAAwB,GAC1C,KAAK,MAAM,eACX,KAAK,MAAM,kBACX,IAAI,QAAQ,SACZ,IAAI,QAAQ;IAEd,MAAM,OACJ,OAAO,GAAG,UAAU,GAAG,OAAO,IAAI,EAAE,IAAI,GAAG,OAAO,IAAI,EAAE,IAAI,GAAG,OAAO,IAAI,EAAE,IAAI,IAAI,sBAAsB,GAAG,KAAK,QAAQ,GAAG,KAAK,MAAM,aAAa,KAAK,GAAG,OAAO,EAAE,EAAE,GAAG,GAAG,OAAO,EAAE,SAAS,CAAC,EAAE,OAAO,MAAM,cAAc,EAAE,EAAE,aAAa,GAAG,KAAK,MAAM,aAAa,KAAK,GAAG,OAAO,EAAE,SAAS,GAAG,GAAG,OAAO,EAAE,SAAS,CAAC,EAAE,OAAO,MAAM,iBAAiB,EAAE,EAAE,SAAS,QAAQ,UAAU,IAAI,MAAM,MAAM,EAAE;IAEhZ,IAAI,KAAK,OAAO,KAAA,GAAW,OAAO,KAAA;IAClC,MAAM,SAAS,OAAO,QAAQ,OAAO,MAAM,GAAG,OAAO,KAAK,IAAI,IAAI,CAAC;IACnE,MAAM,QAAQ,OAAO,QAAQ,UAAU,SAAS,OAAO,SAAS;IAEhE,MAAM,YAAY,OAAO,MAAM,OAC7B,OAAO,WACP,OACA,KAAK,IACL,KAAK,KAAK,QAAQ,GAAG,OAAO,KAAK,IAAI,CAAC,GACtC,KAAK,EAAE,CAAC,UACV;IAEA,OAAO,cAAc,KAAA,IAAY,KAAA,IAAY;KAAE;KAAQ;IAAU;GACnE;GACA,MAAM,WAAW,OAAO,QAAQ,KAAK,MAAM,MAAM;GAEjD,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;GACnC,MAAM,YAAY,OAAO,MAAM,KAAK,SAAS,OAAO,SAAS;GAE7D,OAAO,cAAc,KAAA,KAAa,UAAU,OAAO,SAAS,OAAO,kBAC/D,KAAA,IACA;IAAE,QAAQ,SAAS;IAAQ;GAAU;EAC3C,CAE4B;CAAE;AAChC,CAAC"}
|