@yielded/auth-persistence 0.1.0-beta.18 → 0.1.0-beta.19
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 +5 -2
- package/dist/Adapter.d.mts +2 -2
- package/dist/Adapter.mjs +2 -2
- package/dist/internal/configuration.d.mts +4 -3
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/crypto.mjs +12 -0
- package/dist/internal/crypto.mjs.map +1 -0
- package/dist/internal/passkey/admission.mjs +19 -15
- package/dist/internal/passkey/admission.mjs.map +1 -1
- package/dist/internal/passkey/credentials.mjs +4 -4
- package/dist/internal/passkey/credentials.mjs.map +1 -1
- package/dist/internal/passkey/enrollment.mjs +6 -6
- package/dist/internal/passkey/enrollment.mjs.map +1 -1
- package/dist/internal/passkey/flow.mjs +7 -7
- package/dist/internal/passkey/flow.mjs.map +1 -1
- package/dist/internal/passkey/management.mjs +2 -2
- package/dist/internal/passkey/management.mjs.map +1 -1
- package/dist/internal/passkey/registration-ceremony.mjs +2 -2
- package/dist/internal/passkey/registration-ceremony.mjs.map +1 -1
- package/dist/internal/passkey/registration-custody.mjs +1 -1
- package/dist/internal/passkey/registration-custody.mjs.map +1 -1
- package/dist/internal/passkey/registration-write.mjs +12 -12
- package/dist/internal/passkey/registration-write.mjs.map +1 -1
- package/dist/internal/passkey/state.d.mts +1 -1
- package/dist/internal/passkey/state.mjs +8 -9
- package/dist/internal/passkey/state.mjs.map +1 -1
- package/dist/internal/passkey/target.d.mts +1 -1
- package/dist/internal/passkey/target.mjs.map +1 -1
- package/dist/internal/passkey/write-state.mjs +13 -13
- package/dist/internal/passkey/write-state.mjs.map +1 -1
- package/dist/internal/passkey/write-target.mjs.map +1 -1
- package/dist/internal/passkey-kernel.d.mts +512 -56
- package/dist/internal/passkeys.mjs +32 -14
- package/dist/internal/passkeys.mjs.map +1 -1
- package/dist/internal/password-kernel.d.mts +1 -1
- package/dist/internal/persistence.d.mts +2 -2
- package/dist/internal/persistence.mjs +16 -7
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/phone-kernel.d.mts +5 -5
- package/dist/internal/phone-kernel.mjs +14 -14
- package/dist/internal/phone-kernel.mjs.map +1 -1
- package/dist/internal/registration.mjs +8 -7
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/storage-mapping.d.mts +5 -4
- package/dist/internal/storage-mapping.mjs +16 -13
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +13 -3
- package/dist/internal/storage-validation.mjs +128 -68
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-execution-kernel.d.mts +4 -4
- package/dist/internal/transaction-execution-kernel.mjs +7 -5
- package/dist/internal/transaction-execution-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +7 -1
- package/src/internal/configuration.ts +4 -2
- package/src/internal/crypto.ts +15 -0
- package/src/internal/passkey/admission.ts +22 -15
- package/src/internal/passkey/credentials.ts +4 -4
- package/src/internal/passkey/enrollment.ts +7 -7
- package/src/internal/passkey/flow.ts +8 -8
- package/src/internal/passkey/management.ts +7 -2
- package/src/internal/passkey/registration-ceremony.ts +2 -2
- package/src/internal/passkey/registration-custody.ts +1 -1
- package/src/internal/passkey/registration-write.ts +12 -12
- package/src/internal/passkey/state.ts +10 -13
- package/src/internal/passkey/target.ts +16 -8
- package/src/internal/passkey/write-state.ts +10 -14
- package/src/internal/passkey/write-target.ts +11 -3
- package/src/internal/passkeys.ts +121 -70
- package/src/internal/persistence.ts +28 -11
- package/src/internal/phone-kernel.ts +26 -17
- package/src/internal/registration.ts +10 -8
- package/src/internal/storage-mapping.ts +21 -13
- package/src/internal/storage-validation.ts +219 -139
- package/src/internal/transaction-execution-kernel.ts +30 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction-execution-kernel.mjs","names":[],"sources":["../../src/internal/transaction-execution-kernel.ts"],"sourcesContent":["import {\n coordinateCommit,\n hasCommitScope,\n CurrentCommitJournal,\n type LifecycleHooks,\n type HookConfigurationError,\n} from \"@yielded/auth/Hooks\";\n/* oxlint-disable no-explicit-any -- existing storage kernels erase foreign table shapes; domain errors remain typed. */\n/* oxlint-disable no-explicit-any -- private execution bridge retains exact public driver wrappers. */\nimport { Context, Effect } from \"effect\";\nimport type * as SqlClient from \"effect/sql/SqlClient\";\nimport type * as SqlError from \"effect/sql/SqlError\";\nimport type { Statement } from \"effect/sql/Statement\";\n\nimport { requireStandalone } from \"./standalone\";\nimport {\n NativeDatabase,\n type TransactionNativeDatabase,\n type TransactionOwner,\n type makeTransactionKernel,\n} from \"./transaction-kernel\";\n\nexport interface TransactionTargetConfiguration<Failure> {\n readonly mode: \"interactive\" | \"synchronous\" | \"batch\";\n readonly dialect: \"pg\" | \"mysql\" | \"sqlite\";\n readonly locking: boolean;\n /** The captured database's transaction marker; batch drivers may omit it. */\n readonly standaloneGuard: (\n transactionService: SqlClient.SqlClient[\"transactionService\"] | undefined,\n ) => Effect.Effect<void, Failure>;\n readonly transaction?: <A, E, R>(\n database: any,\n body: (transaction: any) => Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E | SqlError.SqlError | Failure, R>;\n}\n\nexport type TransactionCoordinatorError<E, Failure> =\n | E\n | Failure\n | HookConfigurationError\n | SqlError.SqlError;\n\nexport interface TransactionBound<Failure> {\n readonly owner: TransactionOwner<Failure>;\n used: boolean;\n poisoned: boolean;\n active: boolean;\n inFlight: number;\n}\n\nexport interface TransactionExecution<Failure, OwnerId, Requirements = LifecycleHooks> {\n readonly bound: boolean;\n readonly active: () => boolean;\n readonly admit: Effect.Effect<void, Failure>;\n readonly poison: () => void;\n readonly run: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n mutation?: boolean,\n ) => Effect.Effect<A, Failure, Exclude<R, OwnerId | CurrentCommitJournal> | Requirements>;\n}\n\nexport const makeTransactionExecutionKernel = (\n transactions: Pick<\n ReturnType<typeof makeTransactionKernel>,\n \"makeTransactionOwner\" | \"reportTransactionFailure\"\n >,\n) => {\n const { makeTransactionOwner, reportTransactionFailure } = transactions;\n\n const sqlClientTransactionStandaloneGuard = <Failure>(\n unavailable: () => Failure,\n transactionService: SqlClient.SqlClient[\"transactionService\"] | undefined,\n ) => requireStandalone(unavailable, transactionService);\n\n const makeTransactionExecution = Effect.fnUntraced(function* <Failure, OwnerId>(\n ownerTag: Context.Key<OwnerId, TransactionOwner<Failure>>,\n configuration: TransactionTargetConfiguration<Failure>,\n unavailable: () => Failure,\n nonce: () => string,\n bound?: TransactionBound<Failure>,\n ): Effect.fn.Return<TransactionExecution<Failure, OwnerId>, never, NativeDatabase> {\n const database = yield* NativeDatabase;\n\n const invariant: (value: unknown) => asserts value = (value) => {\n if (!value) throw unavailable();\n };\n\n return {\n bound: bound !== undefined,\n active: () => bound === undefined || bound.active,\n admit:\n bound !== undefined\n ? Effect.suspend(() => (bound.active ? Effect.void : Effect.fail(unavailable())))\n : Effect.gen(function* () {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* configuration.standaloneGuard(database.$client.transactionService);\n }),\n poison: () => {\n if (bound !== undefined) bound.poisoned = true;\n },\n run: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n mutation = true,\n ): Effect.Effect<A, Failure, Exclude<R, OwnerId | CurrentCommitJournal> | LifecycleHooks> => {\n return Effect.suspend(() => {\n let entered = false;\n\n const work =\n bound !== undefined\n ? Effect.gen(function* () {\n if (!bound.active) return yield* Effect.fail(unavailable());\n entered = true;\n bound.inFlight++;\n if (!mutation)\n return yield* Effect.provideContext(\n body,\n Context.make(ownerTag, bound.owner).pipe(\n Context.add(CurrentCommitJournal, bound.owner.journal),\n ),\n );\n invariant(!bound.used && !bound.poisoned);\n bound.used = true;\n\n const result = yield* coordinateCommit(\n (journal) =>\n Effect.gen(function* () {\n bound.owner.guards.push(journal.prepare(undefined));\n\n return yield* Effect.provideContext(\n body,\n Context.make(ownerTag, { ...bound.owner, journal }).pipe(\n Context.add(CurrentCommitJournal, journal),\n ),\n );\n }),\n { mode: configuration.mode },\n );\n\n return result.value;\n }).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n if (entered) bound.inFlight--;\n if (exit._tag === \"Failure\") bound.poisoned = true;\n }),\n ),\n )\n : Effect.gen(function* () {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* configuration.standaloneGuard(database.$client.transactionService);\n const marker = nonce();\n\n const result = yield* coordinateCommit(\n (journal) => {\n const run = (tx: TransactionNativeDatabase) =>\n Effect.gen(function* () {\n const owner = makeTransactionOwner(tx, journal, marker, unavailable, {\n client: database.$client,\n batch: configuration.mode === \"batch\",\n locking: configuration.locking,\n mysql: configuration.dialect === \"mysql\",\n dialect: configuration.dialect,\n });\n\n const value = yield* Effect.provideContext(\n body,\n Context.make(ownerTag, owner).pipe(\n Context.add(CurrentCommitJournal, journal),\n ),\n );\n\n yield* owner.finish();\n if (configuration.mode === \"batch\")\n yield* tx.$client.batch(owner.statements);\n\n return value;\n });\n\n return configuration.mode === \"batch\"\n ? run(database)\n : configuration.transaction !== undefined\n ? configuration.transaction(database, run)\n : database.transaction(run);\n },\n { mode: configuration.mode },\n );\n\n return result.value;\n });\n\n return work.pipe(\n (operation) => reportTransactionFailure(operation, unavailable),\n Effect.mapError(unavailable),\n Effect.catchDefect(() => Effect.fail(unavailable())),\n );\n });\n },\n };\n });\n\n const coordinateTransactionOwner = <\n Failure,\n OwnerId,\n Resources,\n AllocationError,\n AllocationRequirements,\n Services,\n Transaction,\n A,\n E,\n R,\n >(\n database: TransactionNativeDatabase,\n ownerTag: Context.Key<OwnerId, TransactionOwner<Failure>>,\n configuration: TransactionTargetConfiguration<Failure>,\n allocate: Effect.Effect<Resources, AllocationError, AllocationRequirements>,\n unavailable: () => Failure,\n nonce: () => string,\n services: (execution: TransactionExecution<Failure, OwnerId>, resources: Resources) => Services,\n owner: (\n transaction: Transaction,\n services: Services,\n append: (statement: Statement<any>) => void,\n ) => Effect.Effect<A, E, R>,\n ): Effect.Effect<\n A,\n TransactionCoordinatorError<E, Failure>,\n Exclude<R, CurrentCommitJournal> | LifecycleHooks | AllocationRequirements\n > => {\n return Effect.gen(function* () {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* configuration.standaloneGuard(database.$client.transactionService);\n\n // Allocation is independent of registration data and always precedes the physical owner.\n const resources = yield* allocate.pipe(\n (operation) => reportTransactionFailure(operation, unavailable),\n Effect.mapError(unavailable),\n Effect.catchDefect(() => Effect.fail(unavailable())),\n );\n\n const marker = nonce();\n\n const result = yield* coordinateCommit(\n (journal) => {\n const run = (tx: TransactionNativeDatabase) =>\n Effect.gen(function* () {\n const state = makeTransactionOwner(tx, journal, marker, unavailable, {\n client: database.$client,\n batch: configuration.mode === \"batch\",\n dialect: configuration.dialect,\n mysql: configuration.dialect === \"mysql\",\n locking: configuration.locking,\n });\n\n const bound: TransactionBound<Failure> = {\n owner: state,\n used: false,\n poisoned: false,\n active: true,\n inFlight: 0,\n };\n\n return yield* Effect.gen(function* () {\n const value = yield* owner(\n tx as Transaction,\n services(\n yield* makeTransactionExecution(\n ownerTag,\n configuration,\n unavailable,\n nonce,\n bound,\n ).pipe(Effect.provideService(NativeDatabase, tx)),\n resources,\n ),\n (statement) => {\n if (!bound.active) throw unavailable();\n state.statements.push(statement);\n },\n );\n\n bound.active = false;\n if (bound.poisoned || bound.inFlight !== 0)\n return yield* Effect.fail(unavailable());\n yield* state.finish();\n if (configuration.mode === \"batch\") yield* tx.$client.batch(state.statements);\n\n return value;\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n bound.active = false;\n }),\n ),\n );\n });\n\n return configuration.mode === \"batch\"\n ? run(database)\n : configuration.transaction !== undefined\n ? configuration.transaction(database, run)\n : database.transaction(run);\n },\n { mode: configuration.mode },\n );\n\n return result.value as A;\n });\n };\n\n return {\n sqlClientTransactionStandaloneGuard,\n makeTransactionExecution,\n coordinateTransactionOwner,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transaction-execution-kernel.mjs","names":[],"sources":["../../src/internal/transaction-execution-kernel.ts"],"sourcesContent":["import {\n coordinateCommit,\n hasCommitScope,\n CurrentCommitJournal,\n type LifecycleHooks,\n type HookConfigurationError,\n} from \"@yielded/auth/Hooks\";\n/* oxlint-disable no-explicit-any -- existing storage kernels erase foreign table shapes; domain errors remain typed. */\n/* oxlint-disable no-explicit-any -- private execution bridge retains exact public driver wrappers. */\nimport { Context, Crypto, Effect, type PlatformError } from \"effect\";\nimport type * as SqlClient from \"effect/sql/SqlClient\";\nimport type * as SqlError from \"effect/sql/SqlError\";\nimport type { Statement } from \"effect/sql/Statement\";\n\nimport { requireStandalone } from \"./standalone\";\nimport {\n NativeDatabase,\n type TransactionNativeDatabase,\n type TransactionOwner,\n type makeTransactionKernel,\n} from \"./transaction-kernel\";\n\nexport interface TransactionTargetConfiguration<Failure> {\n readonly mode: \"interactive\" | \"synchronous\" | \"batch\";\n readonly dialect: \"pg\" | \"mysql\" | \"sqlite\";\n readonly locking: boolean;\n /** The captured database's transaction marker; batch drivers may omit it. */\n readonly standaloneGuard: (\n transactionService: SqlClient.SqlClient[\"transactionService\"] | undefined,\n ) => Effect.Effect<void, Failure>;\n readonly transaction?: <A, E, R>(\n database: any,\n body: (transaction: any) => Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E | SqlError.SqlError | Failure, R>;\n}\n\nexport type TransactionCoordinatorError<E, Failure> =\n | E\n | Failure\n | HookConfigurationError\n | SqlError.SqlError;\n\nexport interface TransactionBound<Failure> {\n readonly owner: TransactionOwner<Failure>;\n used: boolean;\n poisoned: boolean;\n active: boolean;\n inFlight: number;\n}\n\nexport interface TransactionExecution<Failure, OwnerId, Requirements = LifecycleHooks> {\n readonly bound: boolean;\n readonly active: () => boolean;\n readonly admit: Effect.Effect<void, Failure>;\n readonly poison: () => void;\n readonly run: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n mutation?: boolean,\n ) => Effect.Effect<\n A,\n Failure,\n Exclude<Exclude<R, OwnerId | CurrentCommitJournal>, Crypto.Crypto> | Requirements\n >;\n}\n\nexport const makeTransactionExecutionKernel = (\n transactions: Pick<\n ReturnType<typeof makeTransactionKernel>,\n \"makeTransactionOwner\" | \"reportTransactionFailure\"\n >,\n) => {\n const { makeTransactionOwner, reportTransactionFailure } = transactions;\n\n const sqlClientTransactionStandaloneGuard = <Failure>(\n unavailable: () => Failure,\n transactionService: SqlClient.SqlClient[\"transactionService\"] | undefined,\n ) => requireStandalone(unavailable, transactionService);\n\n const makeTransactionExecution = Effect.fnUntraced(function* <Failure, OwnerId>(\n ownerTag: Context.Key<OwnerId, TransactionOwner<Failure>>,\n configuration: TransactionTargetConfiguration<Failure>,\n unavailable: () => Failure,\n nonce: Effect.Effect<string, PlatformError.PlatformError, Crypto.Crypto>,\n bound?: TransactionBound<Failure>,\n ): Effect.fn.Return<\n TransactionExecution<Failure, OwnerId>,\n never,\n NativeDatabase | Crypto.Crypto\n > {\n const database = yield* NativeDatabase;\n const crypto = yield* Crypto.Crypto;\n\n const invariant: (value: unknown) => asserts value = (value) => {\n if (!value) throw unavailable();\n };\n\n return {\n bound: bound !== undefined,\n active: () => bound === undefined || bound.active,\n admit:\n bound !== undefined\n ? Effect.suspend(() => (bound.active ? Effect.void : Effect.fail(unavailable())))\n : Effect.gen(function* () {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* configuration.standaloneGuard(database.$client.transactionService);\n }),\n poison: () => {\n if (bound !== undefined) bound.poisoned = true;\n },\n run: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n mutation = true,\n ): Effect.Effect<\n A,\n Failure,\n Exclude<Exclude<R, OwnerId | CurrentCommitJournal>, Crypto.Crypto> | LifecycleHooks\n > => {\n return Effect.suspend(() => {\n let entered = false;\n\n const work: Effect.Effect<\n A,\n E | Failure | HookConfigurationError | SqlError.SqlError | PlatformError.PlatformError,\n Exclude<R, OwnerId | CurrentCommitJournal> | LifecycleHooks | Crypto.Crypto\n > =\n bound !== undefined\n ? Effect.gen(function* () {\n if (!bound.active) return yield* Effect.fail(unavailable());\n entered = true;\n bound.inFlight++;\n if (!mutation)\n return yield* Effect.provideContext(\n body,\n Context.make(ownerTag, bound.owner).pipe(\n Context.add(CurrentCommitJournal, bound.owner.journal),\n ),\n );\n invariant(!bound.used && !bound.poisoned);\n bound.used = true;\n\n const result = yield* coordinateCommit(\n (journal) =>\n Effect.gen(function* () {\n bound.owner.guards.push(journal.prepare(undefined));\n\n return yield* Effect.provideContext(\n body,\n Context.make(ownerTag, { ...bound.owner, journal }).pipe(\n Context.add(CurrentCommitJournal, journal),\n ),\n );\n }),\n { mode: configuration.mode },\n );\n\n return result.value;\n }).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n if (entered) bound.inFlight--;\n if (exit._tag === \"Failure\") bound.poisoned = true;\n }),\n ),\n )\n : Effect.gen(function* () {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* configuration.standaloneGuard(database.$client.transactionService);\n const marker = yield* nonce;\n\n const result = yield* coordinateCommit(\n (journal) => {\n const run = (tx: TransactionNativeDatabase) =>\n Effect.gen(function* () {\n const owner = makeTransactionOwner(tx, journal, marker, unavailable, {\n client: database.$client,\n batch: configuration.mode === \"batch\",\n locking: configuration.locking,\n mysql: configuration.dialect === \"mysql\",\n dialect: configuration.dialect,\n });\n\n const value = yield* Effect.provideContext(\n body,\n Context.make(ownerTag, owner).pipe(\n Context.add(CurrentCommitJournal, journal),\n ),\n );\n\n yield* owner.finish();\n if (configuration.mode === \"batch\")\n yield* tx.$client.batch(owner.statements);\n\n return value;\n });\n\n return configuration.mode === \"batch\"\n ? run(database)\n : configuration.transaction !== undefined\n ? configuration.transaction(database, run)\n : database.transaction(run);\n },\n { mode: configuration.mode },\n );\n\n return result.value;\n });\n\n return work.pipe(\n Effect.provideService(Crypto.Crypto, crypto),\n (operation) => reportTransactionFailure(operation, unavailable),\n Effect.mapError(unavailable),\n Effect.catchDefect(() => Effect.fail(unavailable())),\n );\n });\n },\n };\n });\n\n const coordinateTransactionOwner = <\n Failure,\n OwnerId,\n Resources,\n AllocationError,\n AllocationRequirements,\n Services,\n Transaction,\n A,\n E,\n R,\n >(\n database: TransactionNativeDatabase,\n ownerTag: Context.Key<OwnerId, TransactionOwner<Failure>>,\n configuration: TransactionTargetConfiguration<Failure>,\n allocate: Effect.Effect<Resources, AllocationError, AllocationRequirements>,\n unavailable: () => Failure,\n nonce: Effect.Effect<string, PlatformError.PlatformError, Crypto.Crypto>,\n services: (execution: TransactionExecution<Failure, OwnerId>, resources: Resources) => Services,\n owner: (\n transaction: Transaction,\n services: Services,\n append: (statement: Statement<any>) => void,\n ) => Effect.Effect<A, E, R>,\n ): Effect.Effect<\n A,\n TransactionCoordinatorError<E, Failure>,\n Exclude<R, CurrentCommitJournal> | LifecycleHooks | AllocationRequirements | Crypto.Crypto\n > => {\n return Effect.gen(function* () {\n if (yield* hasCommitScope) return yield* Effect.fail(unavailable());\n yield* configuration.standaloneGuard(database.$client.transactionService);\n const crypto = yield* Crypto.Crypto;\n\n // Allocation is independent of registration data and always precedes the physical owner.\n const resources = yield* allocate.pipe(\n (operation) => reportTransactionFailure(operation, unavailable),\n Effect.mapError(unavailable),\n Effect.catchDefect(() => Effect.fail(unavailable())),\n );\n\n const marker = yield* nonce.pipe(Effect.mapError(unavailable));\n\n const result = yield* coordinateCommit(\n (journal) => {\n const run = (tx: TransactionNativeDatabase) =>\n Effect.gen(function* () {\n const state = makeTransactionOwner(tx, journal, marker, unavailable, {\n client: database.$client,\n batch: configuration.mode === \"batch\",\n dialect: configuration.dialect,\n mysql: configuration.dialect === \"mysql\",\n locking: configuration.locking,\n });\n\n const bound: TransactionBound<Failure> = {\n owner: state,\n used: false,\n poisoned: false,\n active: true,\n inFlight: 0,\n };\n\n return yield* Effect.gen(function* () {\n const value = yield* owner(\n tx as Transaction,\n services(\n yield* makeTransactionExecution(\n ownerTag,\n configuration,\n unavailable,\n nonce,\n bound,\n ).pipe(Effect.provideService(NativeDatabase, tx)),\n resources,\n ),\n (statement) => {\n if (!bound.active) throw unavailable();\n state.statements.push(statement);\n },\n );\n\n bound.active = false;\n if (bound.poisoned || bound.inFlight !== 0)\n return yield* Effect.fail(unavailable());\n yield* state.finish();\n if (configuration.mode === \"batch\") yield* tx.$client.batch(state.statements);\n\n return value;\n }).pipe(\n Effect.ensuring(\n Effect.sync(() => {\n bound.active = false;\n }),\n ),\n );\n }).pipe(Effect.provideService(Crypto.Crypto, crypto));\n\n return configuration.mode === \"batch\"\n ? run(database)\n : configuration.transaction !== undefined\n ? configuration.transaction(database, run)\n : database.transaction(run);\n },\n { mode: configuration.mode },\n );\n\n return result.value as A;\n });\n };\n\n return {\n sqlClientTransactionStandaloneGuard,\n makeTransactionExecution,\n coordinateTransactionOwner,\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@yielded/auth-persistence","version":"0.1.0-beta.
|
|
1
|
+
{"name":"@yielded/auth-persistence","version":"0.1.0-beta.19","dependencies":{"@yielded/auth":"0.1.0-beta.19"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/sql-libsql":"4.0.0","@effect/sql-sqlite-node":"4.0.0","@effect/vitest":"4.0.0","effect":"4.0.0","typescript":"7.0.2","vite-plus":"0.3.3"},"peerDependencies":{"effect":"^4.0.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Adapter":{"types":"./dist/Adapter.d.mts","default":"./dist/Adapter.mjs"}},"description":"Direct Effect SQL persistence and shared storage contracts for Yielded Auth.","homepage":"https://yielded.dev/auth/","bugs":{"url":"https://github.com/yielded-dev/auth/issues"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/yielded-dev/auth.git","directory":"packages/auth-persistence"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
|
package/src/Adapter.ts
CHANGED
|
@@ -297,4 +297,10 @@ export type {
|
|
|
297
297
|
PersistenceApi,
|
|
298
298
|
} from "./internal/configuration";
|
|
299
299
|
|
|
300
|
-
export {
|
|
300
|
+
export {
|
|
301
|
+
validateStorage,
|
|
302
|
+
validateStorageBatch,
|
|
303
|
+
withStorageValidation,
|
|
304
|
+
type PhysicalStorageTable,
|
|
305
|
+
type StorageValidation,
|
|
306
|
+
} from "./internal/storage-validation";
|
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
AuthenticationRequirement,
|
|
25
25
|
make as makeSessions,
|
|
26
26
|
} from "@yielded/auth/Sessions";
|
|
27
|
-
import { type Context, type Effect, type Layer, Schema } from "effect";
|
|
27
|
+
import { type Context, type Crypto, type Effect, type Layer, Schema } from "effect";
|
|
28
28
|
import type { SqlClient } from "effect/sql";
|
|
29
29
|
|
|
30
30
|
import type { PersistenceMappingError } from "./mapping-error";
|
|
@@ -251,6 +251,7 @@ export interface BoundPersistence<
|
|
|
251
251
|
readonly layer: Layer.Layer<
|
|
252
252
|
Ports<C, Id, A>,
|
|
253
253
|
PersistenceConfigurationError,
|
|
254
|
+
| Crypto.Crypto
|
|
254
255
|
| ConfigId<A["namespace"]>
|
|
255
256
|
| ProvisioningRequirement<A>
|
|
256
257
|
| PasskeyRequirement<A>
|
|
@@ -260,7 +261,8 @@ export interface BoundPersistence<
|
|
|
260
261
|
readonly managed: <N, Instant = number>(options: {
|
|
261
262
|
readonly subjects: SubjectOptions<T, N>;
|
|
262
263
|
readonly tables?: Partial<Record<Roles<C, Id, A>, T>>;
|
|
263
|
-
|
|
264
|
+
/** Stable SQL identifier prefix; reuse the deployed prefix for existing tables. */
|
|
265
|
+
readonly prefix: string;
|
|
264
266
|
readonly timestamps?: TimestampOptions<Instant>;
|
|
265
267
|
}) => StorageLayout<T, Roles<C, Id, A>>;
|
|
266
268
|
readonly map: <N, Instant = number>(options: {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TokenDigest } from "@yielded/auth/Schema";
|
|
2
|
+
import { Crypto, Effect } from "effect";
|
|
3
|
+
import { Base64Url } from "effect/encoding";
|
|
4
|
+
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
|
|
7
|
+
export const randomId = Effect.flatMap(Crypto.Crypto, (crypto) =>
|
|
8
|
+
crypto.randomBytes(32).pipe(Effect.map(Base64Url.encode)),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
/** Persisted UTF-8 SHA-256, encoded as unpadded base64url. */
|
|
12
|
+
export const digest = (value: string) =>
|
|
13
|
+
Effect.flatMap(Crypto.Crypto, (crypto) => crypto.digest("SHA-256", encoder.encode(value))).pipe(
|
|
14
|
+
Effect.map((bytes) => TokenDigest.make(Base64Url.encode(bytes))),
|
|
15
|
+
);
|
|
@@ -31,18 +31,19 @@ export const makePasskeyAdmissionKernel = (
|
|
|
31
31
|
|
|
32
32
|
const chargeRetentionMillis = 86_400_000;
|
|
33
33
|
|
|
34
|
-
const chargeScopes = (ceremony: PasskeyCeremony, subjectId?: string)
|
|
34
|
+
const chargeScopes = Effect.fnUntraced(function* (ceremony: PasskeyCeremony, subjectId?: string) {
|
|
35
35
|
const scopes: { kind: PasskeyChargeKind; scope: string }[] = [
|
|
36
|
-
{ kind: "global", scope: key("global", [ceremony.moduleId]) },
|
|
36
|
+
{ kind: "global", scope: yield* key("global", [ceremony.moduleId]) },
|
|
37
37
|
];
|
|
38
38
|
|
|
39
|
-
if (subjectId !== undefined)
|
|
40
|
-
|
|
39
|
+
if (subjectId !== undefined)
|
|
40
|
+
scopes.push({ kind: "subject", scope: yield* subjectScope(subjectId) });
|
|
41
|
+
const target = yield* targetScope(ceremony);
|
|
41
42
|
|
|
42
43
|
if (target !== null) scopes.push({ kind: "target", scope: target });
|
|
43
44
|
|
|
44
45
|
return scopes;
|
|
45
|
-
};
|
|
46
|
+
});
|
|
46
47
|
|
|
47
48
|
const lockAdmission = Effect.fn("passkey.lockAdmission")(function* (mapping: any) {
|
|
48
49
|
const owner = yield* CurrentPasskeyTransaction;
|
|
@@ -107,7 +108,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
107
108
|
)})`;
|
|
108
109
|
};
|
|
109
110
|
|
|
110
|
-
const admissionCondition = (
|
|
111
|
+
const admissionCondition = Effect.fnUntraced(function* (
|
|
111
112
|
mapping: any,
|
|
112
113
|
policies: ReadonlyArray<PasskeyMethodPolicy>,
|
|
113
114
|
ceremony: PasskeyCeremony,
|
|
@@ -115,19 +116,23 @@ export const makePasskeyAdmissionKernel = (
|
|
|
115
116
|
added: ReadonlyArray<PasskeyChargeKind>,
|
|
116
117
|
newFlow: boolean,
|
|
117
118
|
resolvedSubject: boolean,
|
|
118
|
-
)
|
|
119
|
-
|
|
119
|
+
) {
|
|
120
|
+
const subject = subjectId === undefined ? undefined : yield* subjectScope(subjectId);
|
|
121
|
+
const scopes = yield* chargeScopes(ceremony, subjectId);
|
|
122
|
+
|
|
123
|
+
return both(
|
|
120
124
|
...policies.flatMap((policy) => [
|
|
121
125
|
sql`${pendingCount(mapping)} + ${newFlow ? 1 : 0} <= ${policy.maximumPending}`,
|
|
122
126
|
subjectId === undefined
|
|
123
127
|
? undefined
|
|
124
|
-
: sql`${pendingCount(mapping,
|
|
125
|
-
...
|
|
128
|
+
: sql`${pendingCount(mapping, subject)} + ${newFlow || resolvedSubject ? 1 : 0} <= ${policy.maximumPendingPerSubject}`,
|
|
129
|
+
...scopes.map(
|
|
126
130
|
({ kind, scope }) =>
|
|
127
131
|
sql`${chargedCount(mapping, kind, scope, policy.admission[kind].windowMillis)} + ${added.includes(kind) ? 1 : 0} <= ${policy.admission[kind].limit}`,
|
|
128
132
|
),
|
|
129
133
|
]),
|
|
130
134
|
);
|
|
135
|
+
});
|
|
131
136
|
|
|
132
137
|
const guardAdmission = (
|
|
133
138
|
mapping: any,
|
|
@@ -135,9 +140,11 @@ export const makePasskeyAdmissionKernel = (
|
|
|
135
140
|
ceremony: PasskeyCeremony,
|
|
136
141
|
subjectId?: string,
|
|
137
142
|
) =>
|
|
138
|
-
Effect.
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const owner = yield* CurrentPasskeyTransaction;
|
|
145
|
+
|
|
139
146
|
owner.postconditions.push(
|
|
140
|
-
admissionCondition(mapping, policies, ceremony, subjectId, [], false, false),
|
|
147
|
+
yield* admissionCondition(mapping, policies, ceremony, subjectId, [], false, false),
|
|
141
148
|
);
|
|
142
149
|
});
|
|
143
150
|
|
|
@@ -161,7 +168,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
161
168
|
columns: mappedColumns(table),
|
|
162
169
|
});
|
|
163
170
|
|
|
164
|
-
const scopes = chargeScopes(ceremony, subjectId);
|
|
171
|
+
const scopes = yield* chargeScopes(ceremony, subjectId);
|
|
165
172
|
|
|
166
173
|
invariant(found.rows.length === scopes.length);
|
|
167
174
|
for (const { kind, scope } of scopes) {
|
|
@@ -202,7 +209,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
202
209
|
Effect.flatMap(CurrentPasskeyTransaction, (owner) =>
|
|
203
210
|
Effect.gen(function* () {
|
|
204
211
|
const table = mapping.charge;
|
|
205
|
-
const scopes = chargeScopes(ceremony, subjectId);
|
|
212
|
+
const scopes = yield* chargeScopes(ceremony, subjectId);
|
|
206
213
|
|
|
207
214
|
const where = equal(table.table, {
|
|
208
215
|
[table.moduleId]: ceremony.moduleId,
|
|
@@ -241,7 +248,7 @@ export const makePasskeyAdmissionKernel = (
|
|
|
241
248
|
const earliest = yield* owner.now(clock);
|
|
242
249
|
const identities: Record<string, unknown>[] = [];
|
|
243
250
|
|
|
244
|
-
for (const { kind, scope } of chargeScopes(ceremony, subjectId).filter((item) =>
|
|
251
|
+
for (const { kind, scope } of (yield* chargeScopes(ceremony, subjectId)).filter((item) =>
|
|
245
252
|
kinds.includes(item.kind),
|
|
246
253
|
)) {
|
|
247
254
|
const identity = {
|
|
@@ -172,7 +172,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
172
172
|
const found = yield* owner.read(
|
|
173
173
|
descriptor.table,
|
|
174
174
|
equal(descriptor.table, {
|
|
175
|
-
[descriptor.credentialKey]: credentialKey(rpId, protocolCredentialId),
|
|
175
|
+
[descriptor.credentialKey]: yield* credentialKey(rpId, protocolCredentialId),
|
|
176
176
|
}),
|
|
177
177
|
{ lock: false, observe: false, limit: 1, columns: mappedColumns(descriptor) },
|
|
178
178
|
);
|
|
@@ -257,7 +257,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
257
257
|
) {
|
|
258
258
|
const owner = yield* CurrentPasskeyTransaction;
|
|
259
259
|
const ownership = mapping.credentialOwnership;
|
|
260
|
-
const tupleKey = credentialKey(rpId, protocolCredentialId);
|
|
260
|
+
const tupleKey = yield* credentialKey(rpId, protocolCredentialId);
|
|
261
261
|
|
|
262
262
|
const tuple = (yield* owner.read(
|
|
263
263
|
ownership.table,
|
|
@@ -298,7 +298,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
298
298
|
return undefined;
|
|
299
299
|
let decoded = descriptor.decode(copiedRow(row));
|
|
300
300
|
const handle = mapping.handleOwnership;
|
|
301
|
-
const expectedHandleKey = handleKey(rpId, decoded.userHandle);
|
|
301
|
+
const expectedHandleKey = yield* handleKey(rpId, decoded.userHandle);
|
|
302
302
|
|
|
303
303
|
const handleRow = (yield* owner.read(
|
|
304
304
|
handle.table,
|
|
@@ -452,7 +452,7 @@ export const makePasskeyCredentialsKernel = (
|
|
|
452
452
|
);
|
|
453
453
|
// oxlint-disable-next-line no-restricted-properties -- native SQL column values enter through this untyped driver boundary.
|
|
454
454
|
userHandle = Schema.decodeUnknownSync(PasskeyUserHandle)(handleRow[handle.userHandle]);
|
|
455
|
-
invariant(handleRow[handle.handleKey] === handleKey(input.rpId, userHandle));
|
|
455
|
+
invariant(handleRow[handle.handleKey] === (yield* handleKey(input.rpId, userHandle)));
|
|
456
456
|
}
|
|
457
457
|
const credential = mapping.read.credential;
|
|
458
458
|
|
|
@@ -97,7 +97,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
97
97
|
|
|
98
98
|
const found = yield* owner.read(table.table, where, { limit: 1 });
|
|
99
99
|
const row = found.rows[0];
|
|
100
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
100
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
101
101
|
|
|
102
102
|
if (row !== undefined)
|
|
103
103
|
return (
|
|
@@ -193,7 +193,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
193
193
|
action: "enroll-begin",
|
|
194
194
|
commandId: ceremony.commandId,
|
|
195
195
|
flowId: ceremony.flowId,
|
|
196
|
-
bindingDigest: enrollmentDigest(ceremony),
|
|
196
|
+
bindingDigest: yield* enrollmentDigest(ceremony),
|
|
197
197
|
revision: ceremony.context.revision,
|
|
198
198
|
},
|
|
199
199
|
{
|
|
@@ -207,11 +207,11 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
207
207
|
)
|
|
208
208
|
return { _tag: "Rejected" } as const;
|
|
209
209
|
invariant(
|
|
210
|
-
digest(Ceremony.fields.context, ceremony.context) ===
|
|
211
|
-
digest(Ceremony.fields.context, {
|
|
210
|
+
(yield* digest(Ceremony.fields.context, ceremony.context)) ===
|
|
211
|
+
(yield* digest(Ceremony.fields.context, {
|
|
212
212
|
...ceremony.context,
|
|
213
213
|
authorization: input.authorization,
|
|
214
|
-
}),
|
|
214
|
+
})),
|
|
215
215
|
);
|
|
216
216
|
const issued = yield* issueAssertion(mapping, ceremony, input.policy);
|
|
217
217
|
|
|
@@ -277,7 +277,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
277
277
|
action: "enroll-complete",
|
|
278
278
|
commandId: ceremony.commandId,
|
|
279
279
|
flowId: ceremony.flowId,
|
|
280
|
-
bindingDigest: digest(Ceremony, ceremony),
|
|
280
|
+
bindingDigest: yield* digest(Ceremony, ceremony),
|
|
281
281
|
revision: ceremony.context.revision,
|
|
282
282
|
},
|
|
283
283
|
{
|
|
@@ -293,7 +293,7 @@ export const makePasskeyEnrollmentKernel = (
|
|
|
293
293
|
return yield* reject;
|
|
294
294
|
const tuple = mapping.read.credentialOwnership;
|
|
295
295
|
// Credential ownership is RP-global, including retained registration custody.
|
|
296
|
-
const key = credentialKeyFor(ceremony.profile.rpId, input.verified.protocolCredentialId);
|
|
296
|
+
const key = yield* credentialKeyFor(ceremony.profile.rpId, input.verified.protocolCredentialId);
|
|
297
297
|
|
|
298
298
|
if (
|
|
299
299
|
(yield* owner.read(tuple.table, equal(tuple.table, { [tuple.credentialKey]: key }), {
|
|
@@ -233,8 +233,8 @@ export const makePasskeyFlowKernel = (
|
|
|
233
233
|
const subject = credential?.revision.subjectId ?? knownSubject(ceremony);
|
|
234
234
|
|
|
235
235
|
invariant(
|
|
236
|
-
row[table.subjectScope] === (subject === undefined ? null : subjectScope(subject)) &&
|
|
237
|
-
row[table.targetScope] === targetScope(ceremony),
|
|
236
|
+
row[table.subjectScope] === (subject === undefined ? null : yield* subjectScope(subject)) &&
|
|
237
|
+
row[table.targetScope] === (yield* targetScope(ceremony)),
|
|
238
238
|
);
|
|
239
239
|
if (state === "Pending") {
|
|
240
240
|
invariant(
|
|
@@ -378,12 +378,12 @@ export const makePasskeyFlowKernel = (
|
|
|
378
378
|
|
|
379
379
|
if (sameCommand.rows.length !== 0 || sameFlow !== undefined)
|
|
380
380
|
return { _tag: "Rejected" } as const;
|
|
381
|
-
const kinds = chargeScopes(ceremony, subjectId).map((item) => item.kind);
|
|
381
|
+
const kinds = (yield* chargeScopes(ceremony, subjectId)).map((item) => item.kind);
|
|
382
382
|
|
|
383
383
|
if (
|
|
384
384
|
!(yield* owner.check(liveCondition(mapping, ceremony))) ||
|
|
385
385
|
!(yield* owner.check(
|
|
386
|
-
admissionCondition(mapping, [current], ceremony, subjectId, kinds, true, false),
|
|
386
|
+
yield* admissionCondition(mapping, [current], ceremony, subjectId, kinds, true, false),
|
|
387
387
|
))
|
|
388
388
|
)
|
|
389
389
|
return { _tag: "Rejected" } as const;
|
|
@@ -410,8 +410,8 @@ export const makePasskeyFlowKernel = (
|
|
|
410
410
|
[table.claimedAt]: null,
|
|
411
411
|
[table.claimExpiresAt]: null,
|
|
412
412
|
[table.credentialSnapshot]: null,
|
|
413
|
-
[table.subjectScope]: subjectId === undefined ? null : subjectScope(subjectId),
|
|
414
|
-
[table.targetScope]: targetScope(ceremony),
|
|
413
|
+
[table.subjectScope]: subjectId === undefined ? null : yield* subjectScope(subjectId),
|
|
414
|
+
[table.targetScope]: yield* targetScope(ceremony),
|
|
415
415
|
};
|
|
416
416
|
|
|
417
417
|
// Empty-key observations now expect this exact inserted row as well.
|
|
@@ -554,7 +554,7 @@ export const makePasskeyFlowKernel = (
|
|
|
554
554
|
|
|
555
555
|
if (
|
|
556
556
|
!(yield* owner.check(
|
|
557
|
-
admissionCondition(
|
|
557
|
+
yield* admissionCondition(
|
|
558
558
|
mapping,
|
|
559
559
|
[read.policy, current],
|
|
560
560
|
read.ceremony,
|
|
@@ -591,7 +591,7 @@ export const makePasskeyFlowKernel = (
|
|
|
591
591
|
[table.claimExpiresAt]: mapping.clock.encodeInstant(claim.claimExpiresAtMillis),
|
|
592
592
|
[table.credentialSnapshot]:
|
|
593
593
|
input.credential === undefined ? null : credentialStorage.encode(input.credential),
|
|
594
|
-
[table.subjectScope]: subjectId === undefined ? null : subjectScope(subjectId),
|
|
594
|
+
[table.subjectScope]: subjectId === undefined ? null : yield* subjectScope(subjectId),
|
|
595
595
|
},
|
|
596
596
|
);
|
|
597
597
|
if (resolved) yield* insertCharges(mapping, read.ceremony, read.policy, ["subject"], subjectId);
|
|
@@ -335,7 +335,7 @@ export const makePasskeyManagementKernel = (
|
|
|
335
335
|
action: "remove",
|
|
336
336
|
commandId: input.commandId,
|
|
337
337
|
flowId: input.commandId,
|
|
338
|
-
bindingDigest: removeDigest(mapping.moduleId, input.commandId, input.credential),
|
|
338
|
+
bindingDigest: yield* removeDigest(mapping.moduleId, input.commandId, input.credential),
|
|
339
339
|
revision: input.credential.revision,
|
|
340
340
|
},
|
|
341
341
|
{
|
|
@@ -384,7 +384,12 @@ export const makePasskeyManagementKernel = (
|
|
|
384
384
|
|
|
385
385
|
yield* owner.update(
|
|
386
386
|
tuple.table,
|
|
387
|
-
{
|
|
387
|
+
{
|
|
388
|
+
[tuple.credentialKey]: yield* credentialKey(
|
|
389
|
+
credential.rpId,
|
|
390
|
+
credential.protocolCredentialId,
|
|
391
|
+
),
|
|
392
|
+
},
|
|
388
393
|
{
|
|
389
394
|
[tuple.state]: mapping.write.credentialOwnership.removedState,
|
|
390
395
|
[tuple.version]: owner.marker,
|
|
@@ -78,7 +78,7 @@ export const makePasskeyRegistrationCeremonyKernel = (
|
|
|
78
78
|
})).rows[0];
|
|
79
79
|
|
|
80
80
|
if (advisory === undefined) return false;
|
|
81
|
-
const hashedHandle = handleKey(ceremony.profile.rpId, context.userHandle);
|
|
81
|
+
const hashedHandle = yield* handleKey(ceremony.profile.rpId, context.userHandle);
|
|
82
82
|
|
|
83
83
|
const held = (yield* owner.read(
|
|
84
84
|
handle.table,
|
|
@@ -204,7 +204,7 @@ export const makePasskeyRegistrationCeremonyKernel = (
|
|
|
204
204
|
policyStorage.encode(current) !== policyStorage.encode(input.policy) ||
|
|
205
205
|
!(yield* owner.check(liveCondition(mapping, read.ceremony))) ||
|
|
206
206
|
!(yield* owner.check(
|
|
207
|
-
admissionCondition(
|
|
207
|
+
yield* admissionCondition(
|
|
208
208
|
mapping,
|
|
209
209
|
[read.policy, current],
|
|
210
210
|
read.ceremony,
|
|
@@ -26,7 +26,7 @@ export const makePasskeyRegistrationCustodyKernel = (
|
|
|
26
26
|
const owner = yield* CurrentPasskeyTransaction;
|
|
27
27
|
const handle = mapping.handle;
|
|
28
28
|
const intent = mapping.intent;
|
|
29
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
29
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
30
30
|
|
|
31
31
|
const held = (yield* owner.read(
|
|
32
32
|
handle.table,
|
|
@@ -67,7 +67,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
67
67
|
const { digest, insertCredential, jsonStorage, validRegistration } = writeState;
|
|
68
68
|
const { both } = transactions;
|
|
69
69
|
|
|
70
|
-
const registrationData = (mapping: any, registration: any)
|
|
70
|
+
const registrationData = Effect.fnUntraced(function* (mapping: any, registration: any) {
|
|
71
71
|
const codec = jsonStorage(mapping.registration.schema);
|
|
72
72
|
const encoded = codec.encode(registration);
|
|
73
73
|
|
|
@@ -76,26 +76,26 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
76
76
|
return {
|
|
77
77
|
encoded,
|
|
78
78
|
registration: codec.decode(encoded),
|
|
79
|
-
fingerprint: digest(mapping.registration.schema, registration),
|
|
79
|
+
fingerprint: yield* digest(mapping.registration.schema, registration),
|
|
80
80
|
};
|
|
81
|
-
};
|
|
81
|
+
});
|
|
82
82
|
|
|
83
|
-
const describe = (mapping: any, registration: any)
|
|
84
|
-
const data = registrationData(mapping, registration);
|
|
83
|
+
const describe = Effect.fnUntraced(function* (mapping: any, registration: any) {
|
|
84
|
+
const data = yield* registrationData(mapping, registration);
|
|
85
85
|
|
|
86
86
|
const labels = Schema.decodeSync(
|
|
87
87
|
Schema.Struct({ name: PasskeyLabel, displayName: PasskeyLabel }),
|
|
88
88
|
)(mapping.registration.describe(data.registration));
|
|
89
89
|
|
|
90
90
|
return { ...data, ...labels };
|
|
91
|
-
};
|
|
91
|
+
});
|
|
92
92
|
|
|
93
93
|
const inspectRegistration = Effect.fn("passkey.inspectRegistration")(function* (
|
|
94
94
|
mapping: any,
|
|
95
95
|
registration: any,
|
|
96
96
|
) {
|
|
97
97
|
const owner = yield* CurrentPasskeyTransaction;
|
|
98
|
-
const value = describe(mapping, registration);
|
|
98
|
+
const value = yield* describe(mapping, registration);
|
|
99
99
|
const current = yield* readModule(mapping);
|
|
100
100
|
|
|
101
101
|
yield* readPolicyGuards(mapping);
|
|
@@ -133,7 +133,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
133
133
|
ceremony.allowedCredentials.length !== 0
|
|
134
134
|
)
|
|
135
135
|
return { _tag: "Rejected" } as const;
|
|
136
|
-
const value = describe(mapping, input.registration);
|
|
136
|
+
const value = yield* describe(mapping, input.registration);
|
|
137
137
|
|
|
138
138
|
if (
|
|
139
139
|
value.fingerprint !== ceremony.context.fingerprint ||
|
|
@@ -148,7 +148,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
148
148
|
|
|
149
149
|
if (!(yield* owner.check(eligible))) return { _tag: "Rejected" } as const;
|
|
150
150
|
const handle = mapping.handle;
|
|
151
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
151
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
152
152
|
|
|
153
153
|
const absentHandle = yield* owner.read(
|
|
154
154
|
handle.table,
|
|
@@ -259,7 +259,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
259
259
|
const absentCredential = yield* owner.read(
|
|
260
260
|
tuple.table,
|
|
261
261
|
equal(tuple.table, {
|
|
262
|
-
[tuple.credentialKey]: credentialKey(
|
|
262
|
+
[tuple.credentialKey]: yield* credentialKey(
|
|
263
263
|
ceremony.profile.rpId,
|
|
264
264
|
input.verified.protocolCredentialId,
|
|
265
265
|
),
|
|
@@ -268,7 +268,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
268
268
|
);
|
|
269
269
|
|
|
270
270
|
const handle = mapping.handle;
|
|
271
|
-
const hashed = handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
271
|
+
const hashed = yield* handleKey(ceremony.profile.rpId, ceremony.context.userHandle);
|
|
272
272
|
|
|
273
273
|
const held = (yield* owner.read(
|
|
274
274
|
handle.table,
|
|
@@ -319,7 +319,7 @@ export const makePasskeyRegistrationWriteKernel = (
|
|
|
319
319
|
)
|
|
320
320
|
return yield* reject;
|
|
321
321
|
|
|
322
|
-
const data = describe(
|
|
322
|
+
const data = yield* describe(
|
|
323
323
|
mapping,
|
|
324
324
|
jsonStorage(mapping.registration.schema).decode(row[intent.applicationSnapshot]),
|
|
325
325
|
);
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any -- existing mapped-row bridge; public adapters preserve table, ID, and Effect types. */
|
|
2
2
|
|
|
3
|
-
// oxlint-disable-next-line import/extensions -- Noble exposes its explicit .js subpaths.
|
|
4
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
|
-
// oxlint-disable-next-line import/extensions -- Noble exposes its explicit .js subpaths.
|
|
6
|
-
import { randomBytes } from "@noble/hashes/utils.js";
|
|
7
3
|
import {
|
|
8
4
|
PasskeyConfigurationError,
|
|
9
5
|
PasskeyUnavailable,
|
|
@@ -15,9 +11,9 @@ import {
|
|
|
15
11
|
PasskeyMethodPolicy,
|
|
16
12
|
snapshotPasskeySync,
|
|
17
13
|
} from "@yielded/auth/Passkey";
|
|
18
|
-
import { Context, Effect, Schema } from "effect";
|
|
19
|
-
import { Base64Url } from "effect/encoding";
|
|
14
|
+
import { Context, Crypto, Effect, Schema } from "effect";
|
|
20
15
|
|
|
16
|
+
import { randomId } from "../crypto";
|
|
21
17
|
import {
|
|
22
18
|
requiredPasskeyCredentialConstraints,
|
|
23
19
|
requiredPasskeyPersistenceConstraints,
|
|
@@ -48,14 +44,14 @@ export const makePasskeyStateKernel = (
|
|
|
48
44
|
|
|
49
45
|
const { col, equal, copiedRow, matchesNativeRow } = makeTransactionRows(unavailable);
|
|
50
46
|
|
|
51
|
-
const nonce =
|
|
47
|
+
const nonce = randomId;
|
|
52
48
|
|
|
53
49
|
const encoder = new TextEncoder();
|
|
54
50
|
|
|
55
51
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
56
52
|
|
|
57
53
|
/** RP tuples and admission scopes have no collation-dependent string identity. */
|
|
58
|
-
const key = (kind: string, fields: ReadonlyArray<string>)
|
|
54
|
+
const key = Effect.fnUntraced(function* (kind: string, fields: ReadonlyArray<string>) {
|
|
59
55
|
const values = ["effect-auth/passkey/" + kind + "/v1", ...fields].map((field) => {
|
|
60
56
|
const bytes = encoder.encode(field);
|
|
61
57
|
|
|
@@ -74,10 +70,11 @@ export const makePasskeyStateKernel = (
|
|
|
74
70
|
offset += 4 + bytes.length;
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
const crypto = yield* Crypto.Crypto;
|
|
74
|
+
const hashed = yield* crypto.digest("SHA-256", packed);
|
|
75
|
+
|
|
76
|
+
return "v1:" + Array.from(hashed, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
77
|
+
});
|
|
81
78
|
|
|
82
79
|
const credentialKey = (rpId: string, id: string) => key("credential", [rpId, id]);
|
|
83
80
|
|
|
@@ -88,7 +85,7 @@ export const makePasskeyStateKernel = (
|
|
|
88
85
|
const targetScope = (ceremony: PasskeyCeremony) => {
|
|
89
86
|
const context = ceremony.context;
|
|
90
87
|
|
|
91
|
-
if (!("target" in context)) return null;
|
|
88
|
+
if (!("target" in context)) return Effect.succeed(null);
|
|
92
89
|
const target = context.target;
|
|
93
90
|
|
|
94
91
|
return key("target", [
|