{"version":3,"file":"passkey-native-registration.mjs","names":[],"sources":["../../src/internal/passkey-native-registration.ts"],"sourcesContent":["/* oxlint-disable no-explicit-any -- physical mapping generics are erased only at this shared adapter boundary. */\nimport * as M from \"@yielded/auth/Passkey\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { randomId } from \"./crypto\";\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport type {\n  PasskeyRegistrationMapping,\n  PasskeyRegistrationWriter,\n} from \"./models/passkey-write-model\";\nimport { passkeyCanonicalJson, passkeyDigest } from \"./passkey-actions\";\nimport { preparePasskeyNative, type NativePasskeyServices } from \"./passkey-native\";\nimport { invalidPasskeyState } from \"./passkey-native-state\";\nimport { makePasskeyNativeWrites } from \"./passkey-native-write\";\nimport { passkeyMatchesAccess, validPasskeyCeremony } from \"./passkey-policy\";\nimport { appendSqlBatchStatement } from \"./sql-commit\";\nimport type { TableModel } from \"./table-model\";\n\n// Foreign mapping expressions are compiled at the NativeSqlTables boundary.\nexport type NativePasskeyRegistrationMapping<R> = PasskeyRegistrationMapping<\n  TableModel,\n  TableModel,\n  TableModel,\n  TableModel,\n  unknown,\n  R,\n  any\n>;\n\nexport const makePasskeyNativeRegistration = <R>(\n  base: NativePasskeyServices,\n  mapping: NativePasskeyRegistrationMapping<R>,\n) => {\n  const { state, run, advisory } = base;\n  const { sql, tables, subject, credential, ceremony: flow } = state;\n\n  const { write, postcondition, check, insertCredential, subjectCondition, owned } =\n    makePasskeyNativeWrites(base, mapping);\n\n  const invalidState = invalidPasskeyState;\n  const codec = passkeyCanonicalJson(mapping.registration.schema);\n\n  const describe = Effect.fnUntraced(function* (original: R) {\n    const encoded = codec.encode(original);\n\n    if (!(new TextEncoder().encode(encoded).length <= 1048576)) return yield* invalidState();\n    const registration = codec.decode(encoded);\n\n    const labels = yield* Schema.decodeEffect(\n      Schema.Struct({ name: M.PasskeyLabel, displayName: M.PasskeyLabel }),\n    )(mapping.registration.describe(registration));\n\n    return {\n      encoded,\n      registration,\n      ...labels,\n      fingerprint: yield* passkeyDigest(mapping.registration.schema, registration),\n    };\n  });\n\n  const writer: PasskeyRegistrationWriter<R> = {\n    inspect: (original) =>\n      advisory(\n        Effect.gen(function* () {\n          const value = yield* describe(original);\n\n          const rows =\n            yield* sql`select case when ${tables.expression(mapping.registration.eligible(value.registration))} then 1 else 0 end as eligible`;\n\n          return {\n            fingerprint: value.fingerprint,\n            name: value.name,\n            displayName: value.displayName,\n            eligible: rows.length === 1 && Number(rows[0]!.eligible) === 1,\n          };\n        }),\n      ),\n    issueRegistration: (input, prepare) =>\n      run(\n        Effect.gen(function* () {\n          const ceremony = M.snapshotPasskeySync(M.PasskeyCeremony, input.ceremony);\n          const value = yield* describe(input.registration);\n\n          if (\n            !flow.validModule(ceremony.moduleId) ||\n            !validPasskeyCeremony(ceremony) ||\n            ceremony.purpose !== \"registration\" ||\n            ceremony.context._tag !== \"Registration\" ||\n            !ceremony.profile.primarySignIn ||\n            ceremony.profile.residentKey !== \"required\" ||\n            ceremony.profile.userVerification !== \"required\" ||\n            ceremony.allowedCredentials.length !== 0 ||\n            ceremony.context.fingerprint !== value.fingerprint ||\n            ceremony.context.name !== value.name ||\n            ceremony.context.displayName !== value.displayName\n          )\n            return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n          const statement = flow.insert(ceremony, { [mapping.applicationSnapshot]: value.encoded });\n\n          if (base.batch) yield* flow.stage(statement, 1);\n          else if ((yield* flow.change(statement)) !== 1)\n            return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n\n          return yield* preparePasskeyNative({ _tag: \"Issued\", ceremony }, prepare);\n        }),\n        \"statement\",\n      ),\n    completeRegistration: (input, prepare) =>\n      run(\n        Effect.gen(function* () {\n          const ceremony = M.snapshotPasskeySync(M.PasskeyCeremony, input.ceremony);\n          const access = M.snapshotPasskeySync(M.PasskeyAccess, input.access);\n          const verified = M.snapshotPasskeySync(M.PasskeyRegistrationVerified, input.verified);\n\n          if (\n            !flow.validModule(ceremony.moduleId) ||\n            ceremony.context._tag !== \"Registration\" ||\n            ceremony.purpose !== \"registration\" ||\n            !passkeyMatchesAccess(ceremony, access) ||\n            !verified.userVerified\n          )\n            return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n\n          const rows =\n            yield* sql`select ${flow.flow.fields(\"flow_\")}, ${flow.now} as \"engineNow\" from ${flow.flow.name} where ${flow.access(access, ceremony)}`;\n\n          if (rows.length !== 1) return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n          const row = flow.flow.decode(rows[0]!, \"flow_\");\n\n          const encoded = yield* Schema.decodeUnknownEffect(Schema.String)(\n            row[mapping.applicationSnapshot],\n          );\n\n          const registration = codec.decode(encoded);\n          const value = yield* describe(registration);\n\n          if (\n            value.fingerprint !== ceremony.context.fingerprint ||\n            value.name !== ceremony.context.name ||\n            value.displayName !== ceremony.context.displayName\n          )\n            return yield* preparePasskeyNative({ _tag: \"Rejected\" }, prepare);\n          const eligible = tables.expression(mapping.registration.eligible(registration));\n          const marker = yield* randomId;\n          const provision = mapping.registration.subject({ registration, ceremony, marker });\n          const nativeId = provision.subjectId;\n          const subjectId = mapping.read.subjectIds.toSubject(nativeId);\n\n          if (\n            !mapping.read.subjectIds.equals(mapping.read.subjectIds.toNative(subjectId), nativeId)\n          )\n            return yield* invalidState();\n          if (base.batch) yield* appendSqlBatchStatement(sqlBatchAssertion(sql, eligible));\n          else yield* check(eligible);\n          // Inserting the new subject acquires its lock before the challenge or\n          // credential is mutated. A failed consume rolls this insertion back.\n          yield* write(subject.insert(provision.values), 1);\n          let revision: M.PasskeyCredential[\"revision\"];\n          let nowMillis: number;\n          let subjectRow: Readonly<Record<string, unknown>> = provision.values;\n\n          if (base.batch) {\n            if (!mapping.read.subject.isActiveStatus(provision.values[mapping.read.subject.status]))\n              return yield* invalidState();\n            revision = yield* Schema.decodeUnknownEffect(M.PasskeyRevision)({\n              subjectId,\n              securityRevision: provision.values[mapping.read.subject.securityRevision],\n              credentials: [],\n            });\n            nowMillis = yield* Schema.decodeEffect(Schema.Int)(Number(rows[0]!.engineNow));\n          } else {\n            const current = yield* state.readAuthority(nativeId, true);\n\n            if (!(current !== undefined)) return yield* invalidState();\n            subjectRow = current.row;\n            revision = current.revision;\n            nowMillis = current.nowMillis;\n          }\n\n          const cap = M.snapshotPasskeySync(\n            M.PasskeyManagementPolicy,\n            mapping.write.policy.management(subjectRow),\n          ).maximumCredentials;\n\n          const count = sql`(select count(*) from ${credential.name} where ${owned(nativeId)})`;\n\n          const finalEligibility = tables.expression(\n            mapping.registration.finalEligibility({ registration, subjectId: nativeId }),\n          );\n\n          yield* write(\n            flow.consume(\n              access,\n              ceremony,\n              sql`${flow.exact(mapping.applicationSnapshot, encoded)} and ${subjectCondition(revision)} and ${count} < ${cap} and ${finalEligibility}`,\n            ),\n            1,\n          );\n\n          const inserted = yield* insertCredential(\n            nativeId,\n            ceremony,\n            verified,\n            revision,\n            value.displayName,\n            nowMillis,\n            subjectRow,\n          );\n\n          yield* postcondition(\n            \"passkey-registration-subject\",\n            sql`${inserted.condition} and ${flow.absent(access)} and ${subjectCondition(revision)} and ${count} <= ${cap} and ${finalEligibility} and ${state.now} < ${ceremony.expiresAtMillis}`,\n          );\n\n          return yield* preparePasskeyNative({ _tag: \"RegistrationAccepted\" }, prepare);\n        }),\n      ),\n  };\n\n  return { passkeyRegistrationAuthority: writer };\n};\n"],"mappings":"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"}