@yielded/auth-persistence 0.1.0-beta.31 → 0.1.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/Adapter.mjs +8 -8
- package/dist/OAuthPersistence.mjs +10 -5
- package/dist/OAuthPersistence.mjs.map +1 -1
- package/dist/internal/configuration.d.mts +31 -5
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-native.mjs +10 -2
- package/dist/internal/email-native.mjs.map +1 -1
- package/dist/internal/email-policy.mjs +3 -2
- package/dist/internal/email-policy.mjs.map +1 -1
- package/dist/internal/models/oauth-model.d.mts +4 -2
- package/dist/internal/models/oauth-model.mjs.map +1 -1
- package/dist/internal/oauth/native-accounts.d.mts +1 -1
- package/dist/internal/oauth/native-accounts.mjs +6 -6
- package/dist/internal/oauth/native-accounts.mjs.map +1 -1
- package/dist/internal/oauth/native-connected.d.mts +1 -1
- package/dist/internal/oauth/native-connected.mjs +1 -1
- package/dist/internal/oauth/native-registration.mjs +19 -8
- package/dist/internal/oauth/native-registration.mjs.map +1 -1
- package/dist/internal/oauth/native-sign-in.d.mts +1 -1
- package/dist/internal/oauth/native-sign-in.mjs +1 -1
- package/dist/internal/oauth/native-state.mjs +21 -11
- package/dist/internal/oauth/native-state.mjs.map +1 -1
- package/dist/internal/oauth/state.d.mts +1 -1
- package/dist/internal/oauth/state.mjs +1 -1
- package/dist/internal/oauth-managed.mjs +182 -0
- package/dist/internal/oauth-managed.mjs.map +1 -0
- package/dist/internal/passkey-managed.mjs +1 -1
- package/dist/internal/passkey-native-registration.mjs +1 -1
- package/dist/internal/passkey-native-state.mjs +33 -23
- package/dist/internal/passkey-native-state.mjs.map +1 -1
- package/dist/internal/passkey-native-write.mjs +3 -3
- package/dist/internal/passkey-native-write.mjs.map +1 -1
- package/dist/internal/passkey-native.d.mts +15 -7
- package/dist/internal/passkey-native.mjs +2 -1
- package/dist/internal/passkey-native.mjs.map +1 -1
- package/dist/internal/passkey-policy.mjs +9 -7
- package/dist/internal/passkey-policy.mjs.map +1 -1
- package/dist/internal/password-native.mjs +9 -6
- package/dist/internal/password-native.mjs.map +1 -1
- package/dist/internal/password-policy.mjs +7 -6
- package/dist/internal/password-policy.mjs.map +1 -1
- package/dist/internal/persistence.mjs +96 -37
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/phone-native.mjs +1 -1
- package/dist/internal/phone-native.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +6 -5
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.d.mts +2 -2
- package/dist/internal/session-native-login.mjs +2 -1
- package/dist/internal/session-native-login.mjs.map +1 -1
- package/dist/internal/session-native-pending-read.mjs +26 -20
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-record.mjs +6 -4
- package/dist/internal/session-native-record.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +3 -0
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.d.mts +2 -2
- package/dist/internal/session-native-stateful.mjs +65 -37
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.d.mts +2 -2
- package/dist/internal/session-native-step-up.mjs +14 -18
- package/dist/internal/session-native-step-up.mjs.map +1 -1
- package/dist/internal/session-native-validity.mjs +3 -1
- package/dist/internal/session-native-validity.mjs.map +1 -1
- package/dist/internal/sql-authority.mjs +5 -1
- package/dist/internal/sql-authority.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +2 -1
- package/dist/internal/sql-storage-validation.mjs.map +1 -1
- package/dist/internal/sql-table.d.mts +1 -0
- package/dist/internal/sql-table.mjs.map +1 -1
- package/dist/internal/step-up-state.mjs +19 -6
- package/dist/internal/step-up-state.mjs.map +1 -1
- package/dist/internal/storage-mapping.mjs +1 -1
- package/dist/internal/storage-tables.d.mts +237 -0
- package/dist/internal/storage-tables.mjs +68 -0
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +3 -1
- package/dist/internal/storage-validation.mjs +70 -17
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/totp-native.mjs +1 -1
- package/package.json +1 -1
- package/src/OAuthPersistence.ts +7 -2
- package/src/internal/configuration.ts +46 -7
- package/src/internal/email-native.ts +16 -6
- package/src/internal/email-policy.ts +12 -3
- package/src/internal/models/oauth-model.ts +7 -1
- package/src/internal/oauth/native-accounts.ts +3 -3
- package/src/internal/oauth/native-registration.ts +17 -6
- package/src/internal/oauth/native-state.ts +38 -18
- package/src/internal/oauth-managed.ts +277 -0
- package/src/internal/passkey-native-state.ts +59 -52
- package/src/internal/passkey-native-write.ts +2 -2
- package/src/internal/passkey-native.ts +4 -2
- package/src/internal/passkey-policy.ts +13 -9
- package/src/internal/password-native.ts +17 -9
- package/src/internal/password-policy.ts +16 -8
- package/src/internal/persistence.ts +216 -70
- package/src/internal/phone-native.ts +1 -1
- package/src/internal/session-native-authority.ts +23 -7
- package/src/internal/session-native-login.ts +6 -3
- package/src/internal/session-native-pending-read.ts +46 -33
- package/src/internal/session-native-record.ts +6 -4
- package/src/internal/session-native-state.ts +3 -0
- package/src/internal/session-native-stateful.ts +144 -51
- package/src/internal/session-native-step-up.ts +27 -23
- package/src/internal/session-native-validity.ts +3 -1
- package/src/internal/sql-authority.ts +5 -1
- package/src/internal/sql-storage-validation.ts +53 -46
- package/src/internal/sql-table.ts +1 -0
- package/src/internal/step-up-state.ts +43 -6
- package/src/internal/storage-tables.ts +76 -0
- package/src/internal/storage-validation.ts +127 -27
|
@@ -7,6 +7,7 @@ import { makePasskeyNativeReadState, makePasskeyNativeState, passkeyNativeInvari
|
|
|
7
7
|
import { Crypto, Effect, Option, Schema } from "effect";
|
|
8
8
|
import { CurrentCommitJournal } from "@yielded/auth/Hooks";
|
|
9
9
|
import * as M from "@yielded/auth/Passkey";
|
|
10
|
+
import { AuthenticationClock } from "@yielded/auth/Operations";
|
|
10
11
|
import { SqlError } from "effect/sql/SqlError";
|
|
11
12
|
//#region src/internal/passkey-native.ts
|
|
12
13
|
const unavailable = () => M.PasskeyUnavailable.make({});
|
|
@@ -24,7 +25,7 @@ const services = Effect.fnUntraced(function* (tables, mapping, batch) {
|
|
|
24
25
|
const { sql, ceremony: flow, credential } = state;
|
|
25
26
|
const read = mapping.read;
|
|
26
27
|
const provide = (effect) => Effect.provideService(effect, Crypto.Crypto, crypto);
|
|
27
|
-
const run = (effect, mode = "transaction") => batch === void 0 ? executor.run(provide(effect), mode) : executor.batch(provide(effect)).pipe(Effect.provideService(SqlBatchCommit, batch));
|
|
28
|
+
const run = (effect, mode = "transaction") => (batch === void 0 ? executor.run(provide(effect), mode) : executor.batch(provide(effect)).pipe(Effect.provideService(SqlBatchCommit, batch))).pipe(Effect.provideService(AuthenticationClock, state.clockPolicy));
|
|
28
29
|
const advisory = (effect) => executor.read(provide(effect));
|
|
29
30
|
const issue = (original, prepare) => run(Effect.gen(function* () {
|
|
30
31
|
const ceremony = M.snapshotPasskeySync(passkeyOperationInputs.issue, original).ceremony;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-native.mjs","names":[],"sources":["../../src/internal/passkey-native.ts"],"sourcesContent":["import { CurrentCommitJournal } from \"@yielded/auth/Hooks\";\nimport * as M from \"@yielded/auth/Passkey\";\nimport { Crypto, Effect, Option, Schema } from \"effect\";\nimport { SqlError } from \"effect/sql/SqlError\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { passkeyOperationInputs } from \"./passkey-inputs\";\nimport {\n makePasskeyNativeReadState,\n makePasskeyNativeState,\n passkeyNativeInvariant,\n type PasskeyNativeMapping,\n type PasskeyNativeRead,\n type PasskeyNativeReadState,\n} from \"./passkey-native-state\";\nimport {\n passkeyAssertionPurposes,\n passkeyCredentialKey,\n passkeyMatchesAccess,\n validPasskeyCeremony,\n} from \"./passkey-policy\";\nimport { exactSqlText } from \"./sql-change\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n registerSqlCommitReceipt,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n SqlBatchCommit,\n} from \"./sql-commit\";\n\nconst unavailable = () => M.PasskeyUnavailable.make({});\n\nexport const preparePasskeyNative = <Value, A>(\n value: Value,\n prepare: M.PreparePasskeyCommit<Value, A>,\n) =>\n Effect.gen(function* () {\n const journal = yield* CurrentCommitJournal;\n\n const receipt = prepare(value, journal);\n\n passkeyNativeInvariant(receipt?._tag === \"PreparedCommit\" && Effect.isEffect(receipt.read));\n yield* registerSqlCommitReceipt(receipt);\n\n return receipt;\n });\n\nconst services = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: PasskeyNativeMapping,\n batch?: SqlBatchCommit[\"Service\"],\n) {\n const boundOwner = yield* Effect.serviceOption(CurrentSqlCommit);\n const state = yield* makePasskeyNativeState(tables, mapping);\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const crypto = yield* Crypto.Crypto;\n const { sql, ceremony: flow, credential } = state;\n const read = mapping.read;\n\n const provide = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.provideService(effect, Crypto.Crypto, crypto);\n\n const run = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n batch === undefined\n ? executor.run(provide(effect), mode)\n : executor.batch(provide(effect)).pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const advisory = <A, E, R>(effect: Effect.Effect<A, E, R>) => executor.read(provide(effect));\n\n const issue: M.PasskeyPersistence[\"Service\"][\"issue\"] = (original, prepare) =>\n run(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.issue, original);\n const ceremony = input.ceremony;\n\n if (\n !flow.validModule(ceremony.moduleId) ||\n !validPasskeyCeremony(ceremony) ||\n ceremony.purpose === \"registration\"\n )\n return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n const statement = flow.insert(ceremony);\n let changed: number;\n\n if (batch !== undefined) {\n yield* flow.stage(statement, 1);\n changed = 1;\n } else {\n changed = yield* flow.change(statement).pipe(\n Effect.catchIf(\n (error) => Schema.is(SqlError)(error) && error.reason._tag === \"UniqueViolation\",\n () => Effect.succeed(0),\n ),\n );\n }\n\n return yield* preparePasskeyNative(\n changed === 1 ? { _tag: \"Issued\", ceremony } : { _tag: \"Rejected\" },\n prepare,\n );\n }),\n \"statement\",\n );\n\n const persistence: M.PasskeyPersistence[\"Service\"] = {\n issue,\n context: (original) =>\n advisory(\n Effect.suspend(() => flow.context(M.snapshotPasskeySync(M.PasskeyAccess, original))),\n ),\n consume: (original, prepare) =>\n run(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.consume, original);\n const captured = input.credential;\n\n const revision = captured.revision.credentials.find(\n (entry) => entry.credentialId === captured.credentialId,\n );\n\n const assertion = input.assertion;\n\n if (\n !flow.validModule(input.access.moduleId) ||\n !passkeyMatchesAccess(input.ceremony, input.access) ||\n !passkeyAssertionPurposes.some((purpose) => purpose === input.ceremony.purpose) ||\n !validPasskeyCeremony(input.ceremony) ||\n revision === undefined ||\n !captured.active ||\n captured.rpId !== input.ceremony.profile.rpId ||\n assertion.protocolCredentialId !== captured.protocolCredentialId ||\n assertion.backupEligible !== captured.backupEligible ||\n (assertion.userHandle !== undefined && assertion.userHandle !== captured.userHandle)\n )\n return yield* preparePasskeyNative(\"Rejected\", prepare);\n const consumed = flow.consume(input.access, input.ceremony);\n\n if (batch === undefined) {\n if ((yield* flow.change(consumed)) !== 1)\n return yield* preparePasskeyNative(\"Rejected\", prepare);\n } else yield* flow.stage(consumed, 1);\n const key = yield* passkeyCredentialKey(captured.rpId, captured.protocolCredentialId);\n\n const exact = (column: string, value: unknown) =>\n exactSqlText(sql, credential.column(column), credential.value(column, value));\n\n const counter = credential.column(read.credential.counter);\n const next = credential.value(read.credential.counter, assertion.counter);\n\n const merged = captured.backupEligible\n ? sql`case when ${counter} > ${next} then ${counter} else ${next} end`\n : next;\n\n const identity = [\n exact(read.credential.credentialId, captured.credentialId),\n exact(read.credential.credentialKey, key),\n exact(read.credential.credentialRevision, revision.revision),\n sql`${credential.column(read.credential.backupEligible)} = ${credential.value(read.credential.backupEligible, mapping.telemetry.encodeBackupEligible(captured.backupEligible))}`,\n state.active(\"credential\"),\n ];\n\n const statement = sql`${credential.update({\n [read.credential.counter]: merged,\n [read.credential.backupState]: mapping.telemetry.encodeBackupState(\n assertion.backupState,\n ),\n [mapping.telemetry.lastUsedAt]: state.storedNow,\n })} where ${sql.and([\n ...identity,\n captured.backupEligible\n ? sql`true`\n : sql`((${counter} = 0 and ${next} = 0) or ${counter} < ${next})`,\n ])}`;\n\n let changed: number;\n\n if (batch === undefined) {\n changed = yield* flow.change(statement);\n if (\n changed === 0 &&\n sql.onDialectOrElse({ mysql: () => true, orElse: () => false }) &&\n (captured.backupEligible || assertion.counter === 0)\n ) {\n // mysql2 can disable FOUND_ROWS. The UPDATE still holds the row lock;\n // prove the unchanged telemetry matched this exact semantic key.\n const matched =\n yield* sql`select 1 as matched from ${credential.name} where ${sql.and(identity)} and ${captured.backupEligible ? sql`${counter} >= ${next}` : sql`${counter} = 0`} and ${credential.column(read.credential.backupState)} = ${credential.value(read.credential.backupState, mapping.telemetry.encodeBackupState(assertion.backupState))}`;\n\n if (matched.length === 1) changed = 1;\n }\n } else {\n yield* flow.stage(statement, 1);\n changed = 1;\n }\n\n if (Option.isSome(boundOwner)) {\n // Protect the accepted result from later bound application SQL. BE\n // retains the observed accepted floor; it does not compare an entire\n // telemetry snapshot or reject valid out-of-order assertions.\n const acceptedCounter = captured.backupEligible\n ? sql`${counter} >= ${credential.value(read.credential.counter, Math.max(captured.counter, assertion.counter))}`\n : sql`${counter} = ${next}`;\n\n const condition = sql.and([\n flow.absent(input.access),\n changed === 1\n ? sql`exists(select 1 from ${credential.name} where ${sql.and(identity)} and ${acceptedCounter}\n and ${credential.column(read.credential.backupState)} = ${credential.value(read.credential.backupState, mapping.telemetry.encodeBackupState(assertion.backupState))})`\n : sql`true`,\n ]);\n\n if (batch === undefined) {\n yield* registerSqlPostcondition({\n name: \"passkey-consume-preserved\",\n check: Effect.gen(function* () {\n const rows = yield* sql`select 1 where ${condition}`;\n\n passkeyNativeInvariant(rows.length === 1);\n }),\n });\n } else {\n yield* registerSqlBatchPostcondition({\n name: \"passkey-consume-preserved\",\n statement: sqlBatchAssertion(sql, condition),\n });\n }\n }\n\n return yield* preparePasskeyNative(changed === 1 ? \"Verified\" : \"Rejected\", prepare);\n }),\n ),\n cleanup: (original, prepare) =>\n run(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.cleanup, original);\n\n passkeyNativeInvariant(flow.validModule(input.moduleId));\n\n return yield* preparePasskeyNative(\n yield* flow.cleanup(input.moduleId, input.limit, batch !== undefined),\n prepare,\n );\n }),\n \"statement\",\n ),\n };\n\n const credentials = makePasskeyNativeCredentialService(state, read, advisory);\n\n return {\n batch: batch !== undefined,\n state,\n executor,\n run,\n advisory,\n passkeyPersistence: persistence,\n passkeyCredentials: credentials,\n };\n});\n\nexport const makeNativePasskeyServices = (tables: NativeSqlTables, mapping: PasskeyNativeMapping) =>\n services(tables, mapping);\n\nexport const makeBatchPasskeyServices = (tables: NativeSqlTables, mapping: PasskeyNativeMapping) =>\n Effect.flatMap(SqlBatchCommit, (batch) => services(tables, mapping, batch));\n\nexport type NativePasskeyServices = Effect.Success<ReturnType<typeof makeNativePasskeyServices>>;\n\nconst makePasskeyNativeCredentialService = (\n state: PasskeyNativeReadState,\n read: PasskeyNativeRead,\n advisory: <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n ) => Effect.Effect<\n A,\n M.PasskeyUnavailable,\n Exclude<\n Exclude<Exclude<R, Crypto.Crypto>, import(\"effect/sql/SqlClient\").SqlClient>,\n import(\"@yielded/auth/Hooks\").LifecycleHooks\n >\n >,\n) => {\n const { sql, joinedSubject: subject, joinedFactor: factor, joinedCredential: credential } = state;\n\n const credentials: M.PasskeyCredentials[\"Service\"] = {\n lookup: (original) =>\n advisory(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.lookup, original);\n\n return yield* state.lookup(input.rpId, input.protocolCredentialId);\n }),\n ),\n listForSubject: (original) =>\n advisory(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.listForSubject, original);\n\n const nativeId = state.native(input.subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"s_\")}, ${factor.fields(\"f_\")}, ${credential.fields(\"c_\")}\n from ${state.selected(\"subject\", \"passkey_subject\")}\n left join ${state.selected(\"authority\", \"passkey_factor\")} on ${factor.column(read.authority.subjectId)} = ${factor.value(read.authority.subjectId, nativeId)}\n left join ${state.selected(\"credential\", \"passkey_credential\")} on ${credential.column(read.credential.subjectId)} = ${credential.value(read.credential.subjectId, nativeId)} and ${credential.column(read.credential.rpId)} = ${credential.value(read.credential.rpId, input.rpId)}\n where ${subject.column(read.subject.id)} = ${subject.value(read.subject.id, nativeId)} limit 4097`;\n\n if (rows.length === 0 || rows.length > 4096) return undefined;\n const subjectRow = subject.decode(rows[0]!, \"s_\");\n\n passkeyNativeInvariant(\n rows.every((selected) => {\n const other = subject.decode(selected, \"s_\");\n\n return (\n state.sameId(read.subject.decodeId(other), nativeId) &&\n other[read.subject.securityRevision] === subjectRow[read.subject.securityRevision]\n );\n }),\n );\n\n const revision = state.revision(\n subjectRow,\n rows.map((row) => factor.decode(row, \"f_\")),\n );\n\n passkeyNativeInvariant(revision.subjectId === input.subjectId);\n const values = new Map<string, M.PasskeyCredential>();\n\n for (const selected of rows) {\n const row = credential.decode(selected, \"c_\");\n\n if (row[read.credential.credentialId] === null) continue;\n const decoded = yield* state.decodeCredential(row, revision, subjectRow);\n\n if (decoded !== undefined) values.set(decoded.credentialId, decoded);\n }\n const userHandle = values.values().next().value?.userHandle;\n\n return M.snapshotPasskeySync(M.PasskeyEnrollmentSnapshot, {\n revision,\n ...(userHandle === undefined ? {} : { userHandle }),\n credentials: [...values.values()].map((value) => ({\n type: \"public-key\" as const,\n id: value.protocolCredentialId,\n })),\n });\n }),\n ),\n };\n\n return credentials;\n};\n\nexport const makeNativePasskeyCredentialServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: PasskeyNativeRead,\n) {\n const state = yield* makePasskeyNativeReadState(tables, mapping);\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const crypto = yield* Crypto.Crypto;\n\n const advisory = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n executor.read(Effect.provideService(effect, Crypto.Crypto, crypto));\n\n return { passkeyCredentials: makePasskeyNativeCredentialService(state, mapping, advisory) };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"passkey-native.mjs","names":[],"sources":["../../src/internal/passkey-native.ts"],"sourcesContent":["import { CurrentCommitJournal } from \"@yielded/auth/Hooks\";\nimport { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport * as M from \"@yielded/auth/Passkey\";\nimport { Crypto, Effect, Option, Schema } from \"effect\";\nimport { SqlError } from \"effect/sql/SqlError\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { passkeyOperationInputs } from \"./passkey-inputs\";\nimport {\n makePasskeyNativeReadState,\n makePasskeyNativeState,\n passkeyNativeInvariant,\n type PasskeyNativeMapping,\n type PasskeyNativeRead,\n type PasskeyNativeReadState,\n} from \"./passkey-native-state\";\nimport {\n passkeyAssertionPurposes,\n passkeyCredentialKey,\n passkeyMatchesAccess,\n validPasskeyCeremony,\n} from \"./passkey-policy\";\nimport { exactSqlText } from \"./sql-change\";\nimport {\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n registerSqlCommitReceipt,\n registerSqlPostcondition,\n registerSqlBatchPostcondition,\n SqlBatchCommit,\n} from \"./sql-commit\";\n\nconst unavailable = () => M.PasskeyUnavailable.make({});\n\nexport const preparePasskeyNative = <Value, A>(\n value: Value,\n prepare: M.PreparePasskeyCommit<Value, A>,\n) =>\n Effect.gen(function* () {\n const journal = yield* CurrentCommitJournal;\n\n const receipt = prepare(value, journal);\n\n passkeyNativeInvariant(receipt?._tag === \"PreparedCommit\" && Effect.isEffect(receipt.read));\n yield* registerSqlCommitReceipt(receipt);\n\n return receipt;\n });\n\nconst services = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: PasskeyNativeMapping,\n batch?: SqlBatchCommit[\"Service\"],\n) {\n const boundOwner = yield* Effect.serviceOption(CurrentSqlCommit);\n const state = yield* makePasskeyNativeState(tables, mapping);\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const crypto = yield* Crypto.Crypto;\n const { sql, ceremony: flow, credential } = state;\n const read = mapping.read;\n\n const provide = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.provideService(effect, Crypto.Crypto, crypto);\n\n const run = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n mode: \"transaction\" | \"statement\" = \"transaction\",\n ) =>\n (batch === undefined\n ? executor.run(provide(effect), mode)\n : executor.batch(provide(effect)).pipe(Effect.provideService(SqlBatchCommit, batch))\n ).pipe(Effect.provideService(AuthenticationClock, state.clockPolicy));\n\n const advisory = <A, E, R>(effect: Effect.Effect<A, E, R>) => executor.read(provide(effect));\n\n const issue: M.PasskeyPersistence[\"Service\"][\"issue\"] = (original, prepare) =>\n run(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.issue, original);\n const ceremony = input.ceremony;\n\n if (\n !flow.validModule(ceremony.moduleId) ||\n !validPasskeyCeremony(ceremony) ||\n ceremony.purpose === \"registration\"\n )\n return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n const statement = flow.insert(ceremony);\n let changed: number;\n\n if (batch !== undefined) {\n yield* flow.stage(statement, 1);\n changed = 1;\n } else {\n changed = yield* flow.change(statement).pipe(\n Effect.catchIf(\n (error) => Schema.is(SqlError)(error) && error.reason._tag === \"UniqueViolation\",\n () => Effect.succeed(0),\n ),\n );\n }\n\n return yield* preparePasskeyNative(\n changed === 1 ? { _tag: \"Issued\", ceremony } : { _tag: \"Rejected\" },\n prepare,\n );\n }),\n \"statement\",\n );\n\n const persistence: M.PasskeyPersistence[\"Service\"] = {\n issue,\n context: (original) =>\n advisory(\n Effect.suspend(() => flow.context(M.snapshotPasskeySync(M.PasskeyAccess, original))),\n ),\n consume: (original, prepare) =>\n run(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.consume, original);\n const captured = input.credential;\n\n const revision = captured.revision.credentials.find(\n (entry) => entry.credentialId === captured.credentialId,\n );\n\n const assertion = input.assertion;\n\n if (\n !flow.validModule(input.access.moduleId) ||\n !passkeyMatchesAccess(input.ceremony, input.access) ||\n !passkeyAssertionPurposes.some((purpose) => purpose === input.ceremony.purpose) ||\n !validPasskeyCeremony(input.ceremony) ||\n revision === undefined ||\n !captured.active ||\n captured.rpId !== input.ceremony.profile.rpId ||\n assertion.protocolCredentialId !== captured.protocolCredentialId ||\n assertion.backupEligible !== captured.backupEligible ||\n (assertion.userHandle !== undefined && assertion.userHandle !== captured.userHandle)\n )\n return yield* preparePasskeyNative(\"Rejected\", prepare);\n const consumed = flow.consume(input.access, input.ceremony);\n\n if (batch === undefined) {\n if ((yield* flow.change(consumed)) !== 1)\n return yield* preparePasskeyNative(\"Rejected\", prepare);\n } else yield* flow.stage(consumed, 1);\n const key = yield* passkeyCredentialKey(captured.rpId, captured.protocolCredentialId);\n\n const exact = (column: string, value: unknown) =>\n exactSqlText(sql, credential.column(column), credential.value(column, value));\n\n const counter = credential.column(read.credential.counter);\n const next = credential.value(read.credential.counter, assertion.counter);\n\n const merged = captured.backupEligible\n ? sql`case when ${counter} > ${next} then ${counter} else ${next} end`\n : next;\n\n const identity = [\n exact(read.credential.credentialId, captured.credentialId),\n exact(read.credential.credentialKey, key),\n exact(read.credential.credentialRevision, revision.revision),\n sql`${credential.column(read.credential.backupEligible)} = ${credential.value(read.credential.backupEligible, mapping.telemetry.encodeBackupEligible(captured.backupEligible))}`,\n state.active(\"credential\"),\n ];\n\n const statement = sql`${credential.update({\n [read.credential.counter]: merged,\n [read.credential.backupState]: mapping.telemetry.encodeBackupState(\n assertion.backupState,\n ),\n [mapping.telemetry.lastUsedAt]: state.storedNow,\n })} where ${sql.and([\n ...identity,\n captured.backupEligible\n ? sql`true`\n : sql`((${counter} = 0 and ${next} = 0) or ${counter} < ${next})`,\n ])}`;\n\n let changed: number;\n\n if (batch === undefined) {\n changed = yield* flow.change(statement);\n if (\n changed === 0 &&\n sql.onDialectOrElse({ mysql: () => true, orElse: () => false }) &&\n (captured.backupEligible || assertion.counter === 0)\n ) {\n // mysql2 can disable FOUND_ROWS. The UPDATE still holds the row lock;\n // prove the unchanged telemetry matched this exact semantic key.\n const matched =\n yield* sql`select 1 as matched from ${credential.name} where ${sql.and(identity)} and ${captured.backupEligible ? sql`${counter} >= ${next}` : sql`${counter} = 0`} and ${credential.column(read.credential.backupState)} = ${credential.value(read.credential.backupState, mapping.telemetry.encodeBackupState(assertion.backupState))}`;\n\n if (matched.length === 1) changed = 1;\n }\n } else {\n yield* flow.stage(statement, 1);\n changed = 1;\n }\n\n if (Option.isSome(boundOwner)) {\n // Protect the accepted result from later bound application SQL. BE\n // retains the observed accepted floor; it does not compare an entire\n // telemetry snapshot or reject valid out-of-order assertions.\n const acceptedCounter = captured.backupEligible\n ? sql`${counter} >= ${credential.value(read.credential.counter, Math.max(captured.counter, assertion.counter))}`\n : sql`${counter} = ${next}`;\n\n const condition = sql.and([\n flow.absent(input.access),\n changed === 1\n ? sql`exists(select 1 from ${credential.name} where ${sql.and(identity)} and ${acceptedCounter}\n and ${credential.column(read.credential.backupState)} = ${credential.value(read.credential.backupState, mapping.telemetry.encodeBackupState(assertion.backupState))})`\n : sql`true`,\n ]);\n\n if (batch === undefined) {\n yield* registerSqlPostcondition({\n name: \"passkey-consume-preserved\",\n check: Effect.gen(function* () {\n const rows = yield* sql`select 1 where ${condition}`;\n\n passkeyNativeInvariant(rows.length === 1);\n }),\n });\n } else {\n yield* registerSqlBatchPostcondition({\n name: \"passkey-consume-preserved\",\n statement: sqlBatchAssertion(sql, condition),\n });\n }\n }\n\n return yield* preparePasskeyNative(changed === 1 ? \"Verified\" : \"Rejected\", prepare);\n }),\n ),\n cleanup: (original, prepare) =>\n run(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.cleanup, original);\n\n passkeyNativeInvariant(flow.validModule(input.moduleId));\n\n return yield* preparePasskeyNative(\n yield* flow.cleanup(input.moduleId, input.limit, batch !== undefined),\n prepare,\n );\n }),\n \"statement\",\n ),\n };\n\n const credentials = makePasskeyNativeCredentialService(state, read, advisory);\n\n return {\n batch: batch !== undefined,\n state,\n executor,\n run,\n advisory,\n passkeyPersistence: persistence,\n passkeyCredentials: credentials,\n };\n});\n\nexport const makeNativePasskeyServices = (tables: NativeSqlTables, mapping: PasskeyNativeMapping) =>\n services(tables, mapping);\n\nexport const makeBatchPasskeyServices = (tables: NativeSqlTables, mapping: PasskeyNativeMapping) =>\n Effect.flatMap(SqlBatchCommit, (batch) => services(tables, mapping, batch));\n\nexport type NativePasskeyServices = Effect.Success<ReturnType<typeof makeNativePasskeyServices>>;\n\nconst makePasskeyNativeCredentialService = (\n state: PasskeyNativeReadState,\n read: PasskeyNativeRead,\n advisory: <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n ) => Effect.Effect<\n A,\n M.PasskeyUnavailable,\n Exclude<\n Exclude<Exclude<R, Crypto.Crypto>, import(\"effect/sql/SqlClient\").SqlClient>,\n import(\"@yielded/auth/Hooks\").LifecycleHooks\n >\n >,\n) => {\n const { sql, joinedSubject: subject, joinedFactor: factor, joinedCredential: credential } = state;\n\n const credentials: M.PasskeyCredentials[\"Service\"] = {\n lookup: (original) =>\n advisory(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.lookup, original);\n\n return yield* state.lookup(input.rpId, input.protocolCredentialId);\n }),\n ),\n listForSubject: (original) =>\n advisory(\n Effect.gen(function* () {\n const input = M.snapshotPasskeySync(passkeyOperationInputs.listForSubject, original);\n\n const nativeId = state.native(input.subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"s_\")}, ${factor.fields(\"f_\")}, ${credential.fields(\"c_\")}\n from ${state.selected(\"subject\", \"passkey_subject\")}\n left join ${state.selected(\"authority\", \"passkey_factor\")} on ${factor.column(read.authority.subjectId)} = ${factor.value(read.authority.subjectId, nativeId)}\n left join ${state.selected(\"credential\", \"passkey_credential\")} on ${credential.column(read.credential.subjectId)} = ${credential.value(read.credential.subjectId, nativeId)} and ${credential.column(read.credential.rpId)} = ${credential.value(read.credential.rpId, input.rpId)}\n where ${subject.column(read.subject.id)} = ${subject.value(read.subject.id, nativeId)} limit 4097`;\n\n if (rows.length === 0 || rows.length > 4096) return undefined;\n const subjectRow = subject.decode(rows[0]!, \"s_\");\n\n passkeyNativeInvariant(\n rows.every((selected) => {\n const other = subject.decode(selected, \"s_\");\n\n return (\n state.sameId(read.subject.decodeId(other), nativeId) &&\n other[read.subject.securityRevision] === subjectRow[read.subject.securityRevision]\n );\n }),\n );\n\n const revision = state.revision(\n subjectRow,\n rows.map((row) => factor.decode(row, \"f_\")),\n );\n\n passkeyNativeInvariant(revision.subjectId === input.subjectId);\n const values = new Map<string, M.PasskeyCredential>();\n\n for (const selected of rows) {\n const row = credential.decode(selected, \"c_\");\n\n if (row[read.credential.credentialId] === null) continue;\n const decoded = yield* state.decodeCredential(row, revision, subjectRow);\n\n if (decoded !== undefined) values.set(decoded.credentialId, decoded);\n }\n const userHandle = values.values().next().value?.userHandle;\n\n return M.snapshotPasskeySync(M.PasskeyEnrollmentSnapshot, {\n revision,\n ...(userHandle === undefined ? {} : { userHandle }),\n credentials: [...values.values()].map((value) => ({\n type: \"public-key\" as const,\n id: value.protocolCredentialId,\n })),\n });\n }),\n ),\n };\n\n return credentials;\n};\n\nexport const makeNativePasskeyCredentialServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: PasskeyNativeRead,\n) {\n const state = yield* makePasskeyNativeReadState(tables, mapping);\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const crypto = yield* Crypto.Crypto;\n\n const advisory = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n executor.read(Effect.provideService(effect, Crypto.Crypto, crypto));\n\n return { passkeyCredentials: makePasskeyNativeCredentialService(state, mapping, advisory) };\n});\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Crypto, DateTime, Effect, Schema } from "effect";
|
|
2
2
|
import { PasskeyCredential, PasskeyUnavailable, snapshotPasskeySync } from "@yielded/auth/Passkey";
|
|
3
|
+
import { AuthenticationClock } from "@yielded/auth/Operations";
|
|
3
4
|
//#region src/internal/passkey-policy.ts
|
|
4
5
|
const invariant = (value) => {
|
|
5
6
|
if (!value) throw PasskeyUnavailable.make({});
|
|
@@ -78,18 +79,19 @@ const contextPurpose = {
|
|
|
78
79
|
const validPasskeyCeremony = (ceremony) => ceremony.expiresAtMillis > ceremony.issuedAtMillis && ceremony.expiresAtMillis <= ceremony.requestBindingExpiresAtMillis && contextPurpose[ceremony.context._tag] === ceremony.purpose && new Set(ceremony.allowedCredentials.map((item) => item.id)).size === ceremony.allowedCredentials.length;
|
|
79
80
|
const passkeyMatchesAccess = (ceremony, access) => ceremony.moduleId === access.moduleId && ceremony.purpose === access.purpose && ceremony.flowId === access.flowId && ceremony.requestBindingVerifier === access.requestBindingVerifier && ceremony.requestBindingExpiresAtMillis === access.requestBindingExpiresAtMillis;
|
|
80
81
|
/** Reject stale bindings before evaluating application requirements. */
|
|
81
|
-
const validPasskeyActionEvidence = (facts, authorization)
|
|
82
|
+
const validPasskeyActionEvidence = Effect.fnUntraced(function* (facts, authorization) {
|
|
83
|
+
const { futureToleranceMillis } = yield* AuthenticationClock;
|
|
82
84
|
const { challenge, evidence } = authorization;
|
|
83
85
|
const { expected, revision, nowMillis: now } = facts;
|
|
84
86
|
if (challenge.moduleId !== facts.moduleId || challenge.action !== expected.action || challenge.commandId !== expected.commandId || challenge.flowId !== expected.flowId || challenge.bindingDigest !== expected.bindingDigest || evidence.flowId !== expected.flowId || evidence.bindingDigest !== expected.bindingDigest || !samePasskeyRevision(challenge.revision, expected.revision) || revision.subjectId !== expected.revision.subjectId || revision.securityRevision !== expected.revision.securityRevision || evidence.revision.subjectId !== revision.subjectId || evidence.revision.securityRevision !== revision.securityRevision) return false;
|
|
85
87
|
for (const requested of [expected.revision, evidence.revision]) if (requested.credentials.some((item) => !revision.credentials.some((current) => current.credentialId === item.credentialId && current.revision === item.revision))) return false;
|
|
86
|
-
if (evidence.proofs.some((proof) => DateTime.toEpochMillis(proof.verifiedAt)
|
|
88
|
+
if (evidence.proofs.some((proof) => DateTime.toEpochMillis(proof.verifiedAt) - now > futureToleranceMillis || !evidence.revision.credentials.some((item) => item.credentialId === proof.credentialId))) return false;
|
|
87
89
|
return true;
|
|
88
|
-
};
|
|
90
|
+
});
|
|
89
91
|
/** SQL policy predicates remain native. This authorizes the exact evidence and
|
|
90
92
|
* returns the deadline every backend must retain through its final commit. */
|
|
91
|
-
const assessPasskeyAction = (facts, authorization, policy)
|
|
92
|
-
if (!validPasskeyActionEvidence(facts, authorization)) return void 0;
|
|
93
|
+
const assessPasskeyAction = Effect.fnUntraced(function* (facts, authorization, policy) {
|
|
94
|
+
if (!(yield* validPasskeyActionEvidence(facts, authorization))) return void 0;
|
|
93
95
|
const { evidence } = authorization;
|
|
94
96
|
const now = facts.nowMillis;
|
|
95
97
|
let expiresBefore = Infinity;
|
|
@@ -99,7 +101,7 @@ const assessPasskeyAction = (facts, authorization, policy) => {
|
|
|
99
101
|
...facts.originalRequirement === void 0 ? [] : [facts.originalRequirement]
|
|
100
102
|
]) {
|
|
101
103
|
const age = Math.min(policy.maximumEvidenceAgeMillis, requirement.maximumAgeMillis);
|
|
102
|
-
const fresh = evidence.proofs.filter((proof) => now - DateTime.toEpochMillis(proof.verifiedAt) < age);
|
|
104
|
+
const fresh = evidence.proofs.filter((proof) => Math.max(0, now - DateTime.toEpochMillis(proof.verifiedAt)) < age);
|
|
103
105
|
const factors = new Set(fresh.flatMap((proof) => proof.factors));
|
|
104
106
|
if (!requirement.alternatives.some((alternative) => alternative.factors.every((factor) => factors.has(factor)) && new Set(fresh.map((proof) => proof.credentialId)).size >= alternative.minimumCredentials && fresh.some((proof) => (!alternative.userVerified || proof.userVerified) && (!alternative.phishingResistant || proof.phishingResistant)))) return void 0;
|
|
105
107
|
for (const proof of fresh) expiresBefore = Math.min(expiresBefore, DateTime.toEpochMillis(proof.verifiedAt) + age);
|
|
@@ -108,7 +110,7 @@ const assessPasskeyAction = (facts, authorization, policy) => {
|
|
|
108
110
|
notBeforeMillis: now,
|
|
109
111
|
expiresBeforeMillis: expiresBefore
|
|
110
112
|
};
|
|
111
|
-
};
|
|
113
|
+
});
|
|
112
114
|
//#endregion
|
|
113
115
|
export { assessPasskeyAction, passkeyAssertionPurposes, passkeyCredentialKey, passkeyCredentialStorage, passkeyKey, passkeyMatchesAccess, passkeyStorage, samePasskeyCredential, samePasskeyRevision, validPasskeyActionEvidence, validPasskeyCeremony };
|
|
114
116
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-policy.mjs","names":[],"sources":["../../src/internal/passkey-policy.ts"],"sourcesContent":["import {\n PasskeyUnavailable,\n type PasskeyCeremony,\n PasskeyCredential,\n type PasskeyRevision,\n snapshotPasskeySync,\n type PasskeyAccess,\n type PasskeyActionAuthorization,\n type PasskeyManagementPolicy,\n type PasskeyRequirement,\n} from \"@yielded/auth/Passkey\";\nimport { Crypto, DateTime, Effect, Schema } from \"effect\";\n\nconst invariant: (value: unknown) => asserts value = (value) => {\n if (!value) throw PasskeyUnavailable.make({});\n};\n\nconst encoder = new TextEncoder();\n\nconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\n/** RP tuples have no collation-dependent string identity. */\nexport const passkeyKey = Effect.fnUntraced(function* (\n kind: string,\n fields: ReadonlyArray<string>,\n) {\n const values = [\"effect-auth/passkey/\" + kind + \"/v1\", ...fields].map((field) => {\n const bytes = encoder.encode(field);\n\n invariant(decoder.decode(bytes) === field);\n\n return bytes;\n });\n\n const packed = new Uint8Array(values.reduce((size, bytes) => size + 4 + bytes.length, 0));\n const view = new DataView(packed.buffer);\n let offset = 0;\n\n for (const bytes of values) {\n view.setUint32(offset, bytes.length, false);\n packed.set(bytes, offset + 4);\n offset += 4 + bytes.length;\n }\n\n const crypto = yield* Crypto.Crypto;\n const hashed = yield* crypto.digest(\"SHA-256\", packed);\n\n return \"v1:\" + Array.from(hashed, (value) => value.toString(16).padStart(2, \"0\")).join(\"\");\n});\n\nexport const passkeyCredentialKey = (rpId: string, id: string) =>\n passkeyKey(\"credential\", [rpId, id]);\n\n/** Private passkeyStorage is decoded Type data, never a consumer wire transform. */\nexport const passkeyStorage = <S extends Schema.Codec<unknown, unknown, never, never>>(\n schema: S,\n maximumBytes: number,\n) => {\n const codec = Schema.fromJsonString(Schema.toCodecJson(Schema.toType(schema)));\n\n const encode = (value: S[\"Type\"]) => {\n const text = \"v1:\" + Schema.encodeSync(codec)(snapshotPasskeySync(schema, value));\n\n invariant(encoder.encode(text).length <= maximumBytes);\n\n return text;\n };\n\n return {\n encode,\n decode: (text: unknown): S[\"Type\"] => {\n invariant(typeof text === \"string\" && text.startsWith(\"v1:\"));\n invariant(encoder.encode(text).length <= maximumBytes);\n const value = snapshotPasskeySync(schema, Schema.decodeSync(codec)(text.slice(3)));\n\n invariant(encode(value) === text);\n\n return value;\n },\n };\n};\n\n// Legal 32 x 16 x 2048 origins require more than one MiB before JSON escaping.\nexport const passkeyCredentialStorage = passkeyStorage(PasskeyCredential, 512 * 1024);\n\nexport const samePasskeyCredential = (left: PasskeyCredential, right: PasskeyCredential) => {\n const semantic = (value: PasskeyCredential) => ({\n ...value,\n counter: 0,\n backupState: false,\n revision: { ...value.revision, credentials: [] },\n });\n\n return (\n passkeyCredentialStorage.encode(semantic(left)) ===\n passkeyCredentialStorage.encode(semantic(right))\n );\n};\n\nexport const samePasskeyRevision = (\n left: typeof PasskeyRevision.Type,\n right: typeof PasskeyRevision.Type,\n) => {\n const pairs = new Map(left.credentials.map((item) => [item.credentialId, item.revision]));\n\n return (\n left.subjectId === right.subjectId &&\n left.securityRevision === right.securityRevision &&\n pairs.size === left.credentials.length &&\n new Set(right.credentials.map((item) => item.credentialId)).size === right.credentials.length &&\n pairs.size === right.credentials.length &&\n right.credentials.every((item) => pairs.get(item.credentialId) === item.revision)\n );\n};\n\nexport const passkeyAssertionPurposes = [\"sign-in\", \"pending\", \"step-up\", \"action\"] as const;\n\nconst contextPurpose = {\n SignIn: \"sign-in\",\n Pending: \"pending\",\n StepUp: \"step-up\",\n Action: \"action\",\n Enrollment: \"enrollment\",\n Registration: \"registration\",\n} as const;\n\nexport const validPasskeyCeremony = (ceremony: PasskeyCeremony) =>\n ceremony.expiresAtMillis > ceremony.issuedAtMillis &&\n ceremony.expiresAtMillis <= ceremony.requestBindingExpiresAtMillis &&\n contextPurpose[ceremony.context._tag] === ceremony.purpose &&\n new Set(ceremony.allowedCredentials.map((item) => item.id)).size ===\n ceremony.allowedCredentials.length;\n\nexport const passkeyMatchesAccess = (ceremony: PasskeyCeremony, access: PasskeyAccess) =>\n ceremony.moduleId === access.moduleId &&\n ceremony.purpose === access.purpose &&\n ceremony.flowId === access.flowId &&\n ceremony.requestBindingVerifier === access.requestBindingVerifier &&\n ceremony.requestBindingExpiresAtMillis === access.requestBindingExpiresAtMillis;\n\nexport interface PasskeyActionFacts {\n readonly moduleId: string;\n readonly revision: typeof PasskeyRevision.Type;\n readonly nowMillis: number;\n readonly currentRequirement: typeof PasskeyRequirement.Type;\n readonly expected: {\n readonly action: PasskeyActionAuthorization[\"challenge\"][\"action\"];\n readonly commandId: string;\n readonly flowId: string;\n readonly bindingDigest: string;\n readonly revision: typeof PasskeyRevision.Type;\n };\n readonly originalRequirement?: typeof PasskeyRequirement.Type;\n}\n\n/** Reject stale bindings before evaluating application requirements. */\nexport const validPasskeyActionEvidence = (\n facts: Pick<PasskeyActionFacts, \"moduleId\" | \"revision\" | \"expected\" | \"nowMillis\">,\n authorization: PasskeyActionAuthorization,\n) => {\n const { challenge, evidence } = authorization;\n const { expected, revision, nowMillis: now } = facts;\n\n if (\n challenge.moduleId !== facts.moduleId ||\n challenge.action !== expected.action ||\n challenge.commandId !== expected.commandId ||\n challenge.flowId !== expected.flowId ||\n challenge.bindingDigest !== expected.bindingDigest ||\n evidence.flowId !== expected.flowId ||\n evidence.bindingDigest !== expected.bindingDigest ||\n !samePasskeyRevision(challenge.revision, expected.revision) ||\n revision.subjectId !== expected.revision.subjectId ||\n revision.securityRevision !== expected.revision.securityRevision ||\n evidence.revision.subjectId !== revision.subjectId ||\n evidence.revision.securityRevision !== revision.securityRevision\n )\n return false;\n for (const requested of [expected.revision, evidence.revision])\n if (\n requested.credentials.some(\n (item) =>\n !revision.credentials.some(\n (current) =>\n current.credentialId === item.credentialId && current.revision === item.revision,\n ),\n )\n )\n return false;\n if (\n evidence.proofs.some(\n (proof) =>\n DateTime.toEpochMillis(proof.verifiedAt) > now ||\n !evidence.revision.credentials.some((item) => item.credentialId === proof.credentialId),\n )\n )\n return false;\n\n return true;\n};\n\n/** SQL policy predicates remain native. This authorizes the exact evidence and\n * returns the deadline every backend must retain through its final commit. */\nexport const assessPasskeyAction = (\n facts: PasskeyActionFacts,\n authorization: PasskeyActionAuthorization,\n policy: PasskeyManagementPolicy,\n) => {\n if (!validPasskeyActionEvidence(facts, authorization)) return undefined;\n const { evidence } = authorization;\n const now = facts.nowMillis;\n let expiresBefore = Infinity;\n\n for (const requirement of [\n authorization.requirement,\n facts.currentRequirement,\n ...(facts.originalRequirement === undefined ? [] : [facts.originalRequirement]),\n ]) {\n const age = Math.min(policy.maximumEvidenceAgeMillis, requirement.maximumAgeMillis);\n\n const fresh = evidence.proofs.filter(\n (proof) => now - DateTime.toEpochMillis(proof.verifiedAt) < age,\n );\n\n const factors = new Set(fresh.flatMap((proof) => proof.factors));\n\n if (\n !requirement.alternatives.some(\n (alternative) =>\n alternative.factors.every((factor) => factors.has(factor)) &&\n new Set(fresh.map((proof) => proof.credentialId)).size >=\n alternative.minimumCredentials &&\n fresh.some(\n (proof) =>\n (!alternative.userVerified || proof.userVerified) &&\n (!alternative.phishingResistant || proof.phishingResistant),\n ),\n )\n )\n return undefined;\n for (const proof of fresh)\n expiresBefore = Math.min(expiresBefore, DateTime.toEpochMillis(proof.verifiedAt) + age);\n }\n\n return { notBeforeMillis: now, expiresBeforeMillis: expiresBefore };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"passkey-policy.mjs","names":[],"sources":["../../src/internal/passkey-policy.ts"],"sourcesContent":["import { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport {\n PasskeyUnavailable,\n type PasskeyCeremony,\n PasskeyCredential,\n type PasskeyRevision,\n snapshotPasskeySync,\n type PasskeyAccess,\n type PasskeyActionAuthorization,\n type PasskeyManagementPolicy,\n type PasskeyRequirement,\n} from \"@yielded/auth/Passkey\";\nimport { Crypto, DateTime, Effect, Schema } from \"effect\";\n\nconst invariant: (value: unknown) => asserts value = (value) => {\n if (!value) throw PasskeyUnavailable.make({});\n};\n\nconst encoder = new TextEncoder();\n\nconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\n/** RP tuples have no collation-dependent string identity. */\nexport const passkeyKey = Effect.fnUntraced(function* (\n kind: string,\n fields: ReadonlyArray<string>,\n) {\n const values = [\"effect-auth/passkey/\" + kind + \"/v1\", ...fields].map((field) => {\n const bytes = encoder.encode(field);\n\n invariant(decoder.decode(bytes) === field);\n\n return bytes;\n });\n\n const packed = new Uint8Array(values.reduce((size, bytes) => size + 4 + bytes.length, 0));\n const view = new DataView(packed.buffer);\n let offset = 0;\n\n for (const bytes of values) {\n view.setUint32(offset, bytes.length, false);\n packed.set(bytes, offset + 4);\n offset += 4 + bytes.length;\n }\n\n const crypto = yield* Crypto.Crypto;\n const hashed = yield* crypto.digest(\"SHA-256\", packed);\n\n return \"v1:\" + Array.from(hashed, (value) => value.toString(16).padStart(2, \"0\")).join(\"\");\n});\n\nexport const passkeyCredentialKey = (rpId: string, id: string) =>\n passkeyKey(\"credential\", [rpId, id]);\n\n/** Private passkeyStorage is decoded Type data, never a consumer wire transform. */\nexport const passkeyStorage = <S extends Schema.Codec<unknown, unknown, never, never>>(\n schema: S,\n maximumBytes: number,\n) => {\n const codec = Schema.fromJsonString(Schema.toCodecJson(Schema.toType(schema)));\n\n const encode = (value: S[\"Type\"]) => {\n const text = \"v1:\" + Schema.encodeSync(codec)(snapshotPasskeySync(schema, value));\n\n invariant(encoder.encode(text).length <= maximumBytes);\n\n return text;\n };\n\n return {\n encode,\n decode: (text: unknown): S[\"Type\"] => {\n invariant(typeof text === \"string\" && text.startsWith(\"v1:\"));\n invariant(encoder.encode(text).length <= maximumBytes);\n const value = snapshotPasskeySync(schema, Schema.decodeSync(codec)(text.slice(3)));\n\n invariant(encode(value) === text);\n\n return value;\n },\n };\n};\n\n// Legal 32 x 16 x 2048 origins require more than one MiB before JSON escaping.\nexport const passkeyCredentialStorage = passkeyStorage(PasskeyCredential, 512 * 1024);\n\nexport const samePasskeyCredential = (left: PasskeyCredential, right: PasskeyCredential) => {\n const semantic = (value: PasskeyCredential) => ({\n ...value,\n counter: 0,\n backupState: false,\n revision: { ...value.revision, credentials: [] },\n });\n\n return (\n passkeyCredentialStorage.encode(semantic(left)) ===\n passkeyCredentialStorage.encode(semantic(right))\n );\n};\n\nexport const samePasskeyRevision = (\n left: typeof PasskeyRevision.Type,\n right: typeof PasskeyRevision.Type,\n) => {\n const pairs = new Map(left.credentials.map((item) => [item.credentialId, item.revision]));\n\n return (\n left.subjectId === right.subjectId &&\n left.securityRevision === right.securityRevision &&\n pairs.size === left.credentials.length &&\n new Set(right.credentials.map((item) => item.credentialId)).size === right.credentials.length &&\n pairs.size === right.credentials.length &&\n right.credentials.every((item) => pairs.get(item.credentialId) === item.revision)\n );\n};\n\nexport const passkeyAssertionPurposes = [\"sign-in\", \"pending\", \"step-up\", \"action\"] as const;\n\nconst contextPurpose = {\n SignIn: \"sign-in\",\n Pending: \"pending\",\n StepUp: \"step-up\",\n Action: \"action\",\n Enrollment: \"enrollment\",\n Registration: \"registration\",\n} as const;\n\nexport const validPasskeyCeremony = (ceremony: PasskeyCeremony) =>\n ceremony.expiresAtMillis > ceremony.issuedAtMillis &&\n ceremony.expiresAtMillis <= ceremony.requestBindingExpiresAtMillis &&\n contextPurpose[ceremony.context._tag] === ceremony.purpose &&\n new Set(ceremony.allowedCredentials.map((item) => item.id)).size ===\n ceremony.allowedCredentials.length;\n\nexport const passkeyMatchesAccess = (ceremony: PasskeyCeremony, access: PasskeyAccess) =>\n ceremony.moduleId === access.moduleId &&\n ceremony.purpose === access.purpose &&\n ceremony.flowId === access.flowId &&\n ceremony.requestBindingVerifier === access.requestBindingVerifier &&\n ceremony.requestBindingExpiresAtMillis === access.requestBindingExpiresAtMillis;\n\nexport interface PasskeyActionFacts {\n readonly moduleId: string;\n readonly revision: typeof PasskeyRevision.Type;\n readonly nowMillis: number;\n readonly currentRequirement: typeof PasskeyRequirement.Type;\n readonly expected: {\n readonly action: PasskeyActionAuthorization[\"challenge\"][\"action\"];\n readonly commandId: string;\n readonly flowId: string;\n readonly bindingDigest: string;\n readonly revision: typeof PasskeyRevision.Type;\n };\n readonly originalRequirement?: typeof PasskeyRequirement.Type;\n}\n\n/** Reject stale bindings before evaluating application requirements. */\nexport const validPasskeyActionEvidence = Effect.fnUntraced(function* (\n facts: Pick<PasskeyActionFacts, \"moduleId\" | \"revision\" | \"expected\" | \"nowMillis\">,\n authorization: PasskeyActionAuthorization,\n): Effect.fn.Return<boolean> {\n const { futureToleranceMillis } = yield* AuthenticationClock;\n const { challenge, evidence } = authorization;\n const { expected, revision, nowMillis: now } = facts;\n\n if (\n challenge.moduleId !== facts.moduleId ||\n challenge.action !== expected.action ||\n challenge.commandId !== expected.commandId ||\n challenge.flowId !== expected.flowId ||\n challenge.bindingDigest !== expected.bindingDigest ||\n evidence.flowId !== expected.flowId ||\n evidence.bindingDigest !== expected.bindingDigest ||\n !samePasskeyRevision(challenge.revision, expected.revision) ||\n revision.subjectId !== expected.revision.subjectId ||\n revision.securityRevision !== expected.revision.securityRevision ||\n evidence.revision.subjectId !== revision.subjectId ||\n evidence.revision.securityRevision !== revision.securityRevision\n )\n return false;\n for (const requested of [expected.revision, evidence.revision])\n if (\n requested.credentials.some(\n (item) =>\n !revision.credentials.some(\n (current) =>\n current.credentialId === item.credentialId && current.revision === item.revision,\n ),\n )\n )\n return false;\n if (\n evidence.proofs.some(\n (proof) =>\n DateTime.toEpochMillis(proof.verifiedAt) - now > futureToleranceMillis ||\n !evidence.revision.credentials.some((item) => item.credentialId === proof.credentialId),\n )\n )\n return false;\n\n return true;\n});\n\n/** SQL policy predicates remain native. This authorizes the exact evidence and\n * returns the deadline every backend must retain through its final commit. */\nexport const assessPasskeyAction = Effect.fnUntraced(function* (\n facts: PasskeyActionFacts,\n authorization: PasskeyActionAuthorization,\n policy: PasskeyManagementPolicy,\n): Effect.fn.Return<\n { readonly notBeforeMillis: number; readonly expiresBeforeMillis: number } | undefined\n> {\n if (!(yield* validPasskeyActionEvidence(facts, authorization))) return undefined;\n const { evidence } = authorization;\n const now = facts.nowMillis;\n let expiresBefore = Infinity;\n\n for (const requirement of [\n authorization.requirement,\n facts.currentRequirement,\n ...(facts.originalRequirement === undefined ? [] : [facts.originalRequirement]),\n ]) {\n const age = Math.min(policy.maximumEvidenceAgeMillis, requirement.maximumAgeMillis);\n\n const fresh = evidence.proofs.filter(\n (proof) => Math.max(0, now - DateTime.toEpochMillis(proof.verifiedAt)) < age,\n );\n\n const factors = new Set(fresh.flatMap((proof) => proof.factors));\n\n if (\n !requirement.alternatives.some(\n (alternative) =>\n alternative.factors.every((factor) => factors.has(factor)) &&\n new Set(fresh.map((proof) => proof.credentialId)).size >=\n alternative.minimumCredentials &&\n fresh.some(\n (proof) =>\n (!alternative.userVerified || proof.userVerified) &&\n (!alternative.phishingResistant || proof.phishingResistant),\n ),\n )\n )\n return undefined;\n for (const proof of fresh)\n expiresBefore = Math.min(expiresBefore, DateTime.toEpochMillis(proof.verifiedAt) + age);\n }\n\n return { notBeforeMillis: now, expiresBeforeMillis: expiresBefore };\n});\n"],"mappings":"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"}
|
|
@@ -6,8 +6,8 @@ import { allocatePasswordNextSecurityRevision, allocatePasswordValue, passwordEv
|
|
|
6
6
|
import { requireStandalone } from "./standalone.mjs";
|
|
7
7
|
import { CurrentSqlCommit, SqlBatchCommit, appendSqlBatchStatement, makeSqlCommitExecutor, registerSqlBatchPostcondition, registerSqlCommitReceipt, registerSqlPostcondition } from "./sql-commit.mjs";
|
|
8
8
|
import { makeNativeProofStore } from "./proof-native.mjs";
|
|
9
|
-
import { makePasswordCredentials, samePasswordCredentialSnapshot } from "./password-credentials.mjs";
|
|
10
9
|
import { guardSqlitePolicy } from "./sqlite-policy-guard.mjs";
|
|
10
|
+
import { makePasswordCredentials, samePasswordCredentialSnapshot } from "./password-credentials.mjs";
|
|
11
11
|
import { Crypto, DateTime, Effect, Option, Redacted, Schema } from "effect";
|
|
12
12
|
import { fragment, parameter } from "effect/sql/Statement";
|
|
13
13
|
import { CurrentCommitJournal } from "@yielded/auth/Hooks";
|
|
@@ -15,6 +15,7 @@ import * as P from "@yielded/auth/Password";
|
|
|
15
15
|
import { ProofRedemptionInput } from "@yielded/auth/Proofs";
|
|
16
16
|
import { SecurityRevision } from "@yielded/auth/Sessions";
|
|
17
17
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
18
|
+
import { AuthenticationClock } from "@yielded/auth/Operations";
|
|
18
19
|
//#region src/internal/password-native.ts
|
|
19
20
|
const unavailable = () => P.PasswordUnavailable.make({});
|
|
20
21
|
const ensure = (condition) => {
|
|
@@ -22,6 +23,7 @@ const ensure = (condition) => {
|
|
|
22
23
|
};
|
|
23
24
|
const makeNativePasswordServices = Effect.fnUntraced(function* (tables, mapping, proofs) {
|
|
24
25
|
const batch = yield* SqlBatchCommit;
|
|
26
|
+
const clockPolicy = yield* AuthenticationClock;
|
|
25
27
|
const sql = (yield* SqlClient).withoutTransforms();
|
|
26
28
|
const executor = yield* makeSqlCommitExecutor(unavailable);
|
|
27
29
|
const crypto = yield* Crypto.Crypto;
|
|
@@ -40,7 +42,7 @@ const makeNativePasswordServices = Effect.fnUntraced(function* (tables, mapping,
|
|
|
40
42
|
});
|
|
41
43
|
const exact = (table, key, value) => exactSqlText(sql, table.column(key), table.value(key, value));
|
|
42
44
|
const subjectKey = (table, key, nativeId) => sql`${table.column(key)} = ${table.value(key, nativeId)}`;
|
|
43
|
-
const run = (work) => batch === void 0 ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto))) : executor.batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto))).pipe(Effect.provideService(SqlBatchCommit, batch));
|
|
45
|
+
const run = (work) => (batch === void 0 ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto))) : executor.batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto))).pipe(Effect.provideService(SqlBatchCommit, batch))).pipe(Effect.provideService(AuthenticationClock, clockPolicy));
|
|
44
46
|
const read = Effect.fnUntraced(function* (moduleId, subjectId, wanted, locked) {
|
|
45
47
|
const nativeId = yield* mapping.subjectId.toNative(subjectId);
|
|
46
48
|
const joined = wanted !== void 0;
|
|
@@ -164,16 +166,17 @@ const makeNativePasswordServices = Effect.fnUntraced(function* (tables, mapping,
|
|
|
164
166
|
const verifierVersion = yield* allocatePasswordValue(mode, mapping.allocateRevision, mapping.allocateRevisionSync);
|
|
165
167
|
const nextSecurityRevision = yield* allocatePasswordNextSecurityRevision(mapping, mode, input.expectedRevision.securityRevision);
|
|
166
168
|
ensure(nextSecurityRevision !== input.expectedRevision.securityRevision && credentialRevision !== input.credential?.credentialRevision && verifierVersion !== input.credential?.verifierVersion);
|
|
167
|
-
const freshUntil = (requirement)
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
const freshUntil = Effect.fnUntraced(function* (requirement) {
|
|
170
|
+
const deadlines = [...new Set(input.authorization.evidence.proofs.map((proof) => DateTime.toEpochMillis(proof.verifiedAt) + requirement.maximumAgeMillis))].filter((time) => time > state.now).sort((a, b) => a - b);
|
|
171
|
+
for (const time of deadlines) if (!(yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, requirement, time))) return time;
|
|
172
|
+
});
|
|
170
173
|
if (batch !== void 0) yield* guardSqlitePolicy({
|
|
171
174
|
name: "password-subject-policy",
|
|
172
175
|
table: rawSubject.name,
|
|
173
176
|
owner: subjectKey(rawSubject, s.id, nativeId),
|
|
174
177
|
values: state.policyValues
|
|
175
178
|
});
|
|
176
|
-
const requestedDeadline = freshUntil(input.authorization.requirement), currentDeadline = freshUntil(requirement);
|
|
179
|
+
const requestedDeadline = yield* freshUntil(input.authorization.requirement), currentDeadline = yield* freshUntil(requirement);
|
|
177
180
|
ensure(requestedDeadline !== void 0 && currentDeadline !== void 0);
|
|
178
181
|
const deadline = Math.min(requestedDeadline, currentDeadline);
|
|
179
182
|
const freshness = sql`${now} >= ${state.now} and ${now} < ${deadline} and ${proofValidity}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-native.mjs","names":[],"sources":["../../src/internal/password-native.ts"],"sourcesContent":["import { CurrentCommitJournal, type LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport * as P from \"@yielded/auth/Password\";\nimport { ProofRedemptionInput } from \"@yielded/auth/Proofs\";\nimport type { SubjectId } from \"@yielded/auth/Schema\";\nimport { SecurityRevision, type AuthenticationRequirement } from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option, Redacted, Schema, Crypto } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport { fragment, parameter, type Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport type { AnyProofPersistenceMapping } from \"./models/proof-model\";\nimport { nativeSqlAlias, type NativeSqlTables, type SqlTable } from \"./native-sql-table\";\nimport { makePasswordCredentials, samePasswordCredentialSnapshot } from \"./password-credentials\";\nimport {\n allocatePasswordNextSecurityRevision,\n allocatePasswordValue,\n passwordEvidenceSatisfiedAt,\n passwordProofRedemptionMatches,\n snapshotPasswordMutation,\n validatePasswordMutation,\n} from \"./password-policy\";\nimport { makeNativeProofStore } from \"./proof-native\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n appendSqlBatchStatement,\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n registerSqlBatchPostcondition,\n registerSqlCommitReceipt,\n registerSqlPostcondition,\n SqlBatchCommit,\n} from \"./sql-commit\";\nimport { guardSqlitePolicy } from \"./sqlite-policy-guard\";\nimport { requireStandalone } from \"./standalone\";\n\nconst unavailable = () => P.PasswordUnavailable.make({});\n\nconst ensure: (condition: unknown) => asserts condition = (condition) => {\n if (!condition) throw unavailable();\n};\n\nexport const makeNativePasswordServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: AnyPasswordPersistenceMapping,\n proofs?: AnyProofPersistenceMapping,\n): Effect.fn.Return<\n { readonly passwordPersistence: P.PasswordPersistence[\"Service\"] },\n P.PasswordUnavailable,\n SqlClient | LifecycleHooks | Crypto.Crypto | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n const sql = (yield* SqlClient).withoutTransforms();\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const crypto = yield* Crypto.Crypto;\n const parent = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const s = mapping.subject,\n i = mapping.identifier,\n c = mapping.credential,\n a = mapping.authorityCredential;\n\n const subject = tables(s.table).as(\"password_subject\");\n const identifier = tables(i.table).as(\"password_identifier\");\n const credential = tables(c.table).as(\"password_credential\");\n const authority = tables(a.table).as(\"password_authority\");\n\n const rawSubject = tables(s.table),\n rawPassword = tables(c.table),\n rawAuthority = tables(a.table);\n\n // Action policy may inspect any application column. Authority columns have\n // separate checks and securityRevision intentionally changes in this mutation.\n const policyColumns = rawSubject.keys.filter(\n (key) => key !== s.id && key !== s.status && key !== s.securityRevision,\n );\n\n const now = tables.expression(mapping.clock.engineNowMillis);\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n\n const exact = (table: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, table.column(key), table.value(key, value));\n\n const subjectKey = (table: SqlTable, key: string, nativeId: unknown) =>\n sql`${table.column(key)} = ${table.value(key, nativeId)}`;\n\n const run = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n batch === undefined\n ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n : executor\n .batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const read = Effect.fnUntraced(function* (\n moduleId: string,\n subjectId: SubjectId,\n wanted?: LoginIdentifier,\n locked?: Record<string, unknown>,\n ) {\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n // Without one wanted identifier, retired identifiers would multiply authority\n // rows; read authority separately so the result grows linearly.\n const joined = wanted !== undefined;\n\n const rows =\n yield* sql`select ${subject.fields(\"password_subject_\")}, ${identifier.fields(\"password_identifier_\")}, ${credential.fields(\"password_credential_\")}${joined ? sql`, ${authority.fields(\"password_authority_\")}` : sql``}, ${now} as engine_now\n from ${subject.name}\n left join ${identifier.name} on ${subjectKey(identifier, i.subjectId, nativeId)} ${wanted === undefined ? sql`` : sql`and ${exact(identifier, i.namespace, wanted.namespace)} and ${exact(identifier, i.value, wanted.value)}`}\n left join ${credential.name} on ${subjectKey(credential, c.subjectId, nativeId)} and ${exact(credential, c.moduleId, moduleId)}\n ${joined ? sql`left join ${authority.name} on ${subjectKey(authority, a.subjectId, nativeId)}` : sql``}\n where ${subjectKey(subject, s.id, nativeId)} order by ${identifier.column(i.namespace)}, ${identifier.column(i.value)}${joined ? sql`, ${authority.column(a.credentialId)}` : sql``} limit 4097`;\n\n const authorityRows = joined\n ? rows\n : yield* sql`select ${authority.fields(\"password_authority_\")} from ${authority.name} where ${subjectKey(authority, a.subjectId, nativeId)} order by ${authority.column(a.credentialId)} limit 4097`;\n\n ensure(rows.length <= 4096 && authorityRows.length <= 4096);\n if (rows[0] === undefined) return undefined;\n const subjectRow = subject.decode(rows[0], \"password_subject_\");\n const decodedId = yield* mapping.subjectId.toSubject(subjectRow[s.id]);\n\n ensure(\n decodedId === subjectId &&\n (locked === undefined || subjectRow[s.securityRevision] === locked[s.securityRevision]),\n );\n const password = credential.decode(rows[0], \"password_credential_\");\n const passwordRow = password[c.credentialId] === null ? undefined : password;\n\n const factors = new Map<\n string,\n {\n readonly credentialId: string;\n readonly revision: SecurityRevision;\n readonly active: boolean;\n }\n >();\n\n let identifierRow: Record<string, unknown> | undefined;\n\n for (const row of authorityRows) {\n const factor = authority.decode(row, \"password_authority_\");\n\n if (\n factor[a.credentialId] !== null &&\n (a.status === undefined || a.isActiveStatus?.(factor[a.status]) === true)\n ) {\n ensure(mapping.subjectId.equals(factor[a.subjectId], nativeId));\n const id = yield* Schema.decodeUnknownEffect(Schema.String)(factor[a.credentialId]);\n const revision = yield* Schema.decodeUnknownEffect(SecurityRevision)(factor[a.revision]);\n const old = factors.get(id);\n\n ensure(old === undefined || old.revision === revision);\n factors.set(id, { credentialId: id, revision, active: true });\n }\n }\n for (const row of rows) {\n const entry = identifier.decode(row, \"password_identifier_\");\n\n if (\n entry[i.namespace] !== null &&\n mapping.subjectId.equals(entry[i.subjectId], nativeId) &&\n mapping.identifier.isCurrent(entry)\n )\n identifierRow ??= entry;\n }\n ensure(factors.size <= 64);\n\n const revision = {\n subjectId,\n securityRevision: yield* Schema.decodeUnknownEffect(SecurityRevision)(\n subjectRow[s.securityRevision],\n ),\n credentials: [...factors.values()].map(({ credentialId, revision }) => ({\n credentialId,\n revision,\n })),\n };\n\n const snapshot =\n identifierRow === undefined || passwordRow === undefined\n ? undefined\n : yield* mapping.credential\n .decode({\n moduleId,\n subject: subjectRow,\n identifier: identifierRow,\n credential: passwordRow,\n })\n .pipe(Effect.flatMap(P.snapshotPasswordCredential));\n\n if (snapshot !== undefined)\n ensure(\n snapshot.revision.subjectId === subjectId &&\n snapshot.credentialId === passwordRow?.[c.credentialId],\n );\n\n return {\n nativeId,\n subjectRow,\n policyValues:\n batch === undefined\n ? []\n : policyColumns.map((key) => ({\n column: rawSubject.selectedColumn(key),\n value: fragment([\n parameter(\n rows[0][nativeSqlAlias(\"password_subject_\", rawSubject.keys.indexOf(key))],\n ),\n ]),\n })),\n identifierRow,\n passwordRow,\n revision,\n snapshot: snapshot === undefined ? undefined : { ...snapshot, revision },\n now: yield* Schema.decodeEffect(Schema.Int)(Number(rows[0].engine_now)),\n };\n });\n\n const prepare = <V, A>(value: V, project: P.PreparePasswordCommit<V, A>) =>\n Effect.gen(function* () {\n const journal = yield* CurrentCommitJournal;\n\n const receipt = project(value, journal);\n\n ensure(receipt?._tag === \"PreparedCommit\" && Effect.isEffect(receipt.read));\n yield* registerSqlCommitReceipt(receipt);\n\n return receipt;\n });\n\n const mutation = <A>(\n original: P.PasswordMutationInput,\n action: P.PasswordAction,\n project: P.PreparePasswordCommit<P.PasswordMutationDecision, A>,\n redemption?: Parameters<P.PasswordPersistence[\"Service\"][\"resetWithProof\"]>[0][\"redemption\"],\n ) =>\n run(\n Effect.gen(function* () {\n const input = yield* snapshotPasswordMutation(original);\n const nativeId = yield* mapping.subjectId.toNative(input.expectedRevision.subjectId);\n\n const rows =\n yield* sql`select ${rawSubject.fields(\"password_lock_\")} from ${rawSubject.name} where ${subjectKey(rawSubject, s.id, nativeId)} limit 2 ${batch === undefined ? lock : sql``}`;\n\n if (rows.length === 0) return yield* prepare(\"rejected\" as const, project);\n ensure(rows.length === 1);\n const locked = rawSubject.decode(rows[0]!, \"password_lock_\");\n\n const state = yield* read(\n input.moduleId,\n input.expectedRevision.subjectId,\n input.credential?.identifier,\n locked,\n );\n\n if (state === undefined) return yield* prepare(\"rejected\" as const, project);\n const requirement = yield* s.decodeActionRequirement(state.subjectRow, action);\n\n const valid = yield* validatePasswordMutation(\n mapping,\n input,\n action,\n {\n subject: {\n active: s.isActiveStatus(state.subjectRow[s.status]),\n securityRevision: state.revision.securityRevision,\n },\n identifierCurrent: state.identifierRow !== undefined,\n credentials: state.revision.credentials.map((entry) => ({ ...entry, active: true })),\n snapshot: Effect.succeed(state.snapshot),\n requirement: Effect.succeed(requirement),\n },\n state.now,\n );\n\n if (\n valid === undefined ||\n (action === \"add-password\"\n ? state.passwordRow !== undefined\n : state.snapshot === undefined ||\n input.credential === undefined ||\n !samePasswordCredentialSnapshot(state.snapshot, input.credential))\n )\n return yield* prepare(\"rejected\" as const, project);\n let proofValidity = sql`1 = 1`;\n\n if (redemption !== undefined) {\n if (proofs === undefined) return yield* unavailable();\n\n const captured = {\n ...redemption,\n input: yield* Schema.decodeEffect(ProofRedemptionInput)(redemption.input),\n };\n\n if (!passwordProofRedemptionMatches({ ...input, redemption: captured }))\n return yield* prepare(\"rejected\" as const, project);\n // Inherit the physical owner; application owners retain the exact consumed\n // proof absence check after their callback.\n const proof = yield* makeNativeProofStore(tables, proofs, batch !== undefined);\n const redeemed = yield* proof.redeemLocked(captured.input);\n\n const proofReceipt = captured.prepare(\n redeemed.decision,\n yield* CurrentCommitJournal,\n (value) => value,\n );\n\n yield* registerSqlCommitReceipt(proofReceipt);\n if (redeemed.decision === \"rejected\") return yield* prepare(\"rejected\" as const, project);\n proofValidity = redeemed.validUntil;\n }\n const mode = \"interactive\";\n\n const credentialId =\n input.credential?.credentialId ??\n (yield* allocatePasswordValue(\n mode,\n mapping.allocateCredentialId,\n mapping.allocateCredentialIdSync,\n ));\n\n const credentialRevision = yield* allocatePasswordValue(\n mode,\n mapping.allocateRevision,\n mapping.allocateRevisionSync,\n );\n\n const verifierVersion = yield* allocatePasswordValue(\n mode,\n mapping.allocateRevision,\n mapping.allocateRevisionSync,\n );\n\n const nextSecurityRevision = yield* allocatePasswordNextSecurityRevision(\n mapping,\n mode,\n input.expectedRevision.securityRevision,\n );\n\n ensure(\n nextSecurityRevision !== input.expectedRevision.securityRevision &&\n credentialRevision !== input.credential?.credentialRevision &&\n verifierVersion !== input.credential?.verifierVersion,\n );\n\n const freshUntil = (requirement: AuthenticationRequirement) => {\n const deadlines = [\n ...new Set(\n input.authorization.evidence.proofs.map(\n (proof) => DateTime.toEpochMillis(proof.verifiedAt) + requirement.maximumAgeMillis,\n ),\n ),\n ]\n .filter((time) => time > state.now)\n .sort((a, b) => a - b);\n\n return deadlines.find(\n (time) => !passwordEvidenceSatisfiedAt(input.authorization.evidence, requirement, time),\n );\n };\n\n if (batch !== undefined)\n yield* guardSqlitePolicy({\n name: \"password-subject-policy\",\n table: rawSubject.name,\n owner: subjectKey(rawSubject, s.id, nativeId),\n values: state.policyValues,\n });\n\n const requestedDeadline = freshUntil(input.authorization.requirement),\n currentDeadline = freshUntil(requirement);\n\n ensure(requestedDeadline !== undefined && currentDeadline !== undefined);\n const deadline = Math.min(requestedDeadline, currentDeadline);\n const freshness = sql`${now} >= ${state.now} and ${now} < ${deadline} and ${proofValidity}`;\n\n const stage = Effect.fnUntraced(function* (statement: Fragment) {\n if (batch === undefined) ensure((yield* executeSqlChange(sql, statement)) === 1);\n else {\n yield* appendSqlBatchStatement(sql`${statement}`);\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = 1`));\n }\n });\n\n if (batch !== undefined) {\n if (\n s.d1ActiveStatusValue === undefined ||\n (a.status !== undefined && a.d1ActiveStatusValue === undefined)\n )\n return yield* unavailable();\n\n const factors = state.revision.credentials.map(\n (factor) =>\n sql`exists(select 1 from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${exact(rawAuthority, a.credentialId, factor.credentialId)} and ${exact(rawAuthority, a.revision, factor.revision)} ${a.status === undefined ? sql`` : sql`and ${rawAuthority.column(a.status)} = ${rawAuthority.value(a.status, a.d1ActiveStatusValue)}`})`,\n );\n\n const active =\n a.status === undefined\n ? sql`1 = 1`\n : sql`${rawAuthority.column(a.status)} = ${rawAuthority.value(a.status, a.d1ActiveStatusValue)}`;\n\n const condition = sql.and([\n sql`exists(select 1 from ${rawSubject.name} where ${subjectKey(rawSubject, s.id, nativeId)} and ${exact(rawSubject, s.securityRevision, input.expectedRevision.securityRevision)} and ${rawSubject.column(s.status)} = ${rawSubject.value(s.status, s.d1ActiveStatusValue)})`,\n sql`(select count(*) from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${active}) = ${factors.length}`,\n ...factors,\n freshness,\n ]);\n\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n if (input.credential !== undefined) {\n if (i.d1CurrentCondition === undefined) return yield* unavailable();\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql`exists(select 1 from ${tables(i.table).name} where ${exact(tables(i.table), i.namespace, input.credential.identifier.namespace)} and ${exact(tables(i.table), i.value, input.credential.identifier.value)} and ${subjectKey(tables(i.table), i.subjectId, nativeId)} and ${tables.expression(i.d1CurrentCondition({ identifier: input.credential.identifier, nativeSubjectId: nativeId }))})`,\n ),\n );\n }\n }\n const revisions = { credentialId, credentialRevision, verifierVersion };\n\n if (input.credential === undefined) {\n yield* stage(\n sql`${rawPassword.insert(c.encodeInsert({ moduleId: input.moduleId, subjectId: nativeId, replacement: input.replacement, ...revisions }))}`,\n );\n yield* stage(\n sql`${rawAuthority.insert(a.encodeInsert({ subjectId: nativeId, credentialId, revision: credentialRevision }))}`,\n );\n } else {\n const old = input.credential;\n\n yield* stage(\n sql`${rawPassword.update(c.encodeReplacement({ replacement: input.replacement, credentialRevision, verifierVersion }))} where ${subjectKey(rawPassword, c.subjectId, nativeId)} and ${exact(rawPassword, c.moduleId, input.moduleId)} and ${exact(rawPassword, c.credentialId, old.credentialId)} and ${exact(rawPassword, c.credentialRevision, old.credentialRevision)} and ${exact(rawPassword, c.verifierVersion, old.verifierVersion)} and ${exact(rawPassword, c.verifier, Redacted.value(old.verifier))} and ${exact(rawPassword, c.normalization, old.normalization)} and ${freshness}`,\n );\n yield* stage(\n sql`${rawAuthority.update(a.encodeRevision(credentialRevision))} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${exact(rawAuthority, a.credentialId, old.credentialId)} and ${exact(rawAuthority, a.revision, old.credentialRevision)}`,\n );\n }\n\n const expectedFactors = state.revision.credentials\n .filter((factor) => factor.credentialId !== credentialId)\n .concat({ credentialId, revision: credentialRevision });\n\n const finalPassword = sql`exists(select 1 from ${rawPassword.name} where ${subjectKey(rawPassword, c.subjectId, nativeId)} and ${exact(rawPassword, c.moduleId, input.moduleId)} and ${exact(rawPassword, c.credentialId, credentialId)} and ${exact(rawPassword, c.credentialRevision, credentialRevision)} and ${exact(rawPassword, c.verifierVersion, verifierVersion)} and ${exact(rawPassword, c.verifier, Redacted.value(input.replacement.verifier))} and ${exact(rawPassword, c.normalization, input.replacement.normalization)})`;\n\n const canGuardResult =\n (a.status === undefined || a.d1ActiveStatusValue !== undefined) &&\n (input.credential === undefined || i.d1CurrentCondition !== undefined);\n\n let resultingState: Fragment | undefined;\n\n if (canGuardResult) {\n const active =\n a.status === undefined\n ? sql`1 = 1`\n : sql`${rawAuthority.column(a.status)} = ${rawAuthority.value(a.status, a.d1ActiveStatusValue)}`;\n\n const conditions: Array<Fragment> = [\n finalPassword,\n freshness,\n sql`(select count(*) from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${active}) = ${expectedFactors.length}`,\n ...expectedFactors.map(\n (factor) =>\n sql`exists(select 1 from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${exact(rawAuthority, a.credentialId, factor.credentialId)} and ${exact(rawAuthority, a.revision, factor.revision)} and ${active})`,\n ),\n ];\n\n if (input.credential !== undefined) {\n const expected = input.credential;\n const identifierTable = tables(i.table);\n\n ensure(i.d1CurrentCondition !== undefined);\n conditions.push(\n sql`exists(select 1 from ${identifierTable.name} where ${exact(identifierTable, i.namespace, expected.identifier.namespace)} and ${exact(identifierTable, i.value, expected.identifier.value)} and ${subjectKey(identifierTable, i.subjectId, nativeId)} and ${tables.expression(i.d1CurrentCondition({ identifier: expected.identifier, nativeSubjectId: nativeId }))})`,\n sql`exists(select 1 from ${identifierTable.name} where ${exact(identifierTable, i.namespace, expected.identifier.namespace)} and ${exact(identifierTable, i.value, expected.identifier.value)} and ${subjectKey(identifierTable, i.subjectId, nativeId)} and ${exact(identifierTable, i.bindingRevision, expected.identifierBindingRevision)} and ${expected.identifierVerifiedAtMillis === undefined ? sql`${identifierTable.column(i.verifiedAt)} is null` : sql`${identifierTable.column(i.verifiedAt)} = ${identifierTable.value(i.verifiedAt, mapping.clock.encodeInstant(expected.identifierVerifiedAtMillis))}`})`,\n );\n }\n resultingState = sql.and(conditions);\n }\n yield* stage(\n sql`${rawSubject.update({ [s.securityRevision]: nextSecurityRevision })} where ${subjectKey(rawSubject, s.id, nativeId)} and ${exact(rawSubject, s.securityRevision, input.expectedRevision.securityRevision)} and ${rawSubject.column(s.status)} = ${rawSubject.value(s.status, locked[s.status])} and ${freshness} and ${resultingState ?? sql`1 = 1`}`,\n );\n if (batch !== undefined) {\n ensure(resultingState !== undefined);\n yield* registerSqlBatchPostcondition({\n name: \"password-replacement-authority\",\n statement: sqlBatchAssertion(\n sql,\n sql.and([\n resultingState,\n sql`exists(select 1 from ${rawSubject.name} where ${subjectKey(rawSubject, s.id, nativeId)} and ${exact(rawSubject, s.securityRevision, nextSecurityRevision)} and ${rawSubject.column(s.status)} = ${rawSubject.value(s.status, s.d1ActiveStatusValue)})`,\n ]),\n ),\n });\n } else if (Option.isSome(parent) || resultingState === undefined)\n yield* registerSqlPostcondition({\n name: \"password-replacement-authority\",\n check: Effect.gen(function* () {\n const final = yield* read(\n input.moduleId,\n input.expectedRevision.subjectId,\n input.credential?.identifier,\n );\n\n ensure(\n final !== undefined &&\n s.isActiveStatus(final.subjectRow[s.status]) &&\n final.revision.securityRevision === nextSecurityRevision &&\n final.revision.credentials.length === expectedFactors.length &&\n expectedFactors.every((factor) =>\n final.revision.credentials.some(\n (entry) =>\n entry.credentialId === factor.credentialId &&\n entry.revision === factor.revision,\n ),\n ) &&\n final.now >= state.now &&\n final.now < deadline,\n );\n const password = final.passwordRow;\n\n ensure(\n password !== undefined &&\n password[c.credentialId] === credentialId &&\n password[c.credentialRevision] === credentialRevision &&\n password[c.verifierVersion] === verifierVersion &&\n password[c.verifier] === Redacted.value(input.replacement.verifier) &&\n password[c.normalization] === input.replacement.normalization,\n );\n if (input.credential !== undefined)\n ensure(\n final.snapshot !== undefined &&\n final.snapshot.identifierBindingRevision ===\n input.credential.identifierBindingRevision &&\n final.snapshot.identifierVerifiedAtMillis ===\n input.credential.identifierVerifiedAtMillis,\n );\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepare(\"changed\" as const, project);\n }),\n );\n\n const credentials = yield* makePasswordCredentials(tables, mapping, {\n mode: \"interactive\",\n locking: false,\n coordinated: Option.isSome(parent),\n standaloneGuard: requireStandalone(unavailable, sql.transactionService),\n });\n\n const passwordPersistence: P.PasswordPersistence[\"Service\"] = {\n ...credentials,\n readForSubject: (input) =>\n executor.read(\n Effect.map(read(input.moduleId, input.subjectId), (state) =>\n state === undefined || !s.isActiveStatus(state.subjectRow[s.status])\n ? Option.none()\n : Option.fromUndefinedOr(state.snapshot),\n ),\n ),\n recoveryTarget: (input) =>\n executor.read(\n Effect.gen(function* () {\n const rows =\n yield* sql`select ${identifier.fields(\"password_recovery_\")} from ${identifier.name} where ${exact(identifier, i.namespace, input.identifier.namespace)} and ${exact(identifier, i.value, input.identifier.value)} limit 2`;\n\n if (rows.length === 0) return Option.none();\n ensure(rows.length === 1);\n const row = identifier.decode(rows[0]!, \"password_recovery_\");\n\n if (!i.isCurrent(row)) return Option.none();\n const subjectId = yield* mapping.subjectId.toSubject(row[i.subjectId]);\n const state = yield* read(input.moduleId, subjectId, input.identifier);\n\n return state === undefined ||\n !s.isActiveStatus(state.subjectRow[s.status]) ||\n state.snapshot?.identifierVerifiedAtMillis === undefined\n ? Option.none()\n : Option.some(state.snapshot);\n }),\n ),\n addIfAbsent: (input, project) => mutation(input, \"add-password\", project),\n replaceIfCurrent: (input, project) => mutation(input, \"change-password\", project),\n resetWithProof: (input, project) =>\n mutation(input, \"reset-password\", project, input.redemption),\n };\n\n return { passwordPersistence };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"password-native.mjs","names":[],"sources":["../../src/internal/password-native.ts"],"sourcesContent":["import { CurrentCommitJournal, type LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport * as P from \"@yielded/auth/Password\";\nimport { ProofRedemptionInput } from \"@yielded/auth/Proofs\";\nimport type { SubjectId } from \"@yielded/auth/Schema\";\nimport { SecurityRevision, type AuthenticationRequirement } from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect, Option, Redacted, Schema, Crypto } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport { fragment, parameter, type Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport type { AnyProofPersistenceMapping } from \"./models/proof-model\";\nimport { nativeSqlAlias, type NativeSqlTables, type SqlTable } from \"./native-sql-table\";\nimport { makePasswordCredentials, samePasswordCredentialSnapshot } from \"./password-credentials\";\nimport {\n allocatePasswordNextSecurityRevision,\n allocatePasswordValue,\n passwordEvidenceSatisfiedAt,\n passwordProofRedemptionMatches,\n snapshotPasswordMutation,\n validatePasswordMutation,\n} from \"./password-policy\";\nimport { makeNativeProofStore } from \"./proof-native\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n appendSqlBatchStatement,\n CurrentSqlCommit,\n makeSqlCommitExecutor,\n registerSqlBatchPostcondition,\n registerSqlCommitReceipt,\n registerSqlPostcondition,\n SqlBatchCommit,\n} from \"./sql-commit\";\nimport { guardSqlitePolicy } from \"./sqlite-policy-guard\";\nimport { requireStandalone } from \"./standalone\";\n\nconst unavailable = () => P.PasswordUnavailable.make({});\n\nconst ensure: (condition: unknown) => asserts condition = (condition) => {\n if (!condition) throw unavailable();\n};\n\nexport const makeNativePasswordServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: AnyPasswordPersistenceMapping,\n proofs?: AnyProofPersistenceMapping,\n): Effect.fn.Return<\n { readonly passwordPersistence: P.PasswordPersistence[\"Service\"] },\n P.PasswordUnavailable,\n SqlClient | LifecycleHooks | Crypto.Crypto | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n const clockPolicy = yield* AuthenticationClock;\n const sql = (yield* SqlClient).withoutTransforms();\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const crypto = yield* Crypto.Crypto;\n const parent = yield* Effect.serviceOption(CurrentSqlCommit);\n\n const s = mapping.subject,\n i = mapping.identifier,\n c = mapping.credential,\n a = mapping.authorityCredential;\n\n const subject = tables(s.table).as(\"password_subject\");\n const identifier = tables(i.table).as(\"password_identifier\");\n const credential = tables(c.table).as(\"password_credential\");\n const authority = tables(a.table).as(\"password_authority\");\n\n const rawSubject = tables(s.table),\n rawPassword = tables(c.table),\n rawAuthority = tables(a.table);\n\n // Action policy may inspect any application column. Authority columns have\n // separate checks and securityRevision intentionally changes in this mutation.\n const policyColumns = rawSubject.keys.filter(\n (key) => key !== s.id && key !== s.status && key !== s.securityRevision,\n );\n\n const now = tables.expression(mapping.clock.engineNowMillis);\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n\n const exact = (table: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, table.column(key), table.value(key, value));\n\n const subjectKey = (table: SqlTable, key: string, nativeId: unknown) =>\n sql`${table.column(key)} = ${table.value(key, nativeId)}`;\n\n const run = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n (batch === undefined\n ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n : executor\n .batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n .pipe(Effect.provideService(SqlBatchCommit, batch))\n ).pipe(Effect.provideService(AuthenticationClock, clockPolicy));\n\n const read = Effect.fnUntraced(function* (\n moduleId: string,\n subjectId: SubjectId,\n wanted?: LoginIdentifier,\n locked?: Record<string, unknown>,\n ) {\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n // Without one wanted identifier, retired identifiers would multiply authority\n // rows; read authority separately so the result grows linearly.\n const joined = wanted !== undefined;\n\n const rows =\n yield* sql`select ${subject.fields(\"password_subject_\")}, ${identifier.fields(\"password_identifier_\")}, ${credential.fields(\"password_credential_\")}${joined ? sql`, ${authority.fields(\"password_authority_\")}` : sql``}, ${now} as engine_now\n from ${subject.name}\n left join ${identifier.name} on ${subjectKey(identifier, i.subjectId, nativeId)} ${wanted === undefined ? sql`` : sql`and ${exact(identifier, i.namespace, wanted.namespace)} and ${exact(identifier, i.value, wanted.value)}`}\n left join ${credential.name} on ${subjectKey(credential, c.subjectId, nativeId)} and ${exact(credential, c.moduleId, moduleId)}\n ${joined ? sql`left join ${authority.name} on ${subjectKey(authority, a.subjectId, nativeId)}` : sql``}\n where ${subjectKey(subject, s.id, nativeId)} order by ${identifier.column(i.namespace)}, ${identifier.column(i.value)}${joined ? sql`, ${authority.column(a.credentialId)}` : sql``} limit 4097`;\n\n const authorityRows = joined\n ? rows\n : yield* sql`select ${authority.fields(\"password_authority_\")} from ${authority.name} where ${subjectKey(authority, a.subjectId, nativeId)} order by ${authority.column(a.credentialId)} limit 4097`;\n\n ensure(rows.length <= 4096 && authorityRows.length <= 4096);\n if (rows[0] === undefined) return undefined;\n const subjectRow = subject.decode(rows[0], \"password_subject_\");\n const decodedId = yield* mapping.subjectId.toSubject(subjectRow[s.id]);\n\n ensure(\n decodedId === subjectId &&\n (locked === undefined || subjectRow[s.securityRevision] === locked[s.securityRevision]),\n );\n const password = credential.decode(rows[0], \"password_credential_\");\n const passwordRow = password[c.credentialId] === null ? undefined : password;\n\n const factors = new Map<\n string,\n {\n readonly credentialId: string;\n readonly revision: SecurityRevision;\n readonly active: boolean;\n }\n >();\n\n let identifierRow: Record<string, unknown> | undefined;\n\n for (const row of authorityRows) {\n const factor = authority.decode(row, \"password_authority_\");\n\n if (\n factor[a.credentialId] !== null &&\n (a.status === undefined || a.isActiveStatus?.(factor[a.status]) === true)\n ) {\n ensure(mapping.subjectId.equals(factor[a.subjectId], nativeId));\n const id = yield* Schema.decodeUnknownEffect(Schema.String)(factor[a.credentialId]);\n const revision = yield* Schema.decodeUnknownEffect(SecurityRevision)(factor[a.revision]);\n const old = factors.get(id);\n\n ensure(old === undefined || old.revision === revision);\n factors.set(id, { credentialId: id, revision, active: true });\n }\n }\n for (const row of rows) {\n const entry = identifier.decode(row, \"password_identifier_\");\n\n if (\n entry[i.namespace] !== null &&\n mapping.subjectId.equals(entry[i.subjectId], nativeId) &&\n mapping.identifier.isCurrent(entry)\n )\n identifierRow ??= entry;\n }\n ensure(factors.size <= 64);\n\n const revision = {\n subjectId,\n securityRevision: yield* Schema.decodeUnknownEffect(SecurityRevision)(\n subjectRow[s.securityRevision],\n ),\n credentials: [...factors.values()].map(({ credentialId, revision }) => ({\n credentialId,\n revision,\n })),\n };\n\n const snapshot =\n identifierRow === undefined || passwordRow === undefined\n ? undefined\n : yield* mapping.credential\n .decode({\n moduleId,\n subject: subjectRow,\n identifier: identifierRow,\n credential: passwordRow,\n })\n .pipe(Effect.flatMap(P.snapshotPasswordCredential));\n\n if (snapshot !== undefined)\n ensure(\n snapshot.revision.subjectId === subjectId &&\n snapshot.credentialId === passwordRow?.[c.credentialId],\n );\n\n return {\n nativeId,\n subjectRow,\n policyValues:\n batch === undefined\n ? []\n : policyColumns.map((key) => ({\n column: rawSubject.selectedColumn(key),\n value: fragment([\n parameter(\n rows[0][nativeSqlAlias(\"password_subject_\", rawSubject.keys.indexOf(key))],\n ),\n ]),\n })),\n identifierRow,\n passwordRow,\n revision,\n snapshot: snapshot === undefined ? undefined : { ...snapshot, revision },\n now: yield* Schema.decodeEffect(Schema.Int)(Number(rows[0].engine_now)),\n };\n });\n\n const prepare = <V, A>(value: V, project: P.PreparePasswordCommit<V, A>) =>\n Effect.gen(function* () {\n const journal = yield* CurrentCommitJournal;\n\n const receipt = project(value, journal);\n\n ensure(receipt?._tag === \"PreparedCommit\" && Effect.isEffect(receipt.read));\n yield* registerSqlCommitReceipt(receipt);\n\n return receipt;\n });\n\n const mutation = <A>(\n original: P.PasswordMutationInput,\n action: P.PasswordAction,\n project: P.PreparePasswordCommit<P.PasswordMutationDecision, A>,\n redemption?: Parameters<P.PasswordPersistence[\"Service\"][\"resetWithProof\"]>[0][\"redemption\"],\n ) =>\n run(\n Effect.gen(function* () {\n const input = yield* snapshotPasswordMutation(original);\n const nativeId = yield* mapping.subjectId.toNative(input.expectedRevision.subjectId);\n\n const rows =\n yield* sql`select ${rawSubject.fields(\"password_lock_\")} from ${rawSubject.name} where ${subjectKey(rawSubject, s.id, nativeId)} limit 2 ${batch === undefined ? lock : sql``}`;\n\n if (rows.length === 0) return yield* prepare(\"rejected\" as const, project);\n ensure(rows.length === 1);\n const locked = rawSubject.decode(rows[0]!, \"password_lock_\");\n\n const state = yield* read(\n input.moduleId,\n input.expectedRevision.subjectId,\n input.credential?.identifier,\n locked,\n );\n\n if (state === undefined) return yield* prepare(\"rejected\" as const, project);\n const requirement = yield* s.decodeActionRequirement(state.subjectRow, action);\n\n const valid = yield* validatePasswordMutation(\n mapping,\n input,\n action,\n {\n subject: {\n active: s.isActiveStatus(state.subjectRow[s.status]),\n securityRevision: state.revision.securityRevision,\n },\n identifierCurrent: state.identifierRow !== undefined,\n credentials: state.revision.credentials.map((entry) => ({ ...entry, active: true })),\n snapshot: Effect.succeed(state.snapshot),\n requirement: Effect.succeed(requirement),\n },\n state.now,\n );\n\n if (\n valid === undefined ||\n (action === \"add-password\"\n ? state.passwordRow !== undefined\n : state.snapshot === undefined ||\n input.credential === undefined ||\n !samePasswordCredentialSnapshot(state.snapshot, input.credential))\n )\n return yield* prepare(\"rejected\" as const, project);\n let proofValidity = sql`1 = 1`;\n\n if (redemption !== undefined) {\n if (proofs === undefined) return yield* unavailable();\n\n const captured = {\n ...redemption,\n input: yield* Schema.decodeEffect(ProofRedemptionInput)(redemption.input),\n };\n\n if (!passwordProofRedemptionMatches({ ...input, redemption: captured }))\n return yield* prepare(\"rejected\" as const, project);\n // Inherit the physical owner; application owners retain the exact consumed\n // proof absence check after their callback.\n const proof = yield* makeNativeProofStore(tables, proofs, batch !== undefined);\n const redeemed = yield* proof.redeemLocked(captured.input);\n\n const proofReceipt = captured.prepare(\n redeemed.decision,\n yield* CurrentCommitJournal,\n (value) => value,\n );\n\n yield* registerSqlCommitReceipt(proofReceipt);\n if (redeemed.decision === \"rejected\") return yield* prepare(\"rejected\" as const, project);\n proofValidity = redeemed.validUntil;\n }\n const mode = \"interactive\";\n\n const credentialId =\n input.credential?.credentialId ??\n (yield* allocatePasswordValue(\n mode,\n mapping.allocateCredentialId,\n mapping.allocateCredentialIdSync,\n ));\n\n const credentialRevision = yield* allocatePasswordValue(\n mode,\n mapping.allocateRevision,\n mapping.allocateRevisionSync,\n );\n\n const verifierVersion = yield* allocatePasswordValue(\n mode,\n mapping.allocateRevision,\n mapping.allocateRevisionSync,\n );\n\n const nextSecurityRevision = yield* allocatePasswordNextSecurityRevision(\n mapping,\n mode,\n input.expectedRevision.securityRevision,\n );\n\n ensure(\n nextSecurityRevision !== input.expectedRevision.securityRevision &&\n credentialRevision !== input.credential?.credentialRevision &&\n verifierVersion !== input.credential?.verifierVersion,\n );\n\n const freshUntil = Effect.fnUntraced(function* (\n requirement: AuthenticationRequirement,\n ): Effect.fn.Return<number | undefined> {\n const deadlines = [\n ...new Set(\n input.authorization.evidence.proofs.map(\n (proof) => DateTime.toEpochMillis(proof.verifiedAt) + requirement.maximumAgeMillis,\n ),\n ),\n ]\n .filter((time) => time > state.now)\n .sort((a, b) => a - b);\n\n for (const time of deadlines) {\n if (\n !(yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, requirement, time))\n )\n return time;\n }\n });\n\n if (batch !== undefined)\n yield* guardSqlitePolicy({\n name: \"password-subject-policy\",\n table: rawSubject.name,\n owner: subjectKey(rawSubject, s.id, nativeId),\n values: state.policyValues,\n });\n\n const requestedDeadline = yield* freshUntil(input.authorization.requirement),\n currentDeadline = yield* freshUntil(requirement);\n\n ensure(requestedDeadline !== undefined && currentDeadline !== undefined);\n const deadline = Math.min(requestedDeadline, currentDeadline);\n const freshness = sql`${now} >= ${state.now} and ${now} < ${deadline} and ${proofValidity}`;\n\n const stage = Effect.fnUntraced(function* (statement: Fragment) {\n if (batch === undefined) ensure((yield* executeSqlChange(sql, statement)) === 1);\n else {\n yield* appendSqlBatchStatement(sql`${statement}`);\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = 1`));\n }\n });\n\n if (batch !== undefined) {\n if (\n s.d1ActiveStatusValue === undefined ||\n (a.status !== undefined && a.d1ActiveStatusValue === undefined)\n )\n return yield* unavailable();\n\n const factors = state.revision.credentials.map(\n (factor) =>\n sql`exists(select 1 from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${exact(rawAuthority, a.credentialId, factor.credentialId)} and ${exact(rawAuthority, a.revision, factor.revision)} ${a.status === undefined ? sql`` : sql`and ${rawAuthority.column(a.status)} = ${rawAuthority.value(a.status, a.d1ActiveStatusValue)}`})`,\n );\n\n const active =\n a.status === undefined\n ? sql`1 = 1`\n : sql`${rawAuthority.column(a.status)} = ${rawAuthority.value(a.status, a.d1ActiveStatusValue)}`;\n\n const condition = sql.and([\n sql`exists(select 1 from ${rawSubject.name} where ${subjectKey(rawSubject, s.id, nativeId)} and ${exact(rawSubject, s.securityRevision, input.expectedRevision.securityRevision)} and ${rawSubject.column(s.status)} = ${rawSubject.value(s.status, s.d1ActiveStatusValue)})`,\n sql`(select count(*) from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${active}) = ${factors.length}`,\n ...factors,\n freshness,\n ]);\n\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n if (input.credential !== undefined) {\n if (i.d1CurrentCondition === undefined) return yield* unavailable();\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql`exists(select 1 from ${tables(i.table).name} where ${exact(tables(i.table), i.namespace, input.credential.identifier.namespace)} and ${exact(tables(i.table), i.value, input.credential.identifier.value)} and ${subjectKey(tables(i.table), i.subjectId, nativeId)} and ${tables.expression(i.d1CurrentCondition({ identifier: input.credential.identifier, nativeSubjectId: nativeId }))})`,\n ),\n );\n }\n }\n const revisions = { credentialId, credentialRevision, verifierVersion };\n\n if (input.credential === undefined) {\n yield* stage(\n sql`${rawPassword.insert(c.encodeInsert({ moduleId: input.moduleId, subjectId: nativeId, replacement: input.replacement, ...revisions }))}`,\n );\n yield* stage(\n sql`${rawAuthority.insert(a.encodeInsert({ subjectId: nativeId, credentialId, revision: credentialRevision }))}`,\n );\n } else {\n const old = input.credential;\n\n yield* stage(\n sql`${rawPassword.update(c.encodeReplacement({ replacement: input.replacement, credentialRevision, verifierVersion }))} where ${subjectKey(rawPassword, c.subjectId, nativeId)} and ${exact(rawPassword, c.moduleId, input.moduleId)} and ${exact(rawPassword, c.credentialId, old.credentialId)} and ${exact(rawPassword, c.credentialRevision, old.credentialRevision)} and ${exact(rawPassword, c.verifierVersion, old.verifierVersion)} and ${exact(rawPassword, c.verifier, Redacted.value(old.verifier))} and ${exact(rawPassword, c.normalization, old.normalization)} and ${freshness}`,\n );\n yield* stage(\n sql`${rawAuthority.update(a.encodeRevision(credentialRevision))} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${exact(rawAuthority, a.credentialId, old.credentialId)} and ${exact(rawAuthority, a.revision, old.credentialRevision)}`,\n );\n }\n\n const expectedFactors = state.revision.credentials\n .filter((factor) => factor.credentialId !== credentialId)\n .concat({ credentialId, revision: credentialRevision });\n\n const finalPassword = sql`exists(select 1 from ${rawPassword.name} where ${subjectKey(rawPassword, c.subjectId, nativeId)} and ${exact(rawPassword, c.moduleId, input.moduleId)} and ${exact(rawPassword, c.credentialId, credentialId)} and ${exact(rawPassword, c.credentialRevision, credentialRevision)} and ${exact(rawPassword, c.verifierVersion, verifierVersion)} and ${exact(rawPassword, c.verifier, Redacted.value(input.replacement.verifier))} and ${exact(rawPassword, c.normalization, input.replacement.normalization)})`;\n\n const canGuardResult =\n (a.status === undefined || a.d1ActiveStatusValue !== undefined) &&\n (input.credential === undefined || i.d1CurrentCondition !== undefined);\n\n let resultingState: Fragment | undefined;\n\n if (canGuardResult) {\n const active =\n a.status === undefined\n ? sql`1 = 1`\n : sql`${rawAuthority.column(a.status)} = ${rawAuthority.value(a.status, a.d1ActiveStatusValue)}`;\n\n const conditions: Array<Fragment> = [\n finalPassword,\n freshness,\n sql`(select count(*) from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${active}) = ${expectedFactors.length}`,\n ...expectedFactors.map(\n (factor) =>\n sql`exists(select 1 from ${rawAuthority.name} where ${subjectKey(rawAuthority, a.subjectId, nativeId)} and ${exact(rawAuthority, a.credentialId, factor.credentialId)} and ${exact(rawAuthority, a.revision, factor.revision)} and ${active})`,\n ),\n ];\n\n if (input.credential !== undefined) {\n const expected = input.credential;\n const identifierTable = tables(i.table);\n\n ensure(i.d1CurrentCondition !== undefined);\n conditions.push(\n sql`exists(select 1 from ${identifierTable.name} where ${exact(identifierTable, i.namespace, expected.identifier.namespace)} and ${exact(identifierTable, i.value, expected.identifier.value)} and ${subjectKey(identifierTable, i.subjectId, nativeId)} and ${tables.expression(i.d1CurrentCondition({ identifier: expected.identifier, nativeSubjectId: nativeId }))})`,\n sql`exists(select 1 from ${identifierTable.name} where ${exact(identifierTable, i.namespace, expected.identifier.namespace)} and ${exact(identifierTable, i.value, expected.identifier.value)} and ${subjectKey(identifierTable, i.subjectId, nativeId)} and ${exact(identifierTable, i.bindingRevision, expected.identifierBindingRevision)} and ${expected.identifierVerifiedAtMillis === undefined ? sql`${identifierTable.column(i.verifiedAt)} is null` : sql`${identifierTable.column(i.verifiedAt)} = ${identifierTable.value(i.verifiedAt, mapping.clock.encodeInstant(expected.identifierVerifiedAtMillis))}`})`,\n );\n }\n resultingState = sql.and(conditions);\n }\n yield* stage(\n sql`${rawSubject.update({ [s.securityRevision]: nextSecurityRevision })} where ${subjectKey(rawSubject, s.id, nativeId)} and ${exact(rawSubject, s.securityRevision, input.expectedRevision.securityRevision)} and ${rawSubject.column(s.status)} = ${rawSubject.value(s.status, locked[s.status])} and ${freshness} and ${resultingState ?? sql`1 = 1`}`,\n );\n if (batch !== undefined) {\n ensure(resultingState !== undefined);\n yield* registerSqlBatchPostcondition({\n name: \"password-replacement-authority\",\n statement: sqlBatchAssertion(\n sql,\n sql.and([\n resultingState,\n sql`exists(select 1 from ${rawSubject.name} where ${subjectKey(rawSubject, s.id, nativeId)} and ${exact(rawSubject, s.securityRevision, nextSecurityRevision)} and ${rawSubject.column(s.status)} = ${rawSubject.value(s.status, s.d1ActiveStatusValue)})`,\n ]),\n ),\n });\n } else if (Option.isSome(parent) || resultingState === undefined)\n yield* registerSqlPostcondition({\n name: \"password-replacement-authority\",\n check: Effect.gen(function* () {\n const final = yield* read(\n input.moduleId,\n input.expectedRevision.subjectId,\n input.credential?.identifier,\n );\n\n ensure(\n final !== undefined &&\n s.isActiveStatus(final.subjectRow[s.status]) &&\n final.revision.securityRevision === nextSecurityRevision &&\n final.revision.credentials.length === expectedFactors.length &&\n expectedFactors.every((factor) =>\n final.revision.credentials.some(\n (entry) =>\n entry.credentialId === factor.credentialId &&\n entry.revision === factor.revision,\n ),\n ) &&\n final.now >= state.now &&\n final.now < deadline,\n );\n const password = final.passwordRow;\n\n ensure(\n password !== undefined &&\n password[c.credentialId] === credentialId &&\n password[c.credentialRevision] === credentialRevision &&\n password[c.verifierVersion] === verifierVersion &&\n password[c.verifier] === Redacted.value(input.replacement.verifier) &&\n password[c.normalization] === input.replacement.normalization,\n );\n if (input.credential !== undefined)\n ensure(\n final.snapshot !== undefined &&\n final.snapshot.identifierBindingRevision ===\n input.credential.identifierBindingRevision &&\n final.snapshot.identifierVerifiedAtMillis ===\n input.credential.identifierVerifiedAtMillis,\n );\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepare(\"changed\" as const, project);\n }),\n );\n\n const credentials = yield* makePasswordCredentials(tables, mapping, {\n mode: \"interactive\",\n locking: false,\n coordinated: Option.isSome(parent),\n standaloneGuard: requireStandalone(unavailable, sql.transactionService),\n });\n\n const passwordPersistence: P.PasswordPersistence[\"Service\"] = {\n ...credentials,\n readForSubject: (input) =>\n executor.read(\n Effect.map(read(input.moduleId, input.subjectId), (state) =>\n state === undefined || !s.isActiveStatus(state.subjectRow[s.status])\n ? Option.none()\n : Option.fromUndefinedOr(state.snapshot),\n ),\n ),\n recoveryTarget: (input) =>\n executor.read(\n Effect.gen(function* () {\n const rows =\n yield* sql`select ${identifier.fields(\"password_recovery_\")} from ${identifier.name} where ${exact(identifier, i.namespace, input.identifier.namespace)} and ${exact(identifier, i.value, input.identifier.value)} limit 2`;\n\n if (rows.length === 0) return Option.none();\n ensure(rows.length === 1);\n const row = identifier.decode(rows[0]!, \"password_recovery_\");\n\n if (!i.isCurrent(row)) return Option.none();\n const subjectId = yield* mapping.subjectId.toSubject(row[i.subjectId]);\n const state = yield* read(input.moduleId, subjectId, input.identifier);\n\n return state === undefined ||\n !s.isActiveStatus(state.subjectRow[s.status]) ||\n state.snapshot?.identifierVerifiedAtMillis === undefined\n ? Option.none()\n : Option.some(state.snapshot);\n }),\n ),\n addIfAbsent: (input, project) => mutation(input, \"add-password\", project),\n replaceIfCurrent: (input, project) => mutation(input, \"change-password\", project),\n resetWithProof: (input, project) =>\n mutation(input, \"reset-password\", project, input.redemption),\n };\n\n return { passwordPersistence };\n});\n"],"mappings":"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"}
|
|
@@ -2,11 +2,13 @@ import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
|
2
2
|
import { DateTime, Effect, Redacted } from "effect";
|
|
3
3
|
import { PasswordUnavailable, snapshotPasswordCredential, snapshotPasswordRequirement, snapshotPasswordRevision } from "@yielded/auth/Password";
|
|
4
4
|
import { snapshotAuthenticationEvidence } from "@yielded/auth/Sessions";
|
|
5
|
+
import { AuthenticationClock } from "@yielded/auth/Operations";
|
|
5
6
|
//#region src/internal/password-policy.ts
|
|
6
7
|
const unavailable = () => PasswordUnavailable.make({});
|
|
7
8
|
const samePasswordIdentifier = (left, right) => left.namespace === right.namespace && left.value === right.value;
|
|
8
9
|
const samePasswordCredential = (left, right) => left.moduleId === right.moduleId && left.revision.subjectId === right.revision.subjectId && left.revision.securityRevision === right.revision.securityRevision && left.credentialId === right.credentialId && left.credentialRevision === right.credentialRevision && left.verifierVersion === right.verifierVersion && left.normalization === right.normalization && left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis && left.revision.credentials.length === right.revision.credentials.length && left.revision.credentials.every((entry) => right.revision.credentials.some((other) => other.credentialId === entry.credentialId && other.revision === entry.revision)) && left.identifierBindingRevision === right.identifierBindingRevision && samePasswordIdentifier(left.identifier, right.identifier) && Redacted.value(left.verifier) === Redacted.value(right.verifier);
|
|
9
|
-
const passwordEvidenceSatisfiedAt = (evidence, requirement, now)
|
|
10
|
+
const passwordEvidenceSatisfiedAt = Effect.fnUntraced(function* (evidence, requirement, now) {
|
|
11
|
+
const { futureToleranceMillis } = yield* AuthenticationClock;
|
|
10
12
|
if (evidence.proofs.length > 64 || evidence.revision.credentials.length > 64) return false;
|
|
11
13
|
const revisions = /* @__PURE__ */ new Map();
|
|
12
14
|
for (const item of evidence.revision.credentials) {
|
|
@@ -14,15 +16,14 @@ const passwordEvidenceSatisfiedAt = (evidence, requirement, now) => {
|
|
|
14
16
|
revisions.set(item.credentialId, item.revision);
|
|
15
17
|
}
|
|
16
18
|
if (evidence.proofs.some((proof) => !revisions.has(proof.credentialId))) return false;
|
|
17
|
-
if (evidence.proofs.some((proof) =>
|
|
19
|
+
if (evidence.proofs.some((proof) => DateTime.toEpochMillis(proof.verifiedAt) - now > futureToleranceMillis)) return false;
|
|
18
20
|
const eligible = evidence.proofs.filter((proof) => {
|
|
19
|
-
|
|
20
|
-
return age >= 0 && age < requirement.maximumAgeMillis;
|
|
21
|
+
return Math.max(0, now - DateTime.toEpochMillis(proof.verifiedAt)) < requirement.maximumAgeMillis;
|
|
21
22
|
});
|
|
22
23
|
const factors = new Set(eligible.flatMap((proof) => proof.factors));
|
|
23
24
|
const credentials = new Set(eligible.map((proof) => proof.credentialId));
|
|
24
25
|
return requirement.alternatives.some((alternative) => alternative.factors.every((factor) => factors.has(factor)) && credentials.size >= alternative.minimumCredentials && eligible.some((proof) => (!alternative.userVerified || proof.userVerified) && (!alternative.phishingResistant || proof.phishingResistant)));
|
|
25
|
-
};
|
|
26
|
+
});
|
|
26
27
|
const snapshotPasswordMutation = Effect.fn("PasswordPersistence.snapshotMutation")(function* (input) {
|
|
27
28
|
const expectedRevision = snapshotPasswordRevision(input.expectedRevision);
|
|
28
29
|
const evidence = yield* snapshotAuthenticationEvidence(input.authorization.evidence).pipe(Effect.mapError(unavailable));
|
|
@@ -83,7 +84,7 @@ const validatePasswordMutation = Effect.fnUntraced(function* (policy, input, act
|
|
|
83
84
|
if (snapshot === void 0 || !samePasswordCredential(snapshot, input.credential)) return void 0;
|
|
84
85
|
}
|
|
85
86
|
const requirement = yield* current.requirement;
|
|
86
|
-
if (!passwordEvidenceSatisfiedAt(evidence, input.authorization.requirement, now) || !passwordEvidenceSatisfiedAt(evidence, requirement, now)) return void 0;
|
|
87
|
+
if (!(yield* passwordEvidenceSatisfiedAt(evidence, input.authorization.requirement, now)) || !(yield* passwordEvidenceSatisfiedAt(evidence, requirement, now))) return void 0;
|
|
87
88
|
if (input.invalidation.existingSessions === "immediate" && policy.sessionInvalidation !== "same-authority-immediate") return void 0;
|
|
88
89
|
return now;
|
|
89
90
|
});
|