@yielded/auth-persistence 0.1.0-beta.30 → 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 +11 -7
- package/dist/Adapter.d.mts +1 -1
- 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 +49 -13
- 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/models/password-model.d.mts +2 -1
- package/dist/internal/models/password-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 +5 -3
- package/dist/internal/passkey-managed.mjs.map +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 +22 -5
- 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/password-registration-native.d.mts +1 -1
- package/dist/internal/persistence.d.mts +3 -0
- package/dist/internal/persistence.mjs +99 -42
- 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/registration.mjs +17 -8
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +8 -6
- 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 +4 -2
- 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 +24 -8
- 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 +67 -38
- 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 +17 -19
- 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/sqlite-policy-guard.mjs +23 -0
- package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
- package/dist/internal/step-up-state.mjs +19 -6
- package/dist/internal/step-up-state.mjs.map +1 -1
- package/dist/internal/storage-mapping.d.mts +9 -5
- package/dist/internal/storage-mapping.mjs +17 -5
- package/dist/internal/storage-mapping.mjs.map +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 +72 -15
- 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/models/password-model.ts +2 -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-managed.ts +8 -2
- 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 +45 -11
- package/src/internal/password-policy.ts +16 -8
- package/src/internal/persistence.ts +360 -204
- package/src/internal/phone-native.ts +1 -1
- package/src/internal/registration.ts +53 -40
- package/src/internal/session-native-authority.ts +26 -8
- package/src/internal/session-native-login.ts +9 -4
- 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 +32 -10
- package/src/internal/session-native-stateful.ts +147 -52
- package/src/internal/session-native-step-up.ts +31 -24
- 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/sqlite-policy-guard.ts +34 -0
- package/src/internal/step-up-state.ts +43 -6
- package/src/internal/storage-mapping.ts +40 -4
- package/src/internal/storage-tables.ts +76 -0
- package/src/internal/storage-validation.ts +127 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-state.mjs","names":[],"sources":["../../../src/internal/oauth/native-state.ts"],"sourcesContent":["import {\n OAuthAccountRevision,\n OAuthCredentialSnapshot,\n OAuthExternalIdentity,\n type OAuthCredentialKey,\n} from \"@yielded/auth/OAuth\";\nimport type { SubjectId } from \"@yielded/auth/Schema\";\nimport { Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport type { OAuthSignInMapping } from \"../models/oauth-model\";\nimport type { NativeSqlTables } from \"../native-sql-table\";\nimport { exactSqlText } from \"../sql-change\";\nimport type { SqlExpression, TableModel } from \"../table-model\";\nimport { invariant, oauthIdentityKey } from \"./state\";\n\n// Expression handles are checked by the physical compiler, not domain codecs.\nexport type OAuthNativeReadMapping = OAuthSignInMapping<\n TableModel,\n TableModel,\n TableModel,\n TableModel,\n TableModel,\n unknown,\n SqlExpression\n>;\n\ntype Row = Readonly<Record<string, unknown>>;\n\n/** Unlocked snapshots and explicit subject-first reads share decoding rules.\n * No observation registry or discovered-identity placeholder is retained. */\nexport const makeOAuthNativeAuthority = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: Pick<OAuthNativeReadMapping, \"subject\" | \"authority\" | \"subjectId\" | \"clock\">,\n) {\n const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n const subject = tables(mapping.subject.table);\n const authority = tables(mapping.authority.table);\n const s = subject.as(\"oauth_subject\");\n const a = authority.as(\"oauth_authority\");\n const now = tables.expression(mapping.clock.engineNowMillis);\n\n const lock = sql.onDialectOrElse({\n pg: () => sql`for update`,\n mysql: () => sql`for update`,\n orElse: () => sql``,\n });\n\n const active = (kind: \"subject\" | \"authority\", alias: string) => {\n const descriptor = mapping[kind];\n const physical = tables(descriptor.table);\n\n return sql`(select * from ${physical.name} where ${tables.expression(descriptor.activeCondition)}) as ${sql(alias)}`;\n };\n\n const revision = Effect.fnUntraced(function* (\n subjectRow: Row,\n factors: ReadonlyArray<Row>,\n nativeId: unknown,\n ) {\n invariant(mapping.subject.isActiveStatus(subjectRow[mapping.subject.status]));\n invariant(mapping.subjectId.equals(subjectRow[mapping.subject.id], nativeId));\n invariant(factors.length > 0 && factors.length <= 64);\n invariant(\n factors.every(\n (row) =>\n mapping.subjectId.equals(row[mapping.authority.subjectId], nativeId) &&\n mapping.authority.isActiveStatus(row[mapping.authority.status]),\n ),\n );\n\n const credentials = factors.map((row) => ({\n credentialId: row[mapping.authority.credentialId],\n revision: row[mapping.authority.revision],\n }));\n\n invariant(new Set(credentials.map((item) => item.credentialId)).size === credentials.length);\n\n const value = yield* Schema.decodeUnknownEffect(Schema.toType(OAuthAccountRevision))({\n subjectId: yield* mapping.subjectId.toSubject(nativeId),\n securityRevision: subjectRow[mapping.subject.securityRevision],\n credentials,\n });\n\n return {\n ...value,\n credentials: [...value.credentials].sort((left, right) =>\n left.credentialId.localeCompare(right.credentialId),\n ),\n };\n });\n\n const captureSubject = Effect.fnUntraced(function* (subjectId: SubjectId, locking: boolean) {\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"s_\")}, ${now} as engine_now from ${subject.name} where ${subject.column(mapping.subject.id)} = ${subject.value(mapping.subject.id, nativeId)} and ${tables.expression(mapping.subject.activeCondition)} limit 2 ${locking ? lock : sql``}`;\n\n invariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n const row = subject.decode(rows[0], \"s_\");\n\n invariant(mapping.subjectId.equals(row[mapping.subject.id], nativeId));\n\n return {\n nativeId,\n subject: row,\n securityRevision: yield* Schema.decodeUnknownEffect(\n OAuthAccountRevision.fields.securityRevision,\n )(row[mapping.subject.securityRevision]),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(rows[0].engine_now)),\n };\n });\n\n const capture = Effect.fnUntraced(function* (subjectId: SubjectId, locking: boolean) {\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n if (locking) {\n // Never acquire a credential, identity, flow or policy lock before this.\n const subjects =\n yield* sql`select ${subject.fields(\"s_\")}, ${now} as engine_now from ${subject.name}\n where ${subject.column(mapping.subject.id)} = ${subject.value(mapping.subject.id, nativeId)}\n and ${tables.expression(mapping.subject.activeCondition)} ${lock}`;\n\n if (subjects.length === 0) return undefined;\n invariant(subjects.length === 1);\n const subjectRow = subject.decode(subjects[0]!, \"s_\");\n\n const factors = yield* sql`select ${authority.fields(\"a_\")} from ${authority.name}\n where ${authority.column(mapping.authority.subjectId)} = ${authority.value(mapping.authority.subjectId, nativeId)}\n and ${tables.expression(mapping.authority.activeCondition)}\n order by ${authority.column(mapping.authority.credentialId)} limit 65 ${lock}`;\n\n if (factors.length === 0) return undefined;\n\n return {\n nativeId,\n subject: subjectRow,\n revision: yield* revision(\n subjectRow,\n factors.map((row) => authority.decode(row, \"a_\")),\n nativeId,\n ),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(subjects[0]!.engine_now)),\n };\n }\n\n // Bind each column's own native codec; the columns need not share an encoding.\n const rows = yield* sql`select ${s.fields(\"s_\")}, ${a.fields(\"a_\")}, ${now} as engine_now\n from ${active(\"subject\", \"oauth_subject\")}\n join ${active(\"authority\", \"oauth_authority\")} on\n ${a.column(mapping.authority.subjectId)} = ${a.value(mapping.authority.subjectId, nativeId)}\n where ${s.column(mapping.subject.id)} = ${s.value(mapping.subject.id, nativeId)}\n order by ${a.column(mapping.authority.credentialId)} limit 65`;\n\n if (rows.length === 0) return undefined;\n const subjectRow = s.decode(rows[0]!, \"s_\");\n\n return {\n nativeId,\n subject: subjectRow,\n revision: yield* revision(\n subjectRow,\n rows.map((row) => a.decode(row, \"a_\")),\n nativeId,\n ),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(rows[0]!.engine_now)),\n };\n });\n\n return { sql, now, lock, subject, authority, capture, captureSubject, revision };\n});\n\nexport const makeOAuthNativeState = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: OAuthNativeReadMapping,\n) {\n const state = yield* makeOAuthNativeAuthority(tables, mapping);\n const { sql, subject, authority, revision } = state;\n const ownership = tables(mapping.ownership.table);\n const credential = tables(mapping.credential.table);\n const s = subject.as(\"oauth_subject\");\n const o = ownership.as(\"oauth_identity\");\n const c = credential.as(\"oauth_credential\");\n const a = authority.as(\"oauth_authority\");\n\n const exact = (\n left: Parameters<typeof exactSqlText>[1],\n right: Parameters<typeof exactSqlText>[2],\n ) => exactSqlText(sql, left, right);\n\n const active = (kind: \"subject\" | \"credential\" | \"authority\", alias: string) => {\n const descriptor = mapping[kind];\n const physical = tables(descriptor.table);\n\n return sql`(select * from ${physical.name} where ${tables.expression(descriptor.activeCondition)}) as ${sql(alias)}`;\n };\n\n const joinedIds =\n ownership.unencodedTextColumn?.(mapping.ownership.subjectId) !== undefined &&\n subject.unencodedTextColumn?.(mapping.subject.id) !== undefined &&\n credential.unencodedTextColumn?.(mapping.credential.subjectId) !== undefined &&\n authority.unencodedTextColumn?.(mapping.authority.subjectId) !== undefined;\n\n const decodeSnapshot = Effect.fnUntraced(function* (\n rows: ReadonlyArray<Row>,\n moduleId: string,\n nativeId?: unknown,\n ) {\n if (rows.length === 0) return undefined;\n const owned = o.decode(rows[0]!, \"o_\");\n\n const identity = yield* Schema.decodeUnknownEffect(OAuthExternalIdentity)({\n provider: owned[mapping.ownership.provider],\n issuer: owned[mapping.ownership.issuer],\n subject: owned[mapping.ownership.externalSubject],\n });\n\n const identityKey = yield* oauthIdentityKey(identity);\n\n invariant(owned[mapping.ownership.identityKey] === identityKey);\n const ownerId = mapping.ownership.decodeSubjectId(owned);\n\n if (nativeId !== undefined) invariant(mapping.subjectId.equals(ownerId, nativeId));\n const login = c.decode(rows[0]!, \"c_\");\n\n invariant(mapping.subjectId.equals(login[mapping.credential.subjectId], ownerId));\n invariant(login[mapping.credential.identityKey] === identityKey);\n invariant(mapping.credential.isActiveStatus(login[mapping.credential.status]));\n\n const current = yield* revision(\n s.decode(rows[0]!, \"s_\"),\n rows.map((row) => a.decode(row, \"a_\")),\n ownerId,\n );\n\n const credentialId = login[mapping.credential.credentialId];\n const credentialRevision = login[mapping.credential.credentialRevision];\n\n if (\n !current.credentials.some(\n (item) => item.credentialId === credentialId && item.revision === credentialRevision,\n )\n )\n return undefined;\n\n return yield* Schema.decodeUnknownEffect(Schema.toType(OAuthCredentialSnapshot))({\n moduleId,\n identity,\n credentialId,\n credentialRevision,\n revision: current,\n requirement: mapping.subject.decodeAuthenticationRequirement(s.decode(rows[0]!, \"s_\")),\n });\n });\n\n const resolve = Effect.fnUntraced(function* (\n moduleId: string,\n identity: typeof OAuthExternalIdentity.Type,\n ) {\n const identityKey = yield* oauthIdentityKey(identity);\n let nativeId: unknown;\n\n if (!joinedIds) {\n const found = yield* sql`select ${ownership.fields(\"o_\")} from ${ownership.name}\n where ${exact(ownership.column(mapping.ownership.identityKey), ownership.value(mapping.ownership.identityKey, identityKey))}`;\n\n if (found.length === 0) return undefined;\n invariant(found.length === 1);\n const row = ownership.decode(found[0]!, \"o_\");\n\n invariant(\n row[mapping.ownership.provider] === identity.provider &&\n row[mapping.ownership.issuer] === identity.issuer &&\n row[mapping.ownership.externalSubject] === identity.subject,\n );\n nativeId = mapping.ownership.decodeSubjectId(row);\n }\n\n const bindOwner = (table: typeof s, key: string) =>\n joinedIds\n ? exact(table.column(key), o.column(mapping.ownership.subjectId))\n : sql`${table.column(key)} = ${table.value(key, nativeId)}`;\n\n const rows =\n yield* sql`select ${o.fields(\"o_\")}, ${s.fields(\"s_\")}, ${c.fields(\"c_\")}, ${a.fields(\"a_\")}\n from ${o.name}\n join ${active(\"subject\", \"oauth_subject\")} on ${bindOwner(s, mapping.subject.id)}\n join ${active(\"credential\", \"oauth_credential\")} on ${bindOwner(c, mapping.credential.subjectId)}\n and ${exact(c.column(mapping.credential.moduleId), c.value(mapping.credential.moduleId, moduleId))}\n and ${exact(c.column(mapping.credential.identityKey), c.value(mapping.credential.identityKey, identityKey))}\n join ${active(\"authority\", \"oauth_authority\")} on ${bindOwner(a, mapping.authority.subjectId)}\n where ${exact(o.column(mapping.ownership.identityKey), o.value(mapping.ownership.identityKey, identityKey))}\n order by ${a.column(mapping.authority.credentialId)} limit 65`;\n\n const result = yield* decodeSnapshot(rows, moduleId, joinedIds ? undefined : nativeId);\n\n if (result !== undefined)\n invariant(\n result.identity.provider === identity.provider &&\n result.identity.issuer === identity.issuer &&\n result.identity.subject === identity.subject,\n );\n\n return result;\n });\n\n const readCredential = Effect.fnUntraced(function* (\n input: typeof OAuthCredentialKey.Type,\n metadataAccess: Fragment = sql`true`,\n ) {\n const nativeId = yield* mapping.subjectId.toNative(input.subjectId);\n let identityKey: unknown;\n\n const direct =\n credential.unencodedTextColumn?.(mapping.credential.identityKey) !== undefined &&\n ownership.unencodedTextColumn?.(mapping.ownership.identityKey) !== undefined;\n\n if (!direct) {\n const selected = yield* sql`select ${credential.fields(\"c_\")} from ${credential.name}\n where ${exact(credential.column(mapping.credential.moduleId), credential.value(mapping.credential.moduleId, input.moduleId))}\n and ${exact(credential.column(mapping.credential.credentialId), credential.value(mapping.credential.credentialId, input.credentialId))}\n and ${credential.column(mapping.credential.subjectId)} = ${credential.value(mapping.credential.subjectId, nativeId)}`;\n\n if (selected.length === 0) return undefined;\n invariant(selected.length === 1);\n identityKey = credential.decode(selected[0]!, \"c_\")[mapping.credential.identityKey];\n }\n\n const owner = (table: typeof s, key: string) =>\n sql`${table.column(key)} = ${table.value(key, nativeId)}`;\n\n const rows =\n yield* sql`select ${o.fields(\"o_\")}, ${s.fields(\"s_\")}, ${c.fields(\"c_\")}, ${a.fields(\"a_\")}\n from ${active(\"credential\", \"oauth_credential\")}\n join ${o.name} on ${direct ? exact(o.column(mapping.ownership.identityKey), c.column(mapping.credential.identityKey)) : exact(o.column(mapping.ownership.identityKey), o.value(mapping.ownership.identityKey, identityKey))}\n and ${owner(o, mapping.ownership.subjectId)}\n join ${active(\"subject\", \"oauth_subject\")} on ${owner(s, mapping.subject.id)}\n join ${active(\"authority\", \"oauth_authority\")} on ${owner(a, mapping.authority.subjectId)}\n where ${metadataAccess} and ${owner(c, mapping.credential.subjectId)}\n and ${exact(c.column(mapping.credential.moduleId), c.value(mapping.credential.moduleId, input.moduleId))}\n and ${exact(c.column(mapping.credential.credentialId), c.value(mapping.credential.credentialId, input.credentialId))}\n order by ${a.column(mapping.authority.credentialId)} limit 65`;\n\n return yield* decodeSnapshot(rows, input.moduleId, nativeId);\n });\n\n return { ...state, ownership, credential, resolve, readCredential };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"native-state.mjs","names":[],"sources":["../../../src/internal/oauth/native-state.ts"],"sourcesContent":["import {\n OAuthAccountRevision,\n OAuthCredentialSnapshot,\n OAuthExternalIdentity,\n type OAuthCredentialKey,\n} from \"@yielded/auth/OAuth\";\nimport type { SubjectId } from \"@yielded/auth/Schema\";\nimport type { AuthenticationRequirement } from \"@yielded/auth/Sessions\";\nimport { Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport type { PersistenceMappingError } from \"../mapping-error\";\nimport type { OAuthSignInMapping, OAuthSubjectReadTable } from \"../models/oauth-model\";\nimport type { NativeSqlTables } from \"../native-sql-table\";\nimport { exactSqlText } from \"../sql-change\";\nimport { canJoinTextColumns, withStorageValidation } from \"../storage-validation\";\nimport type { SqlExpression, TableModel } from \"../table-model\";\nimport { invariant, oauthIdentityKey, unavailable } from \"./state\";\n\n// Expression handles are checked by the physical compiler, not domain codecs.\nexport type OAuthNativeReadMapping = OAuthSignInMapping<\n TableModel,\n TableModel,\n TableModel,\n TableModel,\n TableModel,\n unknown,\n SqlExpression\n>;\n\ntype Row = Readonly<Record<string, unknown>>;\n\nexport const decodeOAuthRequirement = Effect.fnUntraced(function* (\n subject: OAuthSubjectReadTable<TableModel>,\n row: Row,\n): Effect.fn.Return<AuthenticationRequirement, PersistenceMappingError> {\n const requirement = subject.decodeAuthenticationRequirement(row);\n\n return yield* Effect.isEffect(requirement) ? requirement : Effect.succeed(requirement);\n});\n\n/** Unlocked snapshots and explicit subject-first reads share decoding rules.\n * No observation registry or discovered-identity placeholder is retained. */\nexport const makeOAuthNativeAuthority = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: Pick<OAuthNativeReadMapping, \"subject\" | \"authority\" | \"subjectId\" | \"clock\">,\n) {\n const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n const subject = tables(mapping.subject.table);\n const authority = tables(mapping.authority.table);\n const s = subject.as(\"oauth_subject\");\n const a = authority.as(\"oauth_authority\");\n const now = tables.expression(mapping.clock.engineNowMillis);\n\n const lock = sql.onDialectOrElse({\n pg: () => sql`for update`,\n mysql: () => sql`for update`,\n orElse: () => sql``,\n });\n\n const active = (kind: \"subject\" | \"authority\", alias: string) => {\n const descriptor = mapping[kind];\n const physical = tables(descriptor.table);\n\n return sql`(select * from ${physical.name} where ${tables.expression(descriptor.activeCondition)}) as ${sql(alias)}`;\n };\n\n const revision = Effect.fnUntraced(function* (\n subjectRow: Row,\n factors: ReadonlyArray<Row>,\n nativeId: unknown,\n ) {\n invariant(mapping.subject.isActiveStatus(subjectRow[mapping.subject.status]));\n invariant(mapping.subjectId.equals(subjectRow[mapping.subject.id], nativeId));\n invariant(factors.length > 0 && factors.length <= 64);\n invariant(\n factors.every(\n (row) =>\n mapping.subjectId.equals(row[mapping.authority.subjectId], nativeId) &&\n mapping.authority.isActiveStatus(row[mapping.authority.status]),\n ),\n );\n\n const credentials = factors.map((row) => ({\n credentialId: row[mapping.authority.credentialId],\n revision: row[mapping.authority.revision],\n }));\n\n invariant(new Set(credentials.map((item) => item.credentialId)).size === credentials.length);\n\n const value = yield* Schema.decodeUnknownEffect(Schema.toType(OAuthAccountRevision))({\n subjectId: yield* mapping.subjectId.toSubject(nativeId),\n securityRevision: subjectRow[mapping.subject.securityRevision],\n credentials,\n });\n\n return {\n ...value,\n credentials: [...value.credentials].sort((left, right) =>\n left.credentialId.localeCompare(right.credentialId),\n ),\n };\n });\n\n const captureSubject = Effect.fnUntraced(function* (subjectId: SubjectId, locking: boolean) {\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n const rows =\n yield* sql`select ${subject.fields(\"s_\")}, ${now} as engine_now from ${subject.name} where ${subject.column(mapping.subject.id)} = ${subject.value(mapping.subject.id, nativeId)} and ${tables.expression(mapping.subject.activeCondition)} limit 2 ${locking ? lock : sql``}`;\n\n invariant(rows.length <= 1);\n if (rows[0] === undefined) return undefined;\n const row = subject.decode(rows[0], \"s_\");\n\n invariant(mapping.subjectId.equals(row[mapping.subject.id], nativeId));\n\n return {\n nativeId,\n subject: row,\n securityRevision: yield* Schema.decodeUnknownEffect(\n OAuthAccountRevision.fields.securityRevision,\n )(row[mapping.subject.securityRevision]),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(rows[0].engine_now)),\n };\n });\n\n const capture = Effect.fnUntraced(function* (subjectId: SubjectId, locking: boolean) {\n const nativeId = yield* mapping.subjectId.toNative(subjectId);\n\n if (locking) {\n // Never acquire a credential, identity, flow or policy lock before this.\n const subjects =\n yield* sql`select ${subject.fields(\"s_\")}, ${now} as engine_now from ${subject.name}\n where ${subject.column(mapping.subject.id)} = ${subject.value(mapping.subject.id, nativeId)}\n and ${tables.expression(mapping.subject.activeCondition)} ${lock}`;\n\n if (subjects.length === 0) return undefined;\n invariant(subjects.length === 1);\n const subjectRow = subject.decode(subjects[0]!, \"s_\");\n\n const factors = yield* sql`select ${authority.fields(\"a_\")} from ${authority.name}\n where ${authority.column(mapping.authority.subjectId)} = ${authority.value(mapping.authority.subjectId, nativeId)}\n and ${tables.expression(mapping.authority.activeCondition)}\n order by ${authority.column(mapping.authority.credentialId)} limit 65 ${lock}`;\n\n if (factors.length === 0) return undefined;\n\n return {\n nativeId,\n subject: subjectRow,\n revision: yield* revision(\n subjectRow,\n factors.map((row) => authority.decode(row, \"a_\")),\n nativeId,\n ),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(subjects[0]!.engine_now)),\n };\n }\n\n // Bind each column's own native codec; the columns need not share an encoding.\n const rows = yield* sql`select ${s.fields(\"s_\")}, ${a.fields(\"a_\")}, ${now} as engine_now\n from ${active(\"subject\", \"oauth_subject\")}\n join ${active(\"authority\", \"oauth_authority\")} on\n ${a.column(mapping.authority.subjectId)} = ${a.value(mapping.authority.subjectId, nativeId)}\n where ${s.column(mapping.subject.id)} = ${s.value(mapping.subject.id, nativeId)}\n order by ${a.column(mapping.authority.credentialId)} limit 65`;\n\n if (rows.length === 0) return undefined;\n const subjectRow = s.decode(rows[0]!, \"s_\");\n\n return {\n nativeId,\n subject: subjectRow,\n revision: yield* revision(\n subjectRow,\n rows.map((row) => a.decode(row, \"a_\")),\n nativeId,\n ),\n now: yield* Schema.decodeEffect(Schema.Int)(Number(rows[0]!.engine_now)),\n };\n });\n\n return { sql, now, lock, subject, authority, capture, captureSubject, revision };\n});\n\nexport const makeOAuthNativeState = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: OAuthNativeReadMapping,\n) {\n const state = yield* makeOAuthNativeAuthority(tables, mapping);\n const { sql, subject, authority, revision } = state;\n const ownership = tables(mapping.ownership.table);\n const credential = tables(mapping.credential.table);\n const s = subject.as(\"oauth_subject\");\n const o = ownership.as(\"oauth_identity\");\n const c = credential.as(\"oauth_credential\");\n const a = authority.as(\"oauth_authority\");\n\n const exact = (\n left: Parameters<typeof exactSqlText>[1],\n right: Parameters<typeof exactSqlText>[2],\n ) => exactSqlText(sql, left, right);\n\n const active = (kind: \"subject\" | \"credential\" | \"authority\", alias: string) => {\n const descriptor = mapping[kind];\n const physical = tables(descriptor.table);\n\n return sql`(select * from ${physical.name} where ${tables.expression(descriptor.activeCondition)}) as ${sql(alias)}`;\n };\n\n const [joinedIds, joinedIdentityKeys] = yield* Effect.forEach(\n [\n [\n ownership.unencodedTextColumn?.(mapping.ownership.subjectId),\n subject.unencodedTextColumn?.(mapping.subject.id),\n credential.unencodedTextColumn?.(mapping.credential.subjectId),\n authority.unencodedTextColumn?.(mapping.authority.subjectId),\n ],\n [\n credential.unencodedTextColumn?.(mapping.credential.identityKey),\n ownership.unencodedTextColumn?.(mapping.ownership.identityKey),\n ],\n ],\n canJoinTextColumns,\n ).pipe(withStorageValidation, Effect.mapError(unavailable));\n\n const decodeSnapshot = Effect.fnUntraced(function* (\n rows: ReadonlyArray<Row>,\n moduleId: string,\n nativeId?: unknown,\n ) {\n const first = rows[0];\n\n if (first === undefined) return undefined;\n const owned = o.decode(first, \"o_\");\n\n const identity = yield* Schema.decodeUnknownEffect(OAuthExternalIdentity)({\n provider: owned[mapping.ownership.provider],\n issuer: owned[mapping.ownership.issuer],\n subject: owned[mapping.ownership.externalSubject],\n });\n\n const identityKey = yield* oauthIdentityKey(identity);\n\n invariant(owned[mapping.ownership.identityKey] === identityKey);\n const ownerId = mapping.ownership.decodeSubjectId(owned);\n\n if (nativeId !== undefined) invariant(mapping.subjectId.equals(ownerId, nativeId));\n const login = c.decode(first, \"c_\");\n\n invariant(mapping.subjectId.equals(login[mapping.credential.subjectId], ownerId));\n invariant(login[mapping.credential.identityKey] === identityKey);\n invariant(mapping.credential.isActiveStatus(login[mapping.credential.status]));\n\n const current = yield* revision(\n s.decode(first, \"s_\"),\n rows.map((row) => a.decode(row, \"a_\")),\n ownerId,\n );\n\n const credentialId = login[mapping.credential.credentialId];\n const credentialRevision = login[mapping.credential.credentialRevision];\n\n if (\n !current.credentials.some(\n (item) => item.credentialId === credentialId && item.revision === credentialRevision,\n )\n )\n return undefined;\n\n return yield* Schema.decodeUnknownEffect(Schema.toType(OAuthCredentialSnapshot))({\n moduleId,\n identity,\n credentialId,\n credentialRevision,\n revision: current,\n requirement: yield* decodeOAuthRequirement(mapping.subject, s.decode(first, \"s_\")),\n });\n });\n\n const resolve = Effect.fnUntraced(function* (\n moduleId: string,\n identity: typeof OAuthExternalIdentity.Type,\n ) {\n const identityKey = yield* oauthIdentityKey(identity);\n let nativeId: unknown;\n\n if (!joinedIds) {\n const found = yield* sql`select ${ownership.fields(\"o_\")} from ${ownership.name}\n where ${exact(ownership.column(mapping.ownership.identityKey), ownership.value(mapping.ownership.identityKey, identityKey))}`;\n\n if (found.length === 0) return undefined;\n invariant(found.length === 1);\n const row = ownership.decode(found[0]!, \"o_\");\n\n invariant(\n row[mapping.ownership.provider] === identity.provider &&\n row[mapping.ownership.issuer] === identity.issuer &&\n row[mapping.ownership.externalSubject] === identity.subject,\n );\n nativeId = mapping.ownership.decodeSubjectId(row);\n }\n\n const bindOwner = (table: typeof s, key: string) =>\n joinedIds\n ? exact(table.column(key), o.column(mapping.ownership.subjectId))\n : sql`${table.column(key)} = ${table.value(key, nativeId)}`;\n\n const rows =\n yield* sql`select ${o.fields(\"o_\")}, ${s.fields(\"s_\")}, ${c.fields(\"c_\")}, ${a.fields(\"a_\")}\n from ${o.name}\n join ${active(\"subject\", \"oauth_subject\")} on ${bindOwner(s, mapping.subject.id)}\n join ${active(\"credential\", \"oauth_credential\")} on ${bindOwner(c, mapping.credential.subjectId)}\n and ${exact(c.column(mapping.credential.moduleId), c.value(mapping.credential.moduleId, moduleId))}\n and ${exact(c.column(mapping.credential.identityKey), c.value(mapping.credential.identityKey, identityKey))}\n join ${active(\"authority\", \"oauth_authority\")} on ${bindOwner(a, mapping.authority.subjectId)}\n where ${exact(o.column(mapping.ownership.identityKey), o.value(mapping.ownership.identityKey, identityKey))}\n order by ${a.column(mapping.authority.credentialId)} limit 65`;\n\n const result = yield* decodeSnapshot(rows, moduleId, joinedIds ? undefined : nativeId);\n\n if (result !== undefined)\n invariant(\n result.identity.provider === identity.provider &&\n result.identity.issuer === identity.issuer &&\n result.identity.subject === identity.subject,\n );\n\n return result;\n });\n\n const readCredential = Effect.fnUntraced(function* (\n input: typeof OAuthCredentialKey.Type,\n metadataAccess: Fragment = sql`true`,\n ) {\n const nativeId = yield* mapping.subjectId.toNative(input.subjectId);\n let identityKey: unknown;\n\n if (!joinedIdentityKeys) {\n const selected = yield* sql`select ${credential.fields(\"c_\")} from ${credential.name}\n where ${exact(credential.column(mapping.credential.moduleId), credential.value(mapping.credential.moduleId, input.moduleId))}\n and ${exact(credential.column(mapping.credential.credentialId), credential.value(mapping.credential.credentialId, input.credentialId))}\n and ${credential.column(mapping.credential.subjectId)} = ${credential.value(mapping.credential.subjectId, nativeId)}`;\n\n if (selected.length === 0) return undefined;\n invariant(selected.length === 1);\n identityKey = credential.decode(selected[0]!, \"c_\")[mapping.credential.identityKey];\n }\n\n const owner = (table: typeof s, key: string) =>\n sql`${table.column(key)} = ${table.value(key, nativeId)}`;\n\n const rows =\n yield* sql`select ${o.fields(\"o_\")}, ${s.fields(\"s_\")}, ${c.fields(\"c_\")}, ${a.fields(\"a_\")}\n from ${active(\"credential\", \"oauth_credential\")}\n join ${o.name} on ${joinedIdentityKeys ? exact(o.column(mapping.ownership.identityKey), c.column(mapping.credential.identityKey)) : exact(o.column(mapping.ownership.identityKey), o.value(mapping.ownership.identityKey, identityKey))}\n and ${owner(o, mapping.ownership.subjectId)}\n join ${active(\"subject\", \"oauth_subject\")} on ${owner(s, mapping.subject.id)}\n join ${active(\"authority\", \"oauth_authority\")} on ${owner(a, mapping.authority.subjectId)}\n where ${metadataAccess} and ${owner(c, mapping.credential.subjectId)}\n and ${exact(c.column(mapping.credential.moduleId), c.value(mapping.credential.moduleId, input.moduleId))}\n and ${exact(c.column(mapping.credential.credentialId), c.value(mapping.credential.credentialId, input.credentialId))}\n order by ${a.column(mapping.authority.credentialId)} limit 65`;\n\n return yield* decodeSnapshot(rows, input.moduleId, nativeId);\n });\n\n return { ...state, ownership, credential, resolve, readCredential };\n});\n"],"mappings":";;;;;;;AAiCA,MAAa,yBAAyB,OAAO,WAAW,WACtD,SACA,KACsE;CACtE,MAAM,cAAc,QAAQ,gCAAgC,GAAG;CAE/D,OAAO,OAAO,OAAO,SAAS,WAAW,IAAI,cAAc,OAAO,QAAQ,WAAW;AACvF,CAAC;;;AAID,MAAa,2BAA2B,OAAO,WAAW,WACxD,QACA,SACA;CACA,MAAM,OAAO,OAAO,UAAU,UAAA,CAAW,kBAAkB;CAC3D,MAAM,UAAU,OAAO,QAAQ,QAAQ,KAAK;CAC5C,MAAM,YAAY,OAAO,QAAQ,UAAU,KAAK;CAChD,MAAM,IAAI,QAAQ,GAAG,eAAe;CACpC,MAAM,IAAI,UAAU,GAAG,iBAAiB;CACxC,MAAM,MAAM,OAAO,WAAW,QAAQ,MAAM,eAAe;CAE3D,MAAM,OAAO,IAAI,gBAAgB;EAC/B,UAAU,GAAG;EACb,aAAa,GAAG;EAChB,cAAc,GAAG;CACnB,CAAC;CAED,MAAM,UAAU,MAA+B,UAAkB;EAC/D,MAAM,aAAa,QAAQ;EAC3B,MAAM,WAAW,OAAO,WAAW,KAAK;EAExC,OAAO,GAAG,kBAAkB,SAAS,KAAK,SAAS,OAAO,WAAW,WAAW,eAAe,EAAE,OAAO,IAAI,KAAK;CACnH;CAEA,MAAM,WAAW,OAAO,WAAW,WACjC,YACA,SACA,UACA;EACA,UAAU,QAAQ,QAAQ,eAAe,WAAW,QAAQ,QAAQ,OAAO,CAAC;EAC5E,UAAU,QAAQ,UAAU,OAAO,WAAW,QAAQ,QAAQ,KAAK,QAAQ,CAAC;EAC5E,UAAU,QAAQ,SAAS,KAAK,QAAQ,UAAU,EAAE;EACpD,UACE,QAAQ,OACL,QACC,QAAQ,UAAU,OAAO,IAAI,QAAQ,UAAU,YAAY,QAAQ,KACnE,QAAQ,UAAU,eAAe,IAAI,QAAQ,UAAU,OAAO,CAClE,CACF;EAEA,MAAM,cAAc,QAAQ,KAAK,SAAS;GACxC,cAAc,IAAI,QAAQ,UAAU;GACpC,UAAU,IAAI,QAAQ,UAAU;EAClC,EAAE;EAEF,UAAU,IAAI,IAAI,YAAY,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,YAAY,MAAM;EAE3F,MAAM,QAAQ,OAAO,OAAO,oBAAoB,OAAO,OAAO,oBAAoB,CAAC,CAAC,CAAC;GACnF,WAAW,OAAO,QAAQ,UAAU,UAAU,QAAQ;GACtD,kBAAkB,WAAW,QAAQ,QAAQ;GAC7C;EACF,CAAC;EAED,OAAO;GACL,GAAG;GACH,aAAa,CAAC,GAAG,MAAM,WAAW,CAAC,CAAC,MAAM,MAAM,UAC9C,KAAK,aAAa,cAAc,MAAM,YAAY,CACpD;EACF;CACF,CAAC;CAED,MAAM,iBAAiB,OAAO,WAAW,WAAW,WAAsB,SAAkB;EAC1F,MAAM,WAAW,OAAO,QAAQ,UAAU,SAAS,SAAS;EAE5D,MAAM,OACJ,OAAO,GAAG,UAAU,QAAQ,OAAO,IAAI,EAAE,IAAI,IAAI,sBAAsB,QAAQ,KAAK,SAAS,QAAQ,OAAO,QAAQ,QAAQ,EAAE,EAAE,KAAK,QAAQ,MAAM,QAAQ,QAAQ,IAAI,QAAQ,EAAE,OAAO,OAAO,WAAW,QAAQ,QAAQ,eAAe,EAAE,WAAW,UAAU,OAAO,GAAG;EAE5Q,UAAU,KAAK,UAAU,CAAC;EAC1B,IAAI,KAAK,OAAO,KAAA,GAAW,OAAO,KAAA;EAClC,MAAM,MAAM,QAAQ,OAAO,KAAK,IAAI,IAAI;EAExC,UAAU,QAAQ,UAAU,OAAO,IAAI,QAAQ,QAAQ,KAAK,QAAQ,CAAC;EAErE,OAAO;GACL;GACA,SAAS;GACT,kBAAkB,OAAO,OAAO,oBAC9B,qBAAqB,OAAO,gBAC9B,CAAC,CAAC,IAAI,QAAQ,QAAQ,iBAAiB;GACvC,KAAK,OAAO,OAAO,aAAa,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,UAAU,CAAC;EACxE;CACF,CAAC;CA0DD,OAAO;EAAE;EAAK;EAAK;EAAM;EAAS;EAAW,SAxD7B,OAAO,WAAW,WAAW,WAAsB,SAAkB;GACnF,MAAM,WAAW,OAAO,QAAQ,UAAU,SAAS,SAAS;GAE5D,IAAI,SAAS;IAEX,MAAM,WACJ,OAAO,GAAG,UAAU,QAAQ,OAAO,IAAI,EAAE,IAAI,IAAI,sBAAsB,QAAQ,KAAK;gBAC5E,QAAQ,OAAO,QAAQ,QAAQ,EAAE,EAAE,KAAK,QAAQ,MAAM,QAAQ,QAAQ,IAAI,QAAQ,EAAE;gBACpF,OAAO,WAAW,QAAQ,QAAQ,eAAe,EAAE,GAAG;IAEhE,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;IAClC,UAAU,SAAS,WAAW,CAAC;IAC/B,MAAM,aAAa,QAAQ,OAAO,SAAS,IAAK,IAAI;IAEpD,MAAM,UAAU,OAAO,GAAG,UAAU,UAAU,OAAO,IAAI,EAAE,QAAQ,UAAU,KAAK;gBACxE,UAAU,OAAO,QAAQ,UAAU,SAAS,EAAE,KAAK,UAAU,MAAM,QAAQ,UAAU,WAAW,QAAQ,EAAE;gBAC1G,OAAO,WAAW,QAAQ,UAAU,eAAe,EAAE;mBAClD,UAAU,OAAO,QAAQ,UAAU,YAAY,EAAE,YAAY;IAE1E,IAAI,QAAQ,WAAW,GAAG,OAAO,KAAA;IAEjC,OAAO;KACL;KACA,SAAS;KACT,UAAU,OAAO,SACf,YACA,QAAQ,KAAK,QAAQ,UAAU,OAAO,KAAK,IAAI,CAAC,GAChD,QACF;KACA,KAAK,OAAO,OAAO,aAAa,OAAO,GAAG,CAAC,CAAC,OAAO,SAAS,EAAE,CAAE,UAAU,CAAC;IAC7E;GACF;GAGA,MAAM,OAAO,OAAO,GAAG,UAAU,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE,IAAI,IAAI;aAClE,OAAO,WAAW,eAAe,EAAE;aACnC,OAAO,aAAa,iBAAiB,EAAE;UAC1C,EAAE,OAAO,QAAQ,UAAU,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,UAAU,WAAW,QAAQ,EAAE;cACtF,EAAE,OAAO,QAAQ,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,QAAQ,QAAQ,IAAI,QAAQ,EAAE;iBACrE,EAAE,OAAO,QAAQ,UAAU,YAAY,EAAE;GAEtD,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;GAC9B,MAAM,aAAa,EAAE,OAAO,KAAK,IAAK,IAAI;GAE1C,OAAO;IACL;IACA,SAAS;IACT,UAAU,OAAO,SACf,YACA,KAAK,KAAK,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC,GACrC,QACF;IACA,KAAK,OAAO,OAAO,aAAa,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,EAAE,CAAE,UAAU,CAAC;GACzE;EACF,CAEmD;EAAG;EAAgB;CAAS;AACjF,CAAC;AAED,MAAa,uBAAuB,OAAO,WAAW,WACpD,QACA,SACA;CACA,MAAM,QAAQ,OAAO,yBAAyB,QAAQ,OAAO;CAC7D,MAAM,EAAE,KAAK,SAAS,WAAW,aAAa;CAC9C,MAAM,YAAY,OAAO,QAAQ,UAAU,KAAK;CAChD,MAAM,aAAa,OAAO,QAAQ,WAAW,KAAK;CAClD,MAAM,IAAI,QAAQ,GAAG,eAAe;CACpC,MAAM,IAAI,UAAU,GAAG,gBAAgB;CACvC,MAAM,IAAI,WAAW,GAAG,kBAAkB;CAC1C,MAAM,IAAI,UAAU,GAAG,iBAAiB;CAExC,MAAM,SACJ,MACA,UACG,aAAa,KAAK,MAAM,KAAK;CAElC,MAAM,UAAU,MAA8C,UAAkB;EAC9E,MAAM,aAAa,QAAQ;EAC3B,MAAM,WAAW,OAAO,WAAW,KAAK;EAExC,OAAO,GAAG,kBAAkB,SAAS,KAAK,SAAS,OAAO,WAAW,WAAW,eAAe,EAAE,OAAO,IAAI,KAAK;CACnH;CAEA,MAAM,CAAC,WAAW,sBAAsB,OAAO,OAAO,QACpD,CACE;EACE,UAAU,sBAAsB,QAAQ,UAAU,SAAS;EAC3D,QAAQ,sBAAsB,QAAQ,QAAQ,EAAE;EAChD,WAAW,sBAAsB,QAAQ,WAAW,SAAS;EAC7D,UAAU,sBAAsB,QAAQ,UAAU,SAAS;CAC7D,GACA,CACE,WAAW,sBAAsB,QAAQ,WAAW,WAAW,GAC/D,UAAU,sBAAsB,QAAQ,UAAU,WAAW,CAC/D,CACF,GACA,kBACF,CAAC,CAAC,KAAK,uBAAuB,OAAO,SAAS,WAAW,CAAC;CAE1D,MAAM,iBAAiB,OAAO,WAAW,WACvC,MACA,UACA,UACA;EACA,MAAM,QAAQ,KAAK;EAEnB,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;EAChC,MAAM,QAAQ,EAAE,OAAO,OAAO,IAAI;EAElC,MAAM,WAAW,OAAO,OAAO,oBAAoB,qBAAqB,CAAC,CAAC;GACxE,UAAU,MAAM,QAAQ,UAAU;GAClC,QAAQ,MAAM,QAAQ,UAAU;GAChC,SAAS,MAAM,QAAQ,UAAU;EACnC,CAAC;EAED,MAAM,cAAc,OAAO,iBAAiB,QAAQ;EAEpD,UAAU,MAAM,QAAQ,UAAU,iBAAiB,WAAW;EAC9D,MAAM,UAAU,QAAQ,UAAU,gBAAgB,KAAK;EAEvD,IAAI,aAAa,KAAA,GAAW,UAAU,QAAQ,UAAU,OAAO,SAAS,QAAQ,CAAC;EACjF,MAAM,QAAQ,EAAE,OAAO,OAAO,IAAI;EAElC,UAAU,QAAQ,UAAU,OAAO,MAAM,QAAQ,WAAW,YAAY,OAAO,CAAC;EAChF,UAAU,MAAM,QAAQ,WAAW,iBAAiB,WAAW;EAC/D,UAAU,QAAQ,WAAW,eAAe,MAAM,QAAQ,WAAW,OAAO,CAAC;EAE7E,MAAM,UAAU,OAAO,SACrB,EAAE,OAAO,OAAO,IAAI,GACpB,KAAK,KAAK,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC,GACrC,OACF;EAEA,MAAM,eAAe,MAAM,QAAQ,WAAW;EAC9C,MAAM,qBAAqB,MAAM,QAAQ,WAAW;EAEpD,IACE,CAAC,QAAQ,YAAY,MAClB,SAAS,KAAK,iBAAiB,gBAAgB,KAAK,aAAa,kBACpE,GAEA,OAAO,KAAA;EAET,OAAO,OAAO,OAAO,oBAAoB,OAAO,OAAO,uBAAuB,CAAC,CAAC,CAAC;GAC/E;GACA;GACA;GACA;GACA,UAAU;GACV,aAAa,OAAO,uBAAuB,QAAQ,SAAS,EAAE,OAAO,OAAO,IAAI,CAAC;EACnF,CAAC;CACH,CAAC;CAED,MAAM,UAAU,OAAO,WAAW,WAChC,UACA,UACA;EACA,MAAM,cAAc,OAAO,iBAAiB,QAAQ;EACpD,IAAI;EAEJ,IAAI,CAAC,WAAW;GACd,MAAM,QAAQ,OAAO,GAAG,UAAU,UAAU,OAAO,IAAI,EAAE,QAAQ,UAAU,KAAK;gBACtE,MAAM,UAAU,OAAO,QAAQ,UAAU,WAAW,GAAG,UAAU,MAAM,QAAQ,UAAU,aAAa,WAAW,CAAC;GAE5H,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;GAC/B,UAAU,MAAM,WAAW,CAAC;GAC5B,MAAM,MAAM,UAAU,OAAO,MAAM,IAAK,IAAI;GAE5C,UACE,IAAI,QAAQ,UAAU,cAAc,SAAS,YAC3C,IAAI,QAAQ,UAAU,YAAY,SAAS,UAC3C,IAAI,QAAQ,UAAU,qBAAqB,SAAS,OACxD;GACA,WAAW,QAAQ,UAAU,gBAAgB,GAAG;EAClD;EAEA,MAAM,aAAa,OAAiB,QAClC,YACI,MAAM,MAAM,OAAO,GAAG,GAAG,EAAE,OAAO,QAAQ,UAAU,SAAS,CAAC,IAC9D,GAAG,GAAG,MAAM,OAAO,GAAG,EAAE,KAAK,MAAM,MAAM,KAAK,QAAQ;EAE5D,MAAM,OACJ,OAAO,GAAG,UAAU,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE;aACrF,EAAE,KAAK;aACP,OAAO,WAAW,eAAe,EAAE,MAAM,UAAU,GAAG,QAAQ,QAAQ,EAAE,EAAE;aAC1E,OAAO,cAAc,kBAAkB,EAAE,MAAM,UAAU,GAAG,QAAQ,WAAW,SAAS,EAAE;cACzF,MAAM,EAAE,OAAO,QAAQ,WAAW,QAAQ,GAAG,EAAE,MAAM,QAAQ,WAAW,UAAU,QAAQ,CAAC,EAAE;cAC7F,MAAM,EAAE,OAAO,QAAQ,WAAW,WAAW,GAAG,EAAE,MAAM,QAAQ,WAAW,aAAa,WAAW,CAAC,EAAE;aACvG,OAAO,aAAa,iBAAiB,EAAE,MAAM,UAAU,GAAG,QAAQ,UAAU,SAAS,EAAE;cACtF,MAAM,EAAE,OAAO,QAAQ,UAAU,WAAW,GAAG,EAAE,MAAM,QAAQ,UAAU,aAAa,WAAW,CAAC,EAAE;iBACjG,EAAE,OAAO,QAAQ,UAAU,YAAY,EAAE;EAEtD,MAAM,SAAS,OAAO,eAAe,MAAM,UAAU,YAAY,KAAA,IAAY,QAAQ;EAErF,IAAI,WAAW,KAAA,GACb,UACE,OAAO,SAAS,aAAa,SAAS,YACpC,OAAO,SAAS,WAAW,SAAS,UACpC,OAAO,SAAS,YAAY,SAAS,OACzC;EAEF,OAAO;CACT,CAAC;CAED,MAAM,iBAAiB,OAAO,WAAW,WACvC,OACA,iBAA2B,GAAG,QAC9B;EACA,MAAM,WAAW,OAAO,QAAQ,UAAU,SAAS,MAAM,SAAS;EAClE,IAAI;EAEJ,IAAI,CAAC,oBAAoB;GACvB,MAAM,WAAW,OAAO,GAAG,UAAU,WAAW,OAAO,IAAI,EAAE,QAAQ,WAAW,KAAK;gBAC3E,MAAM,WAAW,OAAO,QAAQ,WAAW,QAAQ,GAAG,WAAW,MAAM,QAAQ,WAAW,UAAU,MAAM,QAAQ,CAAC,EAAE;gBACrH,MAAM,WAAW,OAAO,QAAQ,WAAW,YAAY,GAAG,WAAW,MAAM,QAAQ,WAAW,cAAc,MAAM,YAAY,CAAC,EAAE;gBACjI,WAAW,OAAO,QAAQ,WAAW,SAAS,EAAE,KAAK,WAAW,MAAM,QAAQ,WAAW,WAAW,QAAQ;GAEtH,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;GAClC,UAAU,SAAS,WAAW,CAAC;GAC/B,cAAc,WAAW,OAAO,SAAS,IAAK,IAAI,CAAC,CAAC,QAAQ,WAAW;EACzE;EAEA,MAAM,SAAS,OAAiB,QAC9B,GAAG,GAAG,MAAM,OAAO,GAAG,EAAE,KAAK,MAAM,MAAM,KAAK,QAAQ;EAExD,MAAM,OACJ,OAAO,GAAG,UAAU,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,EAAE;aACrF,OAAO,cAAc,kBAAkB,EAAE;aACzC,EAAE,KAAK,MAAM,qBAAqB,MAAM,EAAE,OAAO,QAAQ,UAAU,WAAW,GAAG,EAAE,OAAO,QAAQ,WAAW,WAAW,CAAC,IAAI,MAAM,EAAE,OAAO,QAAQ,UAAU,WAAW,GAAG,EAAE,MAAM,QAAQ,UAAU,aAAa,WAAW,CAAC,EAAE;cAChO,MAAM,GAAG,QAAQ,UAAU,SAAS,EAAE;aACvC,OAAO,WAAW,eAAe,EAAE,MAAM,MAAM,GAAG,QAAQ,QAAQ,EAAE,EAAE;aACtE,OAAO,aAAa,iBAAiB,EAAE,MAAM,MAAM,GAAG,QAAQ,UAAU,SAAS,EAAE;cAClF,eAAe,OAAO,MAAM,GAAG,QAAQ,WAAW,SAAS,EAAE;cAC7D,MAAM,EAAE,OAAO,QAAQ,WAAW,QAAQ,GAAG,EAAE,MAAM,QAAQ,WAAW,UAAU,MAAM,QAAQ,CAAC,EAAE;cACnG,MAAM,EAAE,OAAO,QAAQ,WAAW,YAAY,GAAG,EAAE,MAAM,QAAQ,WAAW,cAAc,MAAM,YAAY,CAAC,EAAE;iBAC5G,EAAE,OAAO,QAAQ,UAAU,YAAY,EAAE;EAEtD,OAAO,OAAO,eAAe,MAAM,MAAM,UAAU,QAAQ;CAC7D,CAAC;CAED,OAAO;EAAE,GAAG;EAAO;EAAW;EAAY;EAAS;CAAe;AACpE,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../crypto.mjs";
|
|
2
2
|
import { Crypto, Effect, Schema } from "effect";
|
|
3
|
-
import { AuthenticationRequirement } from "@yielded/auth/Sessions";
|
|
4
3
|
import { OAuthAccountRevision, OAuthExternalIdentity, OAuthUnavailable } from "@yielded/auth/OAuth";
|
|
4
|
+
import { AuthenticationRequirement } from "@yielded/auth/Sessions";
|
|
5
5
|
//#region src/internal/oauth/state.d.ts
|
|
6
6
|
/** Capture configuration/callback references while preserving adapter table, SQL
|
|
7
7
|
* and Effect objects. Never freeze or mutate the consumer's original graph. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../crypto.mjs";
|
|
2
2
|
import { Crypto, Effect, Schema } from "effect";
|
|
3
|
-
import { Base64Url } from "effect/encoding";
|
|
4
3
|
import { OAuthExternalIdentity, OAuthUnavailable, snapshotOAuthSync } from "@yielded/auth/OAuth";
|
|
4
|
+
import { Base64Url } from "effect/encoding";
|
|
5
5
|
//#region src/internal/oauth/state.ts
|
|
6
6
|
const encoder = new TextEncoder();
|
|
7
7
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { PersistenceConfigurationError } from "./configuration.mjs";
|
|
2
|
+
import { exactSqlText } from "./sql-change.mjs";
|
|
3
|
+
import { SqlBatchCommit } from "./sql-commit.mjs";
|
|
4
|
+
import { requiredOAuthConnectedConstraints, requiredOAuthConnectedRevocationConstraints } from "./models/oauth-connected-model.mjs";
|
|
5
|
+
import { requiredOAuthSignInConstraints } from "./models/oauth-model.mjs";
|
|
6
|
+
import { sameRevision, satisfies } from "./oauth/state.mjs";
|
|
7
|
+
import { makeNativeOAuthSignInServices } from "./oauth/native-sign-in.mjs";
|
|
8
|
+
import { makeNativeOAuthConnectedServices } from "./oauth/native-connected.mjs";
|
|
9
|
+
import { makeNativeOAuthRevocationServices } from "./oauth/native-revocations.mjs";
|
|
10
|
+
import { makeStorageClock } from "./storage-clock.mjs";
|
|
11
|
+
import { storageTables } from "./storage-tables.mjs";
|
|
12
|
+
import { Context, DateTime, Effect, Schema } from "effect";
|
|
13
|
+
import { SqlClient } from "effect/sql";
|
|
14
|
+
import * as M from "@yielded/auth/OAuth";
|
|
15
|
+
import { AuthenticationFlowId } from "@yielded/auth/Sessions";
|
|
16
|
+
//#region src/internal/oauth-managed.ts
|
|
17
|
+
const revocationRetentionMillis = 2592e6;
|
|
18
|
+
const encodeProfile = Schema.encodeSync(Schema.fromJsonString(M.OAuthConnectedProfile));
|
|
19
|
+
const encodePolicy = Schema.encodeSync(Schema.fromJsonString(M.OAuthConnectedPolicy));
|
|
20
|
+
const configurationError = (reason) => PersistenceConfigurationError.make({ reason });
|
|
21
|
+
/** Managed tables use the same native OAuth operations as explicit mappings. */
|
|
22
|
+
const makeManagedOAuth = Effect.fnUntraced(function* (storage, tables, features) {
|
|
23
|
+
const sql = (yield* SqlClient.SqlClient).withoutTransforms();
|
|
24
|
+
const batch = yield* SqlBatchCommit;
|
|
25
|
+
const clock = yield* makeStorageClock(storage).pipe(Effect.mapError(() => configurationError("Cannot configure the OAuth storage clock")));
|
|
26
|
+
function mapped(role) {
|
|
27
|
+
const table = storage.tables[role];
|
|
28
|
+
if (table === void 0) throw configurationError(`Missing ${role} mapping`);
|
|
29
|
+
return {
|
|
30
|
+
table,
|
|
31
|
+
...Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key]))
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const s = storage.subjects;
|
|
35
|
+
const subject = tables(s.table);
|
|
36
|
+
const credential = mapped("oauthCredentials");
|
|
37
|
+
const credentials = tables(credential.table);
|
|
38
|
+
const authority = mapped("credentials");
|
|
39
|
+
const factors = tables(authority.table);
|
|
40
|
+
const now = tables.expression(clock.engineNowMillis);
|
|
41
|
+
const policies = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const feature of features) {
|
|
43
|
+
if (feature.connected === void 0) continue;
|
|
44
|
+
const policy = yield* M.snapshotOAuth(M.OAuthConnectedPolicy, feature.connected).pipe(Effect.mapError(() => configurationError(`Invalid OAuth policy for ${feature.moduleId}`)));
|
|
45
|
+
const previous = policies.get(feature.moduleId);
|
|
46
|
+
if (previous !== void 0 && encodePolicy(previous) !== encodePolicy(policy)) return yield* configurationError(`Conflicting OAuth policies for ${feature.moduleId}`);
|
|
47
|
+
policies.set(feature.moduleId, policy);
|
|
48
|
+
}
|
|
49
|
+
const shared = {
|
|
50
|
+
subject: {
|
|
51
|
+
table: s.table,
|
|
52
|
+
id: s.id,
|
|
53
|
+
status: s.status,
|
|
54
|
+
securityRevision: s.securityRevision,
|
|
55
|
+
isActiveStatus: (value) => Object.is(value, s.activeValue),
|
|
56
|
+
activeCondition: sql`${subject.column(s.status)} = ${subject.value(s.status, s.activeValue)}`,
|
|
57
|
+
decodeAuthenticationRequirement: s.requirements
|
|
58
|
+
},
|
|
59
|
+
subjectId: {
|
|
60
|
+
toSubject: s.toSubject,
|
|
61
|
+
toNative: s.toNative,
|
|
62
|
+
equals: Object.is
|
|
63
|
+
},
|
|
64
|
+
ownership: {
|
|
65
|
+
...mapped("oauthIdentities"),
|
|
66
|
+
decodeSubjectId: (row) => row.subjectId,
|
|
67
|
+
encodeInsert: () => ({})
|
|
68
|
+
},
|
|
69
|
+
credential: {
|
|
70
|
+
...credential,
|
|
71
|
+
status: "active",
|
|
72
|
+
isActiveStatus: (value) => value === true,
|
|
73
|
+
activeCondition: sql`${credentials.column("active")} = ${credentials.value("active", true)}`
|
|
74
|
+
},
|
|
75
|
+
authority: {
|
|
76
|
+
...authority,
|
|
77
|
+
status: "active",
|
|
78
|
+
isActiveStatus: (value) => value === true,
|
|
79
|
+
activeCondition: sql`${factors.column("active")} = ${factors.value("active", true)}`
|
|
80
|
+
},
|
|
81
|
+
clock
|
|
82
|
+
};
|
|
83
|
+
function policyCondition(input) {
|
|
84
|
+
let moduleId;
|
|
85
|
+
switch (input.kind) {
|
|
86
|
+
case "sign-in":
|
|
87
|
+
moduleId = input.credential.moduleId;
|
|
88
|
+
break;
|
|
89
|
+
case "action":
|
|
90
|
+
moduleId = input.authorization.challenge.moduleId;
|
|
91
|
+
break;
|
|
92
|
+
case "metadata":
|
|
93
|
+
case "use": moduleId = input.authorization.moduleId;
|
|
94
|
+
}
|
|
95
|
+
const policy = policies.get(moduleId);
|
|
96
|
+
if (policy === void 0) return sql`false`;
|
|
97
|
+
const grant = input.grant;
|
|
98
|
+
if (grant !== void 0) {
|
|
99
|
+
if (grant.moduleId !== moduleId || grant.subjectId !== input.revision.subjectId) return sql`false`;
|
|
100
|
+
if (input.kind !== "action" || input.operation !== "disconnect") {
|
|
101
|
+
const profile = policy.profiles.find((value) => value.key === grant.configuration.profile.key && value.issuance === "active");
|
|
102
|
+
if (profile === void 0 || encodeProfile(profile) !== encodeProfile({
|
|
103
|
+
...grant.configuration.profile,
|
|
104
|
+
issuance: "active"
|
|
105
|
+
})) return sql`false`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const current = sql`exists(select 1 from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, input.subjectId)} and ${shared.subject.activeCondition} and ${exactSqlText(sql, subject.column(s.securityRevision), subject.value(s.securityRevision, input.revision.securityRevision))})`;
|
|
109
|
+
if (input.kind === "sign-in") return features.some((feature) => feature.moduleId === moduleId && feature.signIn) ? current : sql`false`;
|
|
110
|
+
if (input.kind !== "action") return input.authorization.policyRevision === input.revision.securityRevision ? current : sql`false`;
|
|
111
|
+
const { authorization } = input;
|
|
112
|
+
const { challenge, evidence, requirement } = authorization;
|
|
113
|
+
const action = input.operation === "settle" ? "connected-complete" : "connected-disconnect";
|
|
114
|
+
if (challenge.action !== action || !sameRevision(challenge.revision, input.revision) || !sameRevision(evidence.revision, input.revision) || evidence.flowId !== AuthenticationFlowId.make(challenge.flowId) || evidence.bindingDigest !== challenge.bindingDigest || !satisfies(evidence.proofs, requirement) || evidence.proofs.some((proof) => !input.revision.credentials.some((entry) => entry.credentialId === proof.credentialId))) return sql`false`;
|
|
115
|
+
const maximumAge = Math.min(requirement.maximumAgeMillis, policy.maximumEvidenceAgeMillis);
|
|
116
|
+
const verified = evidence.proofs.map((proof) => DateTime.toEpochMillis(proof.verifiedAt));
|
|
117
|
+
if (authorization.source._tag === "Session") verified.push(DateTime.toEpochMillis(authorization.source.authenticatedAt));
|
|
118
|
+
return sql.and([
|
|
119
|
+
current,
|
|
120
|
+
sql`${now} < ${authorization.validUntilMillis}`,
|
|
121
|
+
...verified.map((instant) => sql`${now} >= ${instant} and ${now} < ${instant + maximumAge}`)
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
let context = Context.empty();
|
|
125
|
+
if (features.some((feature) => feature.signIn)) {
|
|
126
|
+
const mapping = {
|
|
127
|
+
...shared,
|
|
128
|
+
flow: {
|
|
129
|
+
...mapped("oauthSignInFlows"),
|
|
130
|
+
encodeInsert: () => ({})
|
|
131
|
+
},
|
|
132
|
+
constraints: requiredOAuthSignInConstraints
|
|
133
|
+
};
|
|
134
|
+
const services = yield* makeNativeOAuthSignInServices(tables, mapping, batch).pipe(Effect.mapError(() => configurationError("Cannot configure OAuth sign-in persistence")));
|
|
135
|
+
context = Context.add(context, M.OAuthSignInPersistence, services.oauthSignInPersistence);
|
|
136
|
+
}
|
|
137
|
+
if (policies.size > 0) {
|
|
138
|
+
const job = {
|
|
139
|
+
...mapped("oauthConnectedRevocations"),
|
|
140
|
+
encodeInsert: () => ({})
|
|
141
|
+
};
|
|
142
|
+
const grant = {
|
|
143
|
+
...mapped("oauthConnectedGrants"),
|
|
144
|
+
encodeInsert: () => ({})
|
|
145
|
+
};
|
|
146
|
+
const mapping = {
|
|
147
|
+
...shared,
|
|
148
|
+
flow: {
|
|
149
|
+
...mapped("oauthConnectedFlows"),
|
|
150
|
+
encodeInsert: () => ({})
|
|
151
|
+
},
|
|
152
|
+
grant,
|
|
153
|
+
policy: { condition: policyCondition },
|
|
154
|
+
constraints: requiredOAuthConnectedConstraints,
|
|
155
|
+
revocation: {
|
|
156
|
+
mode: "provider",
|
|
157
|
+
job,
|
|
158
|
+
retentionMillis: revocationRetentionMillis,
|
|
159
|
+
constraints: requiredOAuthConnectedRevocationConstraints
|
|
160
|
+
},
|
|
161
|
+
otherReferences: ({ identityKey, subjectId }) => sql`exists(select 1 from ${credentials.name} where ${credentials.column("subjectId")} = ${credentials.value("subjectId", subjectId)} and ${exactSqlText(sql, credentials.column("identityKey"), credentials.value("identityKey", identityKey))})`
|
|
162
|
+
};
|
|
163
|
+
const connected = yield* makeNativeOAuthConnectedServices(tables, mapping, batch).pipe(Effect.mapError(() => configurationError("Cannot configure OAuth connected persistence")));
|
|
164
|
+
const revocations = yield* makeNativeOAuthRevocationServices(tables, {
|
|
165
|
+
...mapping,
|
|
166
|
+
job,
|
|
167
|
+
constraints: requiredOAuthConnectedRevocationConstraints
|
|
168
|
+
}, batch).pipe(Effect.mapError(() => configurationError("Cannot configure OAuth revocation persistence")));
|
|
169
|
+
context = context.pipe(Context.add(M.OAuthConnectedPersistence, {
|
|
170
|
+
...connected.oauthConnectedPersistence,
|
|
171
|
+
read: (input) => connected.oauthConnectedPersistence.read(input).pipe(Effect.map((snapshot) => snapshot === void 0 ? void 0 : {
|
|
172
|
+
...snapshot,
|
|
173
|
+
policyRevision: snapshot.revision.securityRevision
|
|
174
|
+
}))
|
|
175
|
+
}), Context.add(M.OAuthConnectedRevocations, revocations.oauthConnectedRevocations));
|
|
176
|
+
}
|
|
177
|
+
return context;
|
|
178
|
+
});
|
|
179
|
+
//#endregion
|
|
180
|
+
export { makeManagedOAuth };
|
|
181
|
+
|
|
182
|
+
//# sourceMappingURL=oauth-managed.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-managed.mjs","names":[],"sources":["../../src/internal/oauth-managed.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport * as M from \"@yielded/auth/OAuth\";\nimport { AuthenticationFlowId } from \"@yielded/auth/Sessions\";\nimport { Context, type Crypto, DateTime, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport {\n PersistenceConfigurationError,\n type MappingInput,\n type OAuthFeature,\n} from \"./configuration\";\nimport {\n requiredOAuthConnectedConstraints,\n requiredOAuthConnectedRevocationConstraints,\n type OAuthConnectedPolicyInput,\n} from \"./models/oauth-connected-model\";\nimport { requiredOAuthSignInConstraints } from \"./models/oauth-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeNativeOAuthConnectedServices } from \"./oauth/native-connected\";\nimport type { OAuthNativeConnectedMapping } from \"./oauth/native-connected-state\";\nimport { makeNativeOAuthRevocationServices } from \"./oauth/native-revocations\";\nimport { makeNativeOAuthSignInServices } from \"./oauth/native-sign-in\";\nimport type { OAuthNativeReadMapping } from \"./oauth/native-state\";\nimport { sameRevision, satisfies } from \"./oauth/state\";\nimport { exactSqlText } from \"./sql-change\";\nimport { SqlBatchCommit } from \"./sql-commit\";\nimport { makeStorageClock } from \"./storage-clock\";\nimport { storageTables, type StorageRole } from \"./storage-tables\";\n\n// Removed provider grants retain revocation custody for 30 days; claims may extend it.\nconst revocationRetentionMillis = 30 * 24 * 60 * 60 * 1000;\nconst encodeProfile = Schema.encodeSync(Schema.fromJsonString(M.OAuthConnectedProfile));\nconst encodePolicy = Schema.encodeSync(Schema.fromJsonString(M.OAuthConnectedPolicy));\n\nconst configurationError = (reason: string) => PersistenceConfigurationError.make({ reason });\n\n/** Managed tables use the same native OAuth operations as explicit mappings. */\nexport const makeManagedOAuth = Effect.fnUntraced(function* (\n storage: MappingInput,\n tables: NativeSqlTables,\n features: ReadonlyArray<OAuthFeature>,\n): Effect.fn.Return<\n Context.Context<never>,\n PersistenceConfigurationError,\n Crypto.Crypto | LifecycleHooks | SqlClient.SqlClient | SqlBatchCommit\n> {\n const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n const batch = yield* SqlBatchCommit;\n\n const clock = yield* makeStorageClock(storage).pipe(\n Effect.mapError(() => configurationError(\"Cannot configure the OAuth storage clock\")),\n );\n\n function mapped<Role extends StorageRole>(role: Role) {\n const table = storage.tables[role];\n\n if (table === undefined) throw configurationError(`Missing ${role} mapping`);\n\n return {\n table,\n // Storage validation owns these logical column keys.\n ...(Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key])) as {\n readonly [Key in keyof (typeof storageTables)[Role][\"columns\"]]: Key;\n }),\n };\n }\n\n const s = storage.subjects;\n const subject = tables(s.table);\n const credential = mapped(\"oauthCredentials\");\n const credentials = tables(credential.table);\n const authority = mapped(\"credentials\");\n const factors = tables(authority.table);\n const now = tables.expression(clock.engineNowMillis);\n const policies = new Map<string, M.OAuthConnectedPolicy>();\n\n for (const feature of features) {\n if (feature.connected === undefined) continue;\n\n const policy = yield* M.snapshotOAuth(M.OAuthConnectedPolicy, feature.connected).pipe(\n Effect.mapError(() => configurationError(`Invalid OAuth policy for ${feature.moduleId}`)),\n );\n\n const previous = policies.get(feature.moduleId);\n\n if (previous !== undefined && encodePolicy(previous) !== encodePolicy(policy))\n return yield* configurationError(`Conflicting OAuth policies for ${feature.moduleId}`);\n policies.set(feature.moduleId, policy);\n }\n\n const shared = {\n subject: {\n table: s.table,\n id: s.id,\n status: s.status,\n securityRevision: s.securityRevision,\n isActiveStatus: (value: unknown) => Object.is(value, s.activeValue),\n activeCondition: sql`${subject.column(s.status)} = ${subject.value(s.status, s.activeValue)}`,\n decodeAuthenticationRequirement: s.requirements,\n },\n subjectId: { toSubject: s.toSubject, toNative: s.toNative, equals: Object.is },\n ownership: {\n ...mapped(\"oauthIdentities\"),\n decodeSubjectId: (row: Record<string, unknown>) => row.subjectId,\n encodeInsert: () => ({}),\n },\n credential: {\n ...credential,\n status: \"active\",\n isActiveStatus: (value: unknown) => value === true,\n activeCondition: sql`${credentials.column(\"active\")} = ${credentials.value(\"active\", true)}`,\n },\n authority: {\n ...authority,\n status: \"active\",\n isActiveStatus: (value: unknown) => value === true,\n activeCondition: sql`${factors.column(\"active\")} = ${factors.value(\"active\", true)}`,\n },\n clock,\n };\n\n function policyCondition(input: OAuthConnectedPolicyInput<unknown>): Fragment {\n let moduleId: string;\n\n switch (input.kind) {\n case \"sign-in\":\n moduleId = input.credential.moduleId;\n break;\n case \"action\":\n moduleId = input.authorization.challenge.moduleId;\n break;\n case \"metadata\":\n case \"use\":\n moduleId = input.authorization.moduleId;\n }\n const policy = policies.get(moduleId);\n\n if (policy === undefined) return sql`false`;\n const grant = input.grant;\n\n if (grant !== undefined) {\n if (grant.moduleId !== moduleId || grant.subjectId !== input.revision.subjectId)\n return sql`false`;\n // Disconnect must remain possible after a profile is retired or removed.\n if (input.kind !== \"action\" || input.operation !== \"disconnect\") {\n const profile = policy.profiles.find(\n (value) => value.key === grant.configuration.profile.key && value.issuance === \"active\",\n );\n\n if (\n profile === undefined ||\n encodeProfile(profile) !==\n encodeProfile({\n ...grant.configuration.profile,\n issuance: \"active\",\n })\n )\n return sql`false`;\n }\n }\n\n const current = sql`exists(select 1 from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, input.subjectId)} and ${shared.subject.activeCondition} and ${exactSqlText(sql, subject.column(s.securityRevision), subject.value(s.securityRevision, input.revision.securityRevision))})`;\n\n if (input.kind === \"sign-in\")\n return features.some((feature) => feature.moduleId === moduleId && feature.signIn)\n ? current\n : sql`false`;\n if (input.kind !== \"action\") {\n // Mutable application policy shares the subject's guarded security revision.\n return input.authorization.policyRevision === input.revision.securityRevision\n ? current\n : sql`false`;\n }\n\n const { authorization } = input;\n const { challenge, evidence, requirement } = authorization;\n const action = input.operation === \"settle\" ? \"connected-complete\" : \"connected-disconnect\";\n\n if (\n challenge.action !== action ||\n !sameRevision(challenge.revision, input.revision) ||\n !sameRevision(evidence.revision, input.revision) ||\n evidence.flowId !== AuthenticationFlowId.make(challenge.flowId) ||\n evidence.bindingDigest !== challenge.bindingDigest ||\n !satisfies(evidence.proofs, requirement) ||\n evidence.proofs.some(\n (proof) =>\n !input.revision.credentials.some((entry) => entry.credentialId === proof.credentialId),\n )\n )\n return sql`false`;\n\n const maximumAge = Math.min(requirement.maximumAgeMillis, policy.maximumEvidenceAgeMillis);\n const verified = evidence.proofs.map((proof) => DateTime.toEpochMillis(proof.verifiedAt));\n\n if (authorization.source._tag === \"Session\")\n verified.push(DateTime.toEpochMillis(authorization.source.authenticatedAt));\n\n return sql.and([\n current,\n sql`${now} < ${authorization.validUntilMillis}`,\n ...verified.map((instant) => sql`${now} >= ${instant} and ${now} < ${instant + maximumAge}`),\n ]);\n }\n\n let context: Context.Context<never> = Context.empty();\n\n if (features.some((feature) => feature.signIn)) {\n const mapping: OAuthNativeReadMapping = {\n ...shared,\n flow: { ...mapped(\"oauthSignInFlows\"), encodeInsert: () => ({}) },\n constraints: requiredOAuthSignInConstraints,\n };\n\n const services = yield* makeNativeOAuthSignInServices(tables, mapping, batch).pipe(\n Effect.mapError(() => configurationError(\"Cannot configure OAuth sign-in persistence\")),\n );\n\n context = Context.add(context, M.OAuthSignInPersistence, services.oauthSignInPersistence);\n }\n if (policies.size > 0) {\n const job = { ...mapped(\"oauthConnectedRevocations\"), encodeInsert: () => ({}) };\n const grant = { ...mapped(\"oauthConnectedGrants\"), encodeInsert: () => ({}) };\n\n const mapping: OAuthNativeConnectedMapping = {\n ...shared,\n flow: { ...mapped(\"oauthConnectedFlows\"), encodeInsert: () => ({}) },\n grant,\n policy: { condition: policyCondition },\n constraints: requiredOAuthConnectedConstraints,\n revocation: {\n mode: \"provider\",\n job,\n retentionMillis: revocationRetentionMillis,\n constraints: requiredOAuthConnectedRevocationConstraints,\n },\n otherReferences: ({ identityKey, subjectId }) =>\n sql`exists(select 1 from ${credentials.name} where ${credentials.column(\"subjectId\")} = ${credentials.value(\"subjectId\", subjectId)} and ${exactSqlText(sql, credentials.column(\"identityKey\"), credentials.value(\"identityKey\", identityKey))})`,\n };\n\n const connected = yield* makeNativeOAuthConnectedServices(tables, mapping, batch).pipe(\n Effect.mapError(() => configurationError(\"Cannot configure OAuth connected persistence\")),\n );\n\n const revocations = yield* makeNativeOAuthRevocationServices(\n tables,\n {\n ...mapping,\n job,\n constraints: requiredOAuthConnectedRevocationConstraints,\n },\n batch,\n ).pipe(\n Effect.mapError(() => configurationError(\"Cannot configure OAuth revocation persistence\")),\n );\n\n context = context.pipe(\n Context.add(M.OAuthConnectedPersistence, {\n ...connected.oauthConnectedPersistence,\n read: (input) =>\n connected.oauthConnectedPersistence\n .read(input)\n .pipe(\n Effect.map((snapshot) =>\n snapshot === undefined\n ? undefined\n : { ...snapshot, policyRevision: snapshot.revision.securityRevision },\n ),\n ),\n }),\n Context.add(M.OAuthConnectedRevocations, revocations.oauthConnectedRevocations),\n );\n }\n\n return context;\n});\n"],"mappings":"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"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { PersistenceConfigurationError } from "./configuration.mjs";
|
|
2
2
|
import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
3
3
|
import { anySqlCondition } from "./sql-change.mjs";
|
|
4
|
+
import { SqlBatchCommit } from "./sql-commit.mjs";
|
|
5
|
+
import { storageTables } from "./storage-tables.mjs";
|
|
4
6
|
import { requiredPasskeyCredentialConstraints, requiredPasskeyPersistenceConstraints } from "./models/passkey-model.mjs";
|
|
5
|
-
import { makeNativePasskeyServices } from "./passkey-native.mjs";
|
|
7
|
+
import { makeBatchPasskeyServices, makeNativePasskeyServices } from "./passkey-native.mjs";
|
|
6
8
|
import { makePasskeyNativeManagement } from "./passkey-native-write.mjs";
|
|
7
|
-
import { storageTables } from "./storage-tables.mjs";
|
|
8
9
|
import { Context, Effect, Schema } from "effect";
|
|
9
10
|
import { SqlClient } from "effect/sql";
|
|
10
11
|
import * as M from "@yielded/auth/Passkey";
|
|
@@ -177,6 +178,7 @@ const mappings = Effect.fnUntraced(function* (storage, tables, passwordModules,
|
|
|
177
178
|
});
|
|
178
179
|
/** Managed and mapped tables feed the same native passkey statements. */
|
|
179
180
|
const makeManagedPasskeys = Effect.fnUntraced(function* (input, tables) {
|
|
181
|
+
const batch = yield* SqlBatchCommit;
|
|
180
182
|
const policies = yield* Effect.forEach(input.features, (feature) => Effect.map(feature.policy, (policy) => ({
|
|
181
183
|
feature,
|
|
182
184
|
policy
|
|
@@ -186,7 +188,7 @@ const makeManagedPasskeys = Effect.fnUntraced(function* (input, tables) {
|
|
|
186
188
|
const managers = /* @__PURE__ */ new Map();
|
|
187
189
|
for (const feature of input.features) {
|
|
188
190
|
const selected = feature.management ? mapping.management(feature) : mapping.base(feature);
|
|
189
|
-
const base = yield* makeNativePasskeyServices(tables, selected);
|
|
191
|
+
const base = yield* batch === void 0 ? makeNativePasskeyServices(tables, selected) : makeBatchPasskeyServices(tables, selected);
|
|
190
192
|
services.set(feature.moduleId, base);
|
|
191
193
|
if (feature.management) managers.set(feature.moduleId, makePasskeyNativeManagement(base, mapping.management(feature)).passkeyManagementPersistence);
|
|
192
194
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-managed.mjs","names":[],"sources":["../../src/internal/passkey-managed.ts"],"sourcesContent":["import * as M from \"@yielded/auth/Passkey\";\nimport { Context, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport {\n PersistenceConfigurationError,\n type MappingInput,\n type PasskeyFeature,\n} from \"./configuration\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport {\n requiredPasskeyCredentialConstraints,\n requiredPasskeyPersistenceConstraints,\n} from \"./models/passkey-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeNativePasskeyServices } from \"./passkey-native\";\nimport type { PasskeyNativeMapping, PasskeyNativeRead } from \"./passkey-native-state\";\nimport {\n makePasskeyNativeManagement,\n type NativePasskeyManagementMapping,\n} from \"./passkey-native-write\";\nimport { anySqlCondition } from \"./sql-change\";\nimport { storageTables, type StorageRole } from \"./storage-tables\";\n\nexport interface ComposedPasskeyInput {\n readonly storage: MappingInput;\n readonly namespace: string;\n readonly dialect: \"pg\" | \"sqlite\";\n readonly features: ReadonlyArray<PasskeyFeature>;\n readonly passwordModules: ReadonlyArray<string>;\n}\n\nconst profileJson = Schema.fromJsonString(M.PasskeyProfile);\n\nconst credentialData = M.PasskeyCredential.mapFields(\n ({ revision: _revision, active: _active, requirement: _requirement, ...fields }) => fields,\n);\n\nconst configurationError = (reason: string) => PersistenceConfigurationError.make({ reason });\n\nconst mappings = Effect.fnUntraced(function* (\n storage: MappingInput,\n tables: NativeSqlTables,\n passwordModules: ReadonlyArray<string>,\n signInProfiles: ReadonlyArray<M.PasskeyProfile>,\n) {\n const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n\n const table = (role: StorageRole) => {\n const value = storage.tables[role];\n\n if (value === undefined) throw configurationError(`Missing ${role} mapping`);\n\n return value;\n };\n\n const mapped = <Role extends StorageRole>(role: Role) => ({\n table: table(role),\n // Managed storage validation owns these physical column keys.\n ...(Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key])) as {\n readonly [Key in keyof (typeof storageTables)[Role][\"columns\"]]: Key;\n }),\n });\n\n const t = (role: StorageRole) => tables(table(role));\n\n const eq = (role: StorageRole, key: string, value: unknown) =>\n sql`${t(role).column(key)} = ${t(role).value(key, value)}`;\n\n const s = storage.subjects;\n const subject = tables(s.table);\n const active = (value: unknown) => value === true;\n\n const read: PasskeyNativeRead = {\n subject: {\n table: s.table,\n id: s.id,\n status: s.status,\n securityRevision: s.securityRevision,\n decodeId: (row) => row[s.id],\n decodeRequirement: s.requirements,\n isActiveStatus: (value) => Object.is(value, s.activeValue),\n activeCondition: sql`${subject.column(s.status)} = ${subject.value(s.status, s.activeValue)}`,\n },\n credential: {\n ...mapped(\"passkeyCredentials\"),\n status: \"active\",\n decode: (row) =>\n Schema.decodeUnknownSync(credentialData)({\n ...row,\n profile: Schema.decodeUnknownSync(profileJson)(row.profile),\n }),\n decodeSubjectId: (row) => row.subjectId,\n isActiveStatus: active,\n activeCondition: eq(\"passkeyCredentials\", \"active\", true),\n },\n authority: {\n ...mapped(\"credentials\"),\n status: \"active\",\n isActiveStatus: active,\n activeCondition: eq(\"credentials\", \"active\", true),\n },\n subjectIds: { toNative: s.toNativeSync, toSubject: s.toSubjectSync, equals: Object.is },\n constraints: requiredPasskeyCredentialConstraints,\n };\n\n const base = (feature: PasskeyFeature): PasskeyNativeMapping => ({\n moduleId: feature.moduleId,\n read,\n flow: { ...mapped(\"passkeyFlows\"), encodeInsert: () => ({}) },\n clock: {\n encodeInstant: storage.encodeInstant,\n decodeInstant: storage.decodeInstantSync,\n engineNowMillis: sql.onDialectOrElse({\n pg: () => sql`cast(extract(epoch from clock_timestamp()) * 1000 as bigint)`,\n orElse: () => sql`cast(round((julianday('now') - 2440587.5) * 86400000) as integer)`,\n }),\n toMillis: (expression: Fragment) => expression,\n fromMillis: (expression: Fragment) => expression,\n },\n telemetry: {\n lastUsedAt: \"lastUsedAt\",\n encodeBackupEligible: (value) => value,\n encodeBackupState: (value) => value,\n },\n constraints: requiredPasskeyPersistenceConstraints,\n });\n\n const metadata = (nativeId: unknown) =>\n sql`exists(select 1 from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, nativeId)} and ${subject.column(s.status)} = ${subject.value(s.status, s.activeValue)})`;\n\n const management = (feature: PasskeyFeature): NativePasskeyManagementMapping => {\n const policy = feature.managementPolicy;\n\n if (policy === undefined)\n throw configurationError(`Missing passkey management policy for ${feature.moduleId}`);\n\n const c = t(\"passkeyCredentials\"),\n f = t(\"credentials\");\n\n return {\n ...base(feature),\n write: {\n credential: {\n name: \"name\",\n createdAt: \"createdAt\",\n encodeInsert: ({ credential, subjectId, summary, marker }) => ({\n credentialId: credential.credentialId,\n subjectId,\n rpId: credential.rpId,\n protocolCredentialId: credential.protocolCredentialId,\n userHandle: credential.userHandle,\n credentialKey: \"\",\n publicKey: credential.publicKey,\n algorithm: credential.algorithm,\n profile: Schema.encodeSync(profileJson)(credential.profile),\n credentialRevision: marker,\n active: true,\n primarySignIn: credential.primarySignIn,\n enrollmentUserVerified: credential.enrollmentUserVerified,\n backupEligible: credential.backupEligible,\n backupState: credential.backupState,\n counter: credential.counter,\n name: summary.name,\n createdAt: storage.encodeInstant(summary.createdAtMillis),\n }),\n encodePrimarySignIn: (value) => value,\n encodeEnrollmentUserVerified: (value) => value,\n encodeBackupEligible: (value) => value,\n activeStatus: true,\n removedStatus: false,\n },\n authority: {\n encodeInsert: ({ subjectId, credential, marker }) => ({\n subjectId,\n credentialId: credential.credentialId,\n revision: marker,\n active: true,\n }),\n activeStatus: true,\n removedStatus: false,\n },\n policy: {\n subjectColumns: subject.keys,\n management: () => policy,\n requirement: s.actionRequirements,\n metadata,\n action: metadata,\n remainingSignIn: (nativeId, excluded, row) =>\n Effect.gen(function* () {\n const requirement = yield* Schema.decodeEffect(M.PasskeyRequirement)(\n yield* s.requirements(row),\n );\n\n const alternatives: Fragment[] = [];\n\n if (\n requirement.alternatives.some(\n (alternative) =>\n alternative.minimumCredentials === 1 &&\n alternative.factors.every((factor) => factor === \"possession\"),\n )\n ) {\n alternatives.push(sql`exists(select 1 from ${c.name} join ${f.name} on ${f.column(\"credentialId\")} = ${c.column(\"credentialId\")} and ${f.column(\"subjectId\")} = ${f.value(\"subjectId\", nativeId)} and ${f.column(\"revision\")} = ${c.column(\"credentialRevision\")} and ${eq(\"credentials\", \"active\", true)}\n where ${eq(\"passkeyCredentials\", \"subjectId\", nativeId)} and ${c.column(\"credentialId\")} <> ${c.value(\"credentialId\", excluded)} and ${eq(\"passkeyCredentials\", \"active\", true)} and ${eq(\"passkeyCredentials\", \"primarySignIn\", true)} and ${eq(\"passkeyCredentials\", \"enrollmentUserVerified\", true)} and ${anySqlCondition(\n signInProfiles.map((profile) => eq(\"passkeyCredentials\", \"rpId\", profile.rpId)),\n )})`);\n }\n if (\n passwordModules.length > 0 &&\n requirement.alternatives.some(\n (alternative) =>\n alternative.minimumCredentials === 1 &&\n !alternative.userVerified &&\n !alternative.phishingResistant &&\n alternative.factors.every((factor) => factor === \"knowledge\"),\n )\n ) {\n const p = t(\"passwords\"),\n i = t(\"identifiers\");\n\n alternatives.push(sql`exists(select 1 from ${p.name} join ${f.name} on ${f.column(\"credentialId\")} = ${p.column(\"credentialId\")} and ${f.column(\"subjectId\")} = ${f.value(\"subjectId\", nativeId)} and ${f.column(\"revision\")} = ${p.column(\"credentialRevision\")} and ${eq(\"credentials\", \"active\", true)}\n join ${i.name} on ${eq(\"identifiers\", \"subjectId\", nativeId)} and ${eq(\"identifiers\", \"active\", true)} and ${eq(\"identifiers\", \"namespace\", \"email\")}\n where ${eq(\"passwords\", \"subjectId\", nativeId)} and ${p.column(\"credentialId\")} <> ${p.value(\"credentialId\", excluded)} and ${anySqlCondition(\n passwordModules.map((module) => eq(\"passwords\", \"moduleId\", module)),\n )})`);\n }\n\n return anySqlCondition(alternatives);\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"mapping\", cause }),\n ),\n ),\n },\n },\n invalidation: {\n window: {\n trigger: \"credential-change\",\n existingSessions: \"immediate\",\n maximumExposureMillis: 0,\n oldAuthenticationEvidence: \"rejected\",\n },\n mutations: [],\n postcondition: ({ subjectId, securityRevision }) =>\n sql`exists(select 1 from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, subjectId)} and ${subject.column(s.securityRevision)} = ${subject.value(s.securityRevision, securityRevision)})`,\n },\n };\n };\n\n return { base, management };\n});\n\n/** Managed and mapped tables feed the same native passkey statements. */\nexport const makeManagedPasskeys = Effect.fnUntraced(\n function* (input: ComposedPasskeyInput, tables: NativeSqlTables) {\n const policies = yield* Effect.forEach(input.features, (feature) =>\n Effect.map(feature.policy, (policy) => ({ feature, policy })),\n );\n\n const mapping = yield* mappings(\n input.storage,\n tables,\n input.passwordModules,\n policies.flatMap(({ feature, policy }) => (feature.management ? [] : policy.profiles)),\n );\n\n const services = new Map<\n string,\n Effect.Success<ReturnType<typeof makeNativePasskeyServices>>\n >();\n\n const managers = new Map<string, M.PasskeyManagementPersistence[\"Service\"]>();\n\n for (const feature of input.features) {\n const selected = feature.management ? mapping.management(feature) : mapping.base(feature);\n const base = yield* makeNativePasskeyServices(tables, selected);\n\n services.set(feature.moduleId, base);\n if (feature.management)\n managers.set(\n feature.moduleId,\n makePasskeyNativeManagement(base, mapping.management(feature))\n .passkeyManagementPersistence,\n );\n }\n\n const service = (moduleId: string) => {\n const value = services.get(moduleId);\n\n return value === undefined\n ? Effect.fail(M.PasskeyUnavailable.make({}))\n : Effect.succeed(value);\n };\n\n const manager = (moduleId: string) => {\n const value = managers.get(moduleId);\n\n return value === undefined\n ? Effect.fail(M.PasskeyUnavailable.make({}))\n : Effect.succeed(value);\n };\n\n const first = services.values().next().value;\n\n if (first === undefined) return yield* configurationError(\"A passkey module is required\");\n\n let context: Context.Context<never> = Context.make(M.PasskeyCredentials, {\n lookup: (input) => first.passkeyCredentials.lookup(input),\n listForSubject: (input) =>\n Effect.flatMap(service(input.moduleId), (value) =>\n value.passkeyCredentials.listForSubject(input),\n ),\n }).pipe(\n Context.add(M.PasskeyPersistence, {\n issue: (input, prepare) =>\n Effect.flatMap(service(input.ceremony.moduleId), (value) =>\n value.passkeyPersistence.issue(input, prepare),\n ),\n context: (input) =>\n Effect.flatMap(service(input.moduleId), (value) =>\n value.passkeyPersistence.context(input),\n ),\n consume: (input, prepare) =>\n Effect.flatMap(service(input.access.moduleId), (value) =>\n value.passkeyPersistence.consume(input, prepare),\n ),\n cleanup: (input, prepare) =>\n Effect.flatMap(service(input.moduleId), (value) =>\n value.passkeyPersistence.cleanup(input, prepare),\n ),\n }),\n );\n\n if (managers.size > 0)\n context = context.pipe(\n Context.add(M.PasskeyManagementPersistence, {\n completeEnrollment: (input, prepare) =>\n Effect.flatMap(manager(input.ceremony.moduleId), (value) =>\n value.completeEnrollment(input, prepare),\n ),\n list: (input) => Effect.flatMap(manager(input.moduleId), (value) => value.list(input)),\n inspectRemove: (input) =>\n Effect.flatMap(manager(input.moduleId), (value) => value.inspectRemove(input)),\n rename: (input, prepare) =>\n Effect.flatMap(manager(input.moduleId), (value) => value.rename(input, prepare)),\n remove: (input, prepare) =>\n Effect.flatMap(manager(input.moduleId), (value) => value.remove(input, prepare)),\n }),\n );\n\n return context;\n },\n Effect.mapError((error) =>\n Schema.is(PersistenceConfigurationError)(error)\n ? error\n : configurationError(\"Unable to configure passkey persistence\"),\n ),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"passkey-managed.mjs","names":[],"sources":["../../src/internal/passkey-managed.ts"],"sourcesContent":["import * as M from \"@yielded/auth/Passkey\";\nimport { Context, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport {\n PersistenceConfigurationError,\n type MappingInput,\n type PasskeyFeature,\n} from \"./configuration\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport {\n requiredPasskeyCredentialConstraints,\n requiredPasskeyPersistenceConstraints,\n} from \"./models/passkey-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { makeBatchPasskeyServices, makeNativePasskeyServices } from \"./passkey-native\";\nimport type { PasskeyNativeMapping, PasskeyNativeRead } from \"./passkey-native-state\";\nimport {\n makePasskeyNativeManagement,\n type NativePasskeyManagementMapping,\n} from \"./passkey-native-write\";\nimport { anySqlCondition } from \"./sql-change\";\nimport { SqlBatchCommit } from \"./sql-commit\";\nimport { storageTables, type StorageRole } from \"./storage-tables\";\n\nexport interface ComposedPasskeyInput {\n readonly storage: MappingInput;\n readonly namespace: string;\n readonly dialect: \"pg\" | \"sqlite\";\n readonly features: ReadonlyArray<PasskeyFeature>;\n readonly passwordModules: ReadonlyArray<string>;\n}\n\nconst profileJson = Schema.fromJsonString(M.PasskeyProfile);\n\nconst credentialData = M.PasskeyCredential.mapFields(\n ({ revision: _revision, active: _active, requirement: _requirement, ...fields }) => fields,\n);\n\nconst configurationError = (reason: string) => PersistenceConfigurationError.make({ reason });\n\nconst mappings = Effect.fnUntraced(function* (\n storage: MappingInput,\n tables: NativeSqlTables,\n passwordModules: ReadonlyArray<string>,\n signInProfiles: ReadonlyArray<M.PasskeyProfile>,\n) {\n const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n\n const table = (role: StorageRole) => {\n const value = storage.tables[role];\n\n if (value === undefined) throw configurationError(`Missing ${role} mapping`);\n\n return value;\n };\n\n const mapped = <Role extends StorageRole>(role: Role) => ({\n table: table(role),\n // Managed storage validation owns these physical column keys.\n ...(Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key])) as {\n readonly [Key in keyof (typeof storageTables)[Role][\"columns\"]]: Key;\n }),\n });\n\n const t = (role: StorageRole) => tables(table(role));\n\n const eq = (role: StorageRole, key: string, value: unknown) =>\n sql`${t(role).column(key)} = ${t(role).value(key, value)}`;\n\n const s = storage.subjects;\n const subject = tables(s.table);\n const active = (value: unknown) => value === true;\n\n const read: PasskeyNativeRead = {\n subject: {\n table: s.table,\n id: s.id,\n status: s.status,\n securityRevision: s.securityRevision,\n decodeId: (row) => row[s.id],\n decodeRequirement: s.requirements,\n isActiveStatus: (value) => Object.is(value, s.activeValue),\n activeCondition: sql`${subject.column(s.status)} = ${subject.value(s.status, s.activeValue)}`,\n },\n credential: {\n ...mapped(\"passkeyCredentials\"),\n status: \"active\",\n decode: (row) =>\n Schema.decodeUnknownSync(credentialData)({\n ...row,\n profile: Schema.decodeUnknownSync(profileJson)(row.profile),\n }),\n decodeSubjectId: (row) => row.subjectId,\n isActiveStatus: active,\n activeCondition: eq(\"passkeyCredentials\", \"active\", true),\n },\n authority: {\n ...mapped(\"credentials\"),\n status: \"active\",\n isActiveStatus: active,\n activeCondition: eq(\"credentials\", \"active\", true),\n },\n subjectIds: { toNative: s.toNativeSync, toSubject: s.toSubjectSync, equals: Object.is },\n constraints: requiredPasskeyCredentialConstraints,\n };\n\n const base = (feature: PasskeyFeature): PasskeyNativeMapping => ({\n moduleId: feature.moduleId,\n read,\n flow: { ...mapped(\"passkeyFlows\"), encodeInsert: () => ({}) },\n clock: {\n encodeInstant: storage.encodeInstant,\n decodeInstant: storage.decodeInstantSync,\n engineNowMillis: sql.onDialectOrElse({\n pg: () => sql`cast(extract(epoch from clock_timestamp()) * 1000 as bigint)`,\n orElse: () => sql`cast(round((julianday('now') - 2440587.5) * 86400000) as integer)`,\n }),\n toMillis: (expression: Fragment) => expression,\n fromMillis: (expression: Fragment) => expression,\n },\n telemetry: {\n lastUsedAt: \"lastUsedAt\",\n encodeBackupEligible: (value) => value,\n encodeBackupState: (value) => value,\n },\n constraints: requiredPasskeyPersistenceConstraints,\n });\n\n const metadata = (nativeId: unknown) =>\n sql`exists(select 1 from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, nativeId)} and ${subject.column(s.status)} = ${subject.value(s.status, s.activeValue)})`;\n\n const management = (feature: PasskeyFeature): NativePasskeyManagementMapping => {\n const policy = feature.managementPolicy;\n\n if (policy === undefined)\n throw configurationError(`Missing passkey management policy for ${feature.moduleId}`);\n\n const c = t(\"passkeyCredentials\"),\n f = t(\"credentials\");\n\n return {\n ...base(feature),\n write: {\n credential: {\n name: \"name\",\n createdAt: \"createdAt\",\n encodeInsert: ({ credential, subjectId, summary, marker }) => ({\n credentialId: credential.credentialId,\n subjectId,\n rpId: credential.rpId,\n protocolCredentialId: credential.protocolCredentialId,\n userHandle: credential.userHandle,\n credentialKey: \"\",\n publicKey: credential.publicKey,\n algorithm: credential.algorithm,\n profile: Schema.encodeSync(profileJson)(credential.profile),\n credentialRevision: marker,\n active: true,\n primarySignIn: credential.primarySignIn,\n enrollmentUserVerified: credential.enrollmentUserVerified,\n backupEligible: credential.backupEligible,\n backupState: credential.backupState,\n counter: credential.counter,\n name: summary.name,\n createdAt: storage.encodeInstant(summary.createdAtMillis),\n }),\n encodePrimarySignIn: (value) => value,\n encodeEnrollmentUserVerified: (value) => value,\n encodeBackupEligible: (value) => value,\n activeStatus: true,\n removedStatus: false,\n },\n authority: {\n encodeInsert: ({ subjectId, credential, marker }) => ({\n subjectId,\n credentialId: credential.credentialId,\n revision: marker,\n active: true,\n }),\n activeStatus: true,\n removedStatus: false,\n },\n policy: {\n subjectColumns: subject.keys,\n management: () => policy,\n requirement: s.actionRequirements,\n metadata,\n action: metadata,\n remainingSignIn: (nativeId, excluded, row) =>\n Effect.gen(function* () {\n const requirement = yield* Schema.decodeEffect(M.PasskeyRequirement)(\n yield* s.requirements(row),\n );\n\n const alternatives: Fragment[] = [];\n\n if (\n requirement.alternatives.some(\n (alternative) =>\n alternative.minimumCredentials === 1 &&\n alternative.factors.every((factor) => factor === \"possession\"),\n )\n ) {\n alternatives.push(sql`exists(select 1 from ${c.name} join ${f.name} on ${f.column(\"credentialId\")} = ${c.column(\"credentialId\")} and ${f.column(\"subjectId\")} = ${f.value(\"subjectId\", nativeId)} and ${f.column(\"revision\")} = ${c.column(\"credentialRevision\")} and ${eq(\"credentials\", \"active\", true)}\n where ${eq(\"passkeyCredentials\", \"subjectId\", nativeId)} and ${c.column(\"credentialId\")} <> ${c.value(\"credentialId\", excluded)} and ${eq(\"passkeyCredentials\", \"active\", true)} and ${eq(\"passkeyCredentials\", \"primarySignIn\", true)} and ${eq(\"passkeyCredentials\", \"enrollmentUserVerified\", true)} and ${anySqlCondition(\n signInProfiles.map((profile) => eq(\"passkeyCredentials\", \"rpId\", profile.rpId)),\n )})`);\n }\n if (\n passwordModules.length > 0 &&\n requirement.alternatives.some(\n (alternative) =>\n alternative.minimumCredentials === 1 &&\n !alternative.userVerified &&\n !alternative.phishingResistant &&\n alternative.factors.every((factor) => factor === \"knowledge\"),\n )\n ) {\n const p = t(\"passwords\"),\n i = t(\"identifiers\");\n\n alternatives.push(sql`exists(select 1 from ${p.name} join ${f.name} on ${f.column(\"credentialId\")} = ${p.column(\"credentialId\")} and ${f.column(\"subjectId\")} = ${f.value(\"subjectId\", nativeId)} and ${f.column(\"revision\")} = ${p.column(\"credentialRevision\")} and ${eq(\"credentials\", \"active\", true)}\n join ${i.name} on ${eq(\"identifiers\", \"subjectId\", nativeId)} and ${eq(\"identifiers\", \"active\", true)} and ${eq(\"identifiers\", \"namespace\", \"email\")}\n where ${eq(\"passwords\", \"subjectId\", nativeId)} and ${p.column(\"credentialId\")} <> ${p.value(\"credentialId\", excluded)} and ${anySqlCondition(\n passwordModules.map((module) => eq(\"passwords\", \"moduleId\", module)),\n )})`);\n }\n\n return anySqlCondition(alternatives);\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"mapping\", cause }),\n ),\n ),\n },\n },\n invalidation: {\n window: {\n trigger: \"credential-change\",\n existingSessions: \"immediate\",\n maximumExposureMillis: 0,\n oldAuthenticationEvidence: \"rejected\",\n },\n mutations: [],\n postcondition: ({ subjectId, securityRevision }) =>\n sql`exists(select 1 from ${subject.name} where ${subject.column(s.id)} = ${subject.value(s.id, subjectId)} and ${subject.column(s.securityRevision)} = ${subject.value(s.securityRevision, securityRevision)})`,\n },\n };\n };\n\n return { base, management };\n});\n\n/** Managed and mapped tables feed the same native passkey statements. */\nexport const makeManagedPasskeys = Effect.fnUntraced(\n function* (input: ComposedPasskeyInput, tables: NativeSqlTables) {\n const batch = yield* SqlBatchCommit;\n\n const policies = yield* Effect.forEach(input.features, (feature) =>\n Effect.map(feature.policy, (policy) => ({ feature, policy })),\n );\n\n const mapping = yield* mappings(\n input.storage,\n tables,\n input.passwordModules,\n policies.flatMap(({ feature, policy }) => (feature.management ? [] : policy.profiles)),\n );\n\n const services = new Map<\n string,\n Effect.Success<ReturnType<typeof makeNativePasskeyServices>>\n >();\n\n const managers = new Map<string, M.PasskeyManagementPersistence[\"Service\"]>();\n\n for (const feature of input.features) {\n const selected = feature.management ? mapping.management(feature) : mapping.base(feature);\n\n const base = yield* batch === undefined\n ? makeNativePasskeyServices(tables, selected)\n : makeBatchPasskeyServices(tables, selected);\n\n services.set(feature.moduleId, base);\n if (feature.management)\n managers.set(\n feature.moduleId,\n makePasskeyNativeManagement(base, mapping.management(feature))\n .passkeyManagementPersistence,\n );\n }\n\n const service = (moduleId: string) => {\n const value = services.get(moduleId);\n\n return value === undefined\n ? Effect.fail(M.PasskeyUnavailable.make({}))\n : Effect.succeed(value);\n };\n\n const manager = (moduleId: string) => {\n const value = managers.get(moduleId);\n\n return value === undefined\n ? Effect.fail(M.PasskeyUnavailable.make({}))\n : Effect.succeed(value);\n };\n\n const first = services.values().next().value;\n\n if (first === undefined) return yield* configurationError(\"A passkey module is required\");\n\n let context: Context.Context<never> = Context.make(M.PasskeyCredentials, {\n lookup: (input) => first.passkeyCredentials.lookup(input),\n listForSubject: (input) =>\n Effect.flatMap(service(input.moduleId), (value) =>\n value.passkeyCredentials.listForSubject(input),\n ),\n }).pipe(\n Context.add(M.PasskeyPersistence, {\n issue: (input, prepare) =>\n Effect.flatMap(service(input.ceremony.moduleId), (value) =>\n value.passkeyPersistence.issue(input, prepare),\n ),\n context: (input) =>\n Effect.flatMap(service(input.moduleId), (value) =>\n value.passkeyPersistence.context(input),\n ),\n consume: (input, prepare) =>\n Effect.flatMap(service(input.access.moduleId), (value) =>\n value.passkeyPersistence.consume(input, prepare),\n ),\n cleanup: (input, prepare) =>\n Effect.flatMap(service(input.moduleId), (value) =>\n value.passkeyPersistence.cleanup(input, prepare),\n ),\n }),\n );\n\n if (managers.size > 0)\n context = context.pipe(\n Context.add(M.PasskeyManagementPersistence, {\n completeEnrollment: (input, prepare) =>\n Effect.flatMap(manager(input.ceremony.moduleId), (value) =>\n value.completeEnrollment(input, prepare),\n ),\n list: (input) => Effect.flatMap(manager(input.moduleId), (value) => value.list(input)),\n inspectRemove: (input) =>\n Effect.flatMap(manager(input.moduleId), (value) => value.inspectRemove(input)),\n rename: (input, prepare) =>\n Effect.flatMap(manager(input.moduleId), (value) => value.rename(input, prepare)),\n remove: (input, prepare) =>\n Effect.flatMap(manager(input.moduleId), (value) => value.remove(input, prepare)),\n }),\n );\n\n return context;\n },\n Effect.mapError((error) =>\n Schema.is(PersistenceConfigurationError)(error)\n ? error\n : configurationError(\"Unable to configure passkey persistence\"),\n ),\n);\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { sqlBatchAssertion } from "./d1-planning.mjs";
|
|
2
2
|
import { appendSqlBatchStatement } from "./sql-commit.mjs";
|
|
3
|
+
import { randomId } from "./crypto.mjs";
|
|
3
4
|
import { passkeyMatchesAccess, validPasskeyCeremony } from "./passkey-policy.mjs";
|
|
4
5
|
import { passkeyNativeInvariant } from "./passkey-native-state.mjs";
|
|
5
6
|
import { preparePasskeyNative } from "./passkey-native.mjs";
|
|
6
|
-
import { randomId } from "./crypto.mjs";
|
|
7
7
|
import { passkeyCanonicalJson, passkeyDigest } from "./passkey-actions.mjs";
|
|
8
8
|
import { makePasskeyNativeWrites } from "./passkey-native-write.mjs";
|
|
9
9
|
import { Effect, Schema } from "effect";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { sqlMapping } from "./native-sql-table.mjs";
|
|
2
2
|
import { exactSqlText } from "./sql-change.mjs";
|
|
3
|
+
import { canJoinTextColumns } from "./storage-validation.mjs";
|
|
3
4
|
import { passkeyCredentialKey } from "./passkey-policy.mjs";
|
|
4
5
|
import { makePasskeyNativeFlow } from "./passkey-native-flow.mjs";
|
|
5
6
|
import { Effect, Schema } from "effect";
|
|
6
7
|
import { SqlClient } from "effect/sql";
|
|
7
8
|
import * as M from "@yielded/auth/Passkey";
|
|
9
|
+
import { AuthenticationClock } from "@yielded/auth/Operations";
|
|
8
10
|
//#region src/internal/passkey-native-state.ts
|
|
9
11
|
const passkeyNativeInvariant = (value) => {
|
|
10
12
|
if (!value) throw M.PasskeyUnavailable.make({});
|
|
@@ -119,6 +121,11 @@ const makePasskeyNativeReadState = Effect.fnUntraced(function* (tables, read, cl
|
|
|
119
121
|
const selected = (kind, alias) => {
|
|
120
122
|
return sql`(select * from ${(kind === "subject" ? subject : kind === "authority" ? factor : credential).name} where ${active(kind)}) as ${sql(alias)}`;
|
|
121
123
|
};
|
|
124
|
+
const joinedIds = yield* canJoinTextColumns([
|
|
125
|
+
subject.unencodedTextColumn?.(read.subject.id),
|
|
126
|
+
credential.unencodedTextColumn?.(read.credential.subjectId),
|
|
127
|
+
factor.unencodedTextColumn?.(read.authority.subjectId)
|
|
128
|
+
]).pipe(Effect.mapError(() => M.PasskeyUnavailable.make({})));
|
|
122
129
|
return {
|
|
123
130
|
sql,
|
|
124
131
|
tables,
|
|
@@ -136,31 +143,32 @@ const makePasskeyNativeReadState = Effect.fnUntraced(function* (tables, read, cl
|
|
|
136
143
|
readAuthority,
|
|
137
144
|
lookup: Effect.fnUntraced(function* (rpId, protocolCredentialId) {
|
|
138
145
|
const key = yield* passkeyCredentialKey(rpId, protocolCredentialId);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
left join ${selected("authority", "passkey_factor")} on ${exactSqlText(sql, f.column(read.authority.subjectId), c.column(read.credential.subjectId))}
|
|
145
|
-
where ${exactSqlText(sql, c.column(read.credential.credentialKey), c.value(read.credential.credentialKey, key))} limit 65`;
|
|
146
|
-
if (rows.length === 0 || rows.length > 64) return void 0;
|
|
147
|
-
const row = c.decode(rows[0], "c_");
|
|
148
|
-
const subjectRow = s.decode(rows[0], "s_");
|
|
149
|
-
const owner = read.credential.decodeSubjectId(row);
|
|
146
|
+
let nativeId;
|
|
147
|
+
if (!joinedIds) {
|
|
148
|
+
const found = yield* sql`select ${credential.fields("c_")} from ${credential.name} where ${exactSqlText(sql, credential.column(read.credential.credentialKey), credential.value(read.credential.credentialKey, key))} and ${active("credential")}`;
|
|
149
|
+
if (found.length !== 1) return void 0;
|
|
150
|
+
const row = credential.decode(found[0], "c_");
|
|
150
151
|
invariant(row[read.credential.rpId] === rpId && row[read.credential.protocolCredentialId] === protocolCredentialId);
|
|
151
|
-
|
|
152
|
-
const other = c.decode(selected, "c_");
|
|
153
|
-
const subjectValue = s.decode(selected, "s_");
|
|
154
|
-
return sameId(owner, read.credential.decodeSubjectId(other)) && sameId(owner, read.subject.decodeId(subjectValue)) && other[read.credential.credentialId] === row[read.credential.credentialId] && other[read.credential.credentialRevision] === row[read.credential.credentialRevision] && subjectValue[read.subject.securityRevision] === subjectRow[read.subject.securityRevision];
|
|
155
|
-
}));
|
|
156
|
-
return yield* decodeCredential(row, revision(subjectRow, rows.map((row) => f.decode(row, "f_"))), subjectRow);
|
|
152
|
+
nativeId = read.credential.decodeSubjectId(row);
|
|
157
153
|
}
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
const s = joinedSubject, c = joinedCredential, f = joinedFactor;
|
|
155
|
+
const bindOwner = (table, key) => joinedIds ? exactSqlText(sql, table.column(key), c.column(read.credential.subjectId)) : sql`${table.column(key)} = ${table.value(key, nativeId)}`;
|
|
156
|
+
const rows = yield* sql`select ${s.fields("s_")}, ${c.fields("c_")}, ${f.fields("f_")}
|
|
157
|
+
from ${selected("credential", "passkey_credential")}
|
|
158
|
+
join ${selected("subject", "passkey_subject")} on ${bindOwner(s, read.subject.id)}
|
|
159
|
+
left join ${selected("authority", "passkey_factor")} on ${bindOwner(f, read.authority.subjectId)}
|
|
160
|
+
where ${exactSqlText(sql, c.column(read.credential.credentialKey), c.value(read.credential.credentialKey, key))}${joinedIds ? sql`` : sql` and ${bindOwner(c, read.credential.subjectId)}`} limit 65`;
|
|
161
|
+
if (rows.length === 0 || rows.length > 64) return void 0;
|
|
162
|
+
const row = c.decode(rows[0], "c_");
|
|
163
|
+
const subjectRow = s.decode(rows[0], "s_");
|
|
164
|
+
const owner = read.credential.decodeSubjectId(row);
|
|
165
|
+
invariant((joinedIds || sameId(owner, nativeId)) && row[read.credential.rpId] === rpId && row[read.credential.protocolCredentialId] === protocolCredentialId);
|
|
166
|
+
invariant(rows.every((selected) => {
|
|
167
|
+
const other = c.decode(selected, "c_");
|
|
168
|
+
const subjectValue = s.decode(selected, "s_");
|
|
169
|
+
return sameId(owner, read.credential.decodeSubjectId(other)) && sameId(owner, read.subject.decodeId(subjectValue)) && other[read.credential.credentialId] === row[read.credential.credentialId] && other[read.credential.credentialRevision] === row[read.credential.credentialRevision] && subjectValue[read.subject.securityRevision] === subjectRow[read.subject.securityRevision];
|
|
170
|
+
}));
|
|
171
|
+
return yield* decodeCredential(row, revision(subjectRow, rows.map((row) => f.decode(row, "f_"))), subjectRow);
|
|
164
172
|
}),
|
|
165
173
|
joinedSubject,
|
|
166
174
|
joinedFactor,
|
|
@@ -171,9 +179,11 @@ const makePasskeyNativeReadState = Effect.fnUntraced(function* (tables, read, cl
|
|
|
171
179
|
});
|
|
172
180
|
const makePasskeyNativeState = Effect.fnUntraced(function* (tables, mapping) {
|
|
173
181
|
const state = yield* makePasskeyNativeReadState(tables, mapping.read, mapping.clock);
|
|
182
|
+
const clockPolicy = yield* AuthenticationClock;
|
|
174
183
|
const ceremony = yield* makePasskeyNativeFlow(tables, mapping);
|
|
175
184
|
return {
|
|
176
185
|
...state,
|
|
186
|
+
clockPolicy,
|
|
177
187
|
mapping,
|
|
178
188
|
ceremony,
|
|
179
189
|
flow: ceremony.flow,
|