@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":"email-native.mjs","names":["unavailable"],"sources":["../../src/internal/email-native.ts"],"sourcesContent":["import * as E from \"@yielded/auth/Email\";\nimport { CurrentCommitJournal, type LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { ProofRedemptionInput } from \"@yielded/auth/Proofs\";\nimport type { AuthenticationRevision } from \"@yielded/auth/Sessions\";\nimport { SecurityRevision } from \"@yielded/auth/Sessions\";\nimport { Crypto, DateTime, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport {\n makeNativeEmailReader,\n emailUnavailable as unavailable,\n ensureEmail as ensure,\n} from \"./email-native-state\";\nimport {\n allocateEmailValue,\n allocateEmailSecurityRevision,\n snapshotEmailMutation,\n validateEmailMutation,\n sameEmailRevision,\n} from \"./email-policy\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyEmailAddressMapping, AnyEmailSignInMapping } from \"./models/email-model\";\nimport type { AnyProofPersistenceMapping } from \"./models/proof-model\";\nimport type { NativeSqlTables, SqlTable } from \"./native-sql-table\";\nimport { passwordEvidenceSatisfiedAt } from \"./password-policy\";\nimport { makeNativeProofStore } from \"./proof-native\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n appendSqlBatchStatement,\n makeSqlCommitExecutor,\n registerSqlBatchPostcondition,\n registerSqlCommitReceipt,\n registerSqlPostcondition,\n SqlBatchCommit,\n} from \"./sql-commit\";\n\nexport const makeNativeEmailSignInServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: AnyEmailSignInMapping,\n): Effect.fn.Return<\n { readonly emailSignInTargets: E.EmailSignInTargets[\"Service\"] },\n never,\n SqlClient | LifecycleHooks\n> {\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const reader = yield* makeNativeEmailReader(tables, mapping);\n\n return {\n emailSignInTargets: E.EmailSignInTargets.of({\n lookup: (input) => executor.read(reader.lookup(input)),\n }),\n };\n});\n\nexport const makeNativeEmailAddressServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: AnyEmailAddressMapping,\n proofs?: AnyProofPersistenceMapping,\n): Effect.fn.Return<\n { readonly emailAddressPersistence: E.EmailAddressPersistence[\"Service\"] },\n never,\n SqlClient | LifecycleHooks | Crypto.Crypto | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const sql = (yield* SqlClient).withoutTransforms(),\n executor = yield* makeSqlCommitExecutor(unavailable),\n crypto = yield* Crypto.Crypto;\n\n const now = tables.expression(mapping.clock.engineNowMillis);\n const reader = yield* makeNativeEmailReader(tables, mapping, now);\n\n const s = mapping.subject,\n i = mapping.identifier,\n c = mapping.credential,\n a = mapping.authorityCredential;\n\n const subject = tables(s.table),\n identifier = tables(i.table),\n credential = tables(c.table),\n authority = tables(a.table);\n\n const exact = (t: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, t.column(key), t.value(key, value));\n\n const id = (t: SqlTable, key: string, value: unknown) =>\n sql`${t.column(key)} = ${t.value(key, value)}`;\n\n const identifierKey = (value: { readonly namespace: string; readonly value: string }) =>\n sql.and([\n exact(identifier, i.namespace, value.namespace),\n exact(identifier, i.value, value.value),\n ]);\n\n const activeSubject = id(subject, s.status, s.activeStatusValue),\n activeCredential = id(credential, c.status, c.activeStatusValue),\n activeAuthority = id(authority, a.status, a.activeStatusValue);\n\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n const mode = \"interactive\";\n\n const run = <A, X, R>(work: Effect.Effect<A, X, R>) =>\n batch === undefined\n ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n : executor\n .batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const stage = Effect.fnUntraced(function* (statement: Fragment) {\n if (batch === undefined) ensure((yield* executeSqlChange(sql, statement)) === 1);\n else {\n yield* appendSqlBatchStatement(sql`${statement}`);\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = 1`));\n }\n });\n\n const prepare = <A>(\n decision: E.EmailAddressDecision,\n project: E.PrepareEmailCommit<E.EmailAddressDecision, A>,\n ) =>\n Effect.gen(function* () {\n const receipt = project(decision, yield* CurrentCommitJournal);\n\n yield* registerSqlCommitReceipt(receipt);\n\n return receipt;\n });\n\n const target = Effect.fnUntraced(function* (\n input: Parameters<E.EmailAddressPersistence[\"Service\"][\"target\"]>[0],\n sourceIdentifier?: { readonly namespace: string; readonly value: string },\n ) {\n const state = yield* reader.read({\n moduleId: input.moduleId,\n subjectId: input.subjectId,\n identifier: input.target,\n ...(input.sourceCredentialId === undefined\n ? {}\n : { sourceCredentialId: input.sourceCredentialId }),\n ...(sourceIdentifier === undefined ? {} : { sourceIdentifier }),\n single: mapping.addressCardinality === \"single\",\n });\n\n if (state === undefined) return undefined;\n\n const owned =\n state.targetIdentifier !== undefined &&\n (yield* mapping.subjectId.toSubject(state.targetIdentifier[i.subjectId])) === input.subjectId;\n\n const mutable =\n owned &&\n mapping.identifier.isMutableTarget(state.targetIdentifier!) &&\n (state.targetCredential === undefined ||\n (!c.isActiveStatus(state.targetCredential[c.status]) &&\n (yield* mapping.subjectId.toSubject(state.targetCredential[c.subjectId])) ===\n input.subjectId));\n\n const source = state.sourceSnapshot;\n\n const eligible =\n (state.targetIdentifier === undefined ? state.targetCredential === undefined : mutable) &&\n (input.sourceCredentialId === undefined || source !== undefined) &&\n (source === undefined ||\n source.identifier.namespace !== input.target.namespace ||\n source.identifier.value !== input.target.value) &&\n !state.cardinalityConflict;\n\n const captured: E.EmailAddressTarget = {\n revision: state.revision,\n eligible,\n ...(source === undefined ? {} : { source }),\n ...(mutable\n ? {\n targetIdentifierRevision: yield* Schema.decodeUnknownEffect(SecurityRevision)(\n state.targetIdentifier![i.bindingRevision],\n ),\n }\n : {}),\n };\n\n return { ...state, target: captured };\n });\n\n const mutate = <A>(\n original: E.EmailAddressMutation,\n action: E.EmailAction,\n project: E.PrepareEmailCommit<E.EmailAddressDecision, A>,\n ) =>\n run(\n Effect.gen(function* () {\n const input = yield* snapshotEmailMutation(original);\n\n const redemption = yield* Schema.decodeEffect(Schema.toCodecIso(ProofRedemptionInput))(\n yield* Schema.encodeEffect(Schema.toCodecIso(ProofRedemptionInput))(\n original.redemption.input,\n ),\n );\n\n const native = yield* mapping.subjectId.toNative(input.captured.revision.subjectId);\n\n const locked =\n yield* sql`select ${subject.fields(\"email_locked_\")} from ${subject.name} where ${id(subject, s.id, native)} limit 2 ${batch === undefined ? lock : sql``}`;\n\n if (locked.length !== 1) return yield* prepare(\"rejected\", project);\n ensure(\n (yield* mapping.subjectId.toSubject(\n subject.decode(locked[0]!, \"email_locked_\")[s.id],\n )) === input.captured.revision.subjectId,\n );\n\n const current = yield* target(\n {\n moduleId: input.moduleId,\n subjectId: input.captured.revision.subjectId,\n target: input.target,\n ...(input.captured.source === undefined\n ? {}\n : { sourceCredentialId: input.captured.source.credentialId }),\n },\n input.captured.source?.identifier,\n );\n\n if (current === undefined || proofs === undefined)\n return yield* prepare(\"rejected\", project);\n const requirement = yield* s.decodeActionRequirement(current.subject, action);\n\n if (\n !(yield* validateEmailMutation(\n mapping,\n { ...input, redemption: { input: redemption, prepare: original.redemption.prepare } },\n action,\n { target: current.target, requirement: Effect.succeed(requirement) },\n current.now,\n ))\n )\n return yield* prepare(\"rejected\", project);\n const requirements = [input.authorization.requirement, requirement];\n\n const boundaries = [\n ...new Set(\n input.authorization.evidence.proofs.flatMap((proof) =>\n requirements.map(\n (r) => DateTime.toEpochMillis(proof.verifiedAt) + r.maximumAgeMillis,\n ),\n ),\n ),\n ]\n .filter((time) => time > current.now)\n .sort((a, b) => a - b);\n\n const deadline = boundaries.find((time) =>\n requirements.some(\n (r) => !passwordEvidenceSatisfiedAt(input.authorization.evidence, r, time),\n ),\n );\n\n ensure(deadline !== undefined);\n const freshness = sql`${now} >= ${current.now} and ${now} < ${deadline}`;\n\n const factorCondition = (revision: AuthenticationRevision) =>\n sql.and([\n sql`(select count(*) from ${authority.name} where ${id(authority, a.subjectId, native)} and ${activeAuthority}) = ${revision.credentials.length}`,\n ...revision.credentials.map(\n (f) =>\n sql`exists(select 1 from ${authority.name} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, f.credentialId)} and ${exact(authority, a.revision, f.revision)} and ${activeAuthority})`,\n ),\n ]);\n\n if (batch !== undefined) {\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql.and([\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, current.revision.securityRevision)} and ${activeSubject})`,\n factorCondition(current.revision),\n freshness,\n ]),\n ),\n );\n if (current.targetIdentifier !== undefined) {\n ensure(i.d1MutableTargetCondition !== undefined);\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${tables.expression(i.d1MutableTargetCondition({ identifier: input.target, nativeSubjectId: native }))})`,\n ),\n );\n }\n if (current.target.source !== undefined) {\n ensure(i.d1CurrentCondition !== undefined);\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(current.target.source.identifier)} and ${id(identifier, i.subjectId, native)} and ${tables.expression(i.d1CurrentCondition({ identifier: current.target.source.identifier, nativeSubjectId: native, bindingRevision: current.target.source.identifierRevision }))})`,\n ),\n );\n }\n }\n\n const proof = yield* makeNativeProofStore(tables, proofs, batch !== undefined),\n redeemed = yield* proof.redeemLocked(redemption);\n\n yield* registerSqlCommitReceipt(\n original.redemption.prepare(\n redeemed.decision,\n yield* CurrentCommitJournal,\n (value) => value,\n ),\n );\n if (redeemed.decision !== \"redeemed\") return yield* prepare(\"rejected\", project);\n\n const allocate = () =>\n allocateEmailValue(mode, mapping.allocateRevision, mapping.allocateRevisionSync);\n\n const targetIdentifierRevision = yield* allocate(),\n targetCredentialRevision = yield* allocate(),\n sourceIdentifierRevision = yield* allocate(),\n sourceCredentialRevision = yield* allocate();\n\n const confirms =\n action === \"verify-address\" && current.target.targetIdentifierRevision !== undefined;\n\n const nextSecurityRevision = confirms\n ? current.revision.securityRevision\n : yield* allocateEmailSecurityRevision(mapping, mode, current.revision.securityRevision);\n\n ensure(confirms || nextSecurityRevision !== current.revision.securityRevision);\n\n const targetCredentialId =\n current.targetCredential === undefined\n ? yield* allocateEmailValue(\n mode,\n mapping.allocateCredentialId,\n mapping.allocateCredentialIdSync,\n )\n : yield* Schema.decodeUnknownEffect(Schema.String)(\n current.targetCredential[c.credentialId],\n );\n\n const source = current.target.source;\n\n if (action === \"change-address\") {\n ensure(source !== undefined);\n yield* stage(\n sql`${identifier.update(i.encodeRetirement({ source: source.identifier, bindingRevision: sourceIdentifierRevision }))} where ${identifierKey(source.identifier)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, source.identifierRevision)}`,\n );\n yield* stage(\n sql`${credential.update(c.encodeRetirement({ source: source.identifier, credentialRevision: sourceCredentialRevision }))} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, source.credentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, source.credentialRevision)} and ${activeCredential}`,\n );\n yield* stage(\n sql`${authority.update(a.encodeRetirement(sourceCredentialRevision))} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, source.credentialId)} and ${exact(authority, a.revision, source.credentialRevision)} and ${activeAuthority}`,\n );\n }\n if (current.targetIdentifier === undefined)\n yield* stage(\n identifier.insert(\n i.encodeVerifiedInsert({\n identifier: input.target,\n subjectId: native,\n verifiedAtMillis: current.now,\n bindingRevision: targetIdentifierRevision,\n }),\n ),\n );\n else\n yield* stage(\n sql`${identifier.update(i.encodeVerification({ verifiedAtMillis: current.now, bindingRevision: targetIdentifierRevision }))} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, current.targetIdentifier[i.bindingRevision])} and ${identifier.column(i.verifiedAt)} is null`,\n );\n if (current.targetCredential === undefined) {\n yield* stage(\n credential.insert(\n c.encodeVerifiedInsert({\n moduleId: input.moduleId,\n subjectId: native,\n credentialId: targetCredentialId,\n identifier: input.target,\n credentialRevision: targetCredentialRevision,\n }),\n ),\n );\n yield* stage(\n authority.insert(\n a.encodeInsert({\n subjectId: native,\n credentialId: targetCredentialId,\n revision: targetCredentialRevision,\n }),\n ),\n );\n } else {\n const old = current.targetCredential[c.credentialRevision];\n\n yield* stage(\n sql`${credential.update(c.encodeActivation({ identifier: input.target, credentialRevision: targetCredentialRevision }))} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, targetCredentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, old)}`,\n );\n yield* stage(\n sql`${authority.update(a.encodeActivation(targetCredentialRevision))} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, targetCredentialId)} and ${exact(authority, a.revision, old)}`,\n );\n }\n if (!confirms)\n yield* stage(\n sql`${subject.update({ [s.securityRevision]: nextSecurityRevision })} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, current.revision.securityRevision)} and ${activeSubject} and ${freshness}`,\n );\n\n const revision: AuthenticationRevision = {\n subjectId: current.revision.subjectId,\n securityRevision: nextSecurityRevision,\n credentials: [\n ...current.revision.credentials.filter(\n (f) =>\n f.credentialId !== targetCredentialId && f.credentialId !== source?.credentialId,\n ),\n { credentialId: targetCredentialId, revision: targetCredentialRevision },\n ].sort((a, b) => a.credentialId.localeCompare(b.credentialId)),\n };\n\n if (batch !== undefined) {\n ensure(i.d1CurrentCondition !== undefined);\n\n const conditions = [\n redeemed.validUntil,\n freshness,\n factorCondition(revision),\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, nextSecurityRevision)} and ${activeSubject})`,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${tables.expression(i.d1CurrentCondition({ identifier: input.target, nativeSubjectId: native, bindingRevision: targetIdentifierRevision }))})`,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, targetIdentifierRevision)} and ${id(identifier, i.verifiedAt, mapping.clock.encodeInstant(current.now))})`,\n sql`exists(select 1 from ${credential.name} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, targetCredentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, targetCredentialRevision)} and ${exact(credential, c.identifierNamespace, input.target.namespace)} and ${exact(credential, c.identifierValue, input.target.value)} and ${activeCredential})`,\n ];\n\n if (source !== undefined)\n conditions.push(\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(source.identifier)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, sourceIdentifierRevision)} and not (${tables.expression(i.d1CurrentCondition({ identifier: source.identifier, nativeSubjectId: native, bindingRevision: sourceIdentifierRevision }))}))`,\n sql`exists(select 1 from ${credential.name} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, source.credentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, sourceCredentialRevision)} and not (${activeCredential}))`,\n sql`exists(select 1 from ${authority.name} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, source.credentialId)} and ${exact(authority, a.revision, sourceCredentialRevision)} and not (${activeAuthority}))`,\n );\n yield* registerSqlBatchPostcondition({\n name: \"email-address-authority\",\n statement: sqlBatchAssertion(sql, sql.and(conditions)),\n });\n } else\n yield* registerSqlPostcondition({\n name: \"email-address-authority\",\n check: Effect.gen(function* () {\n const final = yield* reader.read({\n validity: redeemed.validUntil,\n moduleId: input.moduleId,\n subjectId: revision.subjectId,\n identifier: input.target,\n ...(source === undefined\n ? {}\n : {\n sourceCredentialId: source.credentialId,\n sourceIdentifier: source.identifier,\n }),\n });\n\n ensure(\n final !== undefined &&\n sameEmailRevision(final.revision, revision) &&\n final.now >= current.now &&\n final.now < deadline &&\n final.targetSnapshot !== undefined &&\n final.targetSnapshot.credentialId === targetCredentialId &&\n final.targetSnapshot.credentialRevision === targetCredentialRevision &&\n final.targetSnapshot.identifierRevision === targetIdentifierRevision &&\n (yield* mapping.decodeInstant(final.targetIdentifier![i.verifiedAt])) ===\n current.now,\n );\n const policy = yield* s.decodeActionRequirement(final.subject, action);\n\n ensure(passwordEvidenceSatisfiedAt(input.authorization.evidence, policy, final.now));\n if (source !== undefined)\n ensure(\n final.sourceIdentifier !== undefined &&\n final.sourceCredential !== undefined &&\n final.sourceAuthority !== undefined &&\n (yield* mapping.subjectId.toSubject(final.sourceAuthority[a.subjectId])) ===\n revision.subjectId &&\n final.sourceAuthority[a.credentialId] === source.credentialId &&\n final.sourceAuthority[a.revision] === sourceCredentialRevision &&\n !a.isActiveStatus(final.sourceAuthority[a.status]) &&\n (yield* mapping.subjectId.toSubject(final.sourceIdentifier[i.subjectId])) ===\n revision.subjectId &&\n final.sourceIdentifier[i.bindingRevision] === sourceIdentifierRevision &&\n !i.isCurrent(final.sourceIdentifier) &&\n final.sourceCredential[c.credentialRevision] === sourceCredentialRevision &&\n !c.isActiveStatus(final.sourceCredential[c.status]),\n );\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepare(\"changed\", project);\n }),\n );\n\n return {\n emailAddressPersistence: E.EmailAddressPersistence.of({\n target: (input) =>\n executor.read(\n Effect.flatMap(target(input), (value) =>\n value === undefined ? Effect.fail(unavailable()) : Effect.succeed(value.target),\n ),\n ),\n verifyWithProof: (input, project) => mutate(input, \"verify-address\", project),\n changeWithProof: (input, project) => mutate(input, \"change-address\", project),\n }),\n };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"email-native.mjs","names":["unavailable"],"sources":["../../src/internal/email-native.ts"],"sourcesContent":["import * as E from \"@yielded/auth/Email\";\nimport { CurrentCommitJournal, type LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { ProofRedemptionInput } from \"@yielded/auth/Proofs\";\nimport type { AuthenticationRevision } from \"@yielded/auth/Sessions\";\nimport { SecurityRevision } from \"@yielded/auth/Sessions\";\nimport { Crypto, DateTime, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\nimport type { Fragment } from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport {\n makeNativeEmailReader,\n emailUnavailable as unavailable,\n ensureEmail as ensure,\n} from \"./email-native-state\";\nimport {\n allocateEmailValue,\n allocateEmailSecurityRevision,\n snapshotEmailMutation,\n validateEmailMutation,\n sameEmailRevision,\n} from \"./email-policy\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyEmailAddressMapping, AnyEmailSignInMapping } from \"./models/email-model\";\nimport type { AnyProofPersistenceMapping } from \"./models/proof-model\";\nimport type { NativeSqlTables, SqlTable } from \"./native-sql-table\";\nimport { passwordEvidenceSatisfiedAt } from \"./password-policy\";\nimport { makeNativeProofStore } from \"./proof-native\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n appendSqlBatchStatement,\n makeSqlCommitExecutor,\n registerSqlBatchPostcondition,\n registerSqlCommitReceipt,\n registerSqlPostcondition,\n SqlBatchCommit,\n} from \"./sql-commit\";\n\nexport const makeNativeEmailSignInServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: AnyEmailSignInMapping,\n): Effect.fn.Return<\n { readonly emailSignInTargets: E.EmailSignInTargets[\"Service\"] },\n never,\n SqlClient | LifecycleHooks\n> {\n const executor = yield* makeSqlCommitExecutor(unavailable);\n const reader = yield* makeNativeEmailReader(tables, mapping);\n\n return {\n emailSignInTargets: E.EmailSignInTargets.of({\n lookup: (input) => executor.read(reader.lookup(input)),\n }),\n };\n});\n\nexport const makeNativeEmailAddressServices = Effect.fnUntraced(function* (\n tables: NativeSqlTables,\n mapping: AnyEmailAddressMapping,\n proofs?: AnyProofPersistenceMapping,\n): Effect.fn.Return<\n { readonly emailAddressPersistence: E.EmailAddressPersistence[\"Service\"] },\n never,\n SqlClient | LifecycleHooks | Crypto.Crypto | SqlBatchCommit\n> {\n const batch = yield* SqlBatchCommit;\n\n const sql = (yield* SqlClient).withoutTransforms(),\n executor = yield* makeSqlCommitExecutor(unavailable),\n crypto = yield* Crypto.Crypto;\n\n const now = tables.expression(mapping.clock.engineNowMillis);\n const reader = yield* makeNativeEmailReader(tables, mapping, now);\n\n const s = mapping.subject,\n i = mapping.identifier,\n c = mapping.credential,\n a = mapping.authorityCredential;\n\n const subject = tables(s.table),\n identifier = tables(i.table),\n credential = tables(c.table),\n authority = tables(a.table);\n\n const exact = (t: SqlTable, key: string, value: unknown) =>\n exactSqlText(sql, t.column(key), t.value(key, value));\n\n const id = (t: SqlTable, key: string, value: unknown) =>\n sql`${t.column(key)} = ${t.value(key, value)}`;\n\n const identifierKey = (value: { readonly namespace: string; readonly value: string }) =>\n sql.and([\n exact(identifier, i.namespace, value.namespace),\n exact(identifier, i.value, value.value),\n ]);\n\n const activeSubject = id(subject, s.status, s.activeStatusValue),\n activeCredential = id(credential, c.status, c.activeStatusValue),\n activeAuthority = id(authority, a.status, a.activeStatusValue);\n\n const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });\n const mode = \"interactive\";\n\n const run = <A, X, R>(work: Effect.Effect<A, X, R>) =>\n batch === undefined\n ? executor.run(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n : executor\n .batch(work.pipe(Effect.provideService(Crypto.Crypto, crypto)))\n .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n const stage = Effect.fnUntraced(function* (statement: Fragment) {\n if (batch === undefined) ensure((yield* executeSqlChange(sql, statement)) === 1);\n else {\n yield* appendSqlBatchStatement(sql`${statement}`);\n yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = 1`));\n }\n });\n\n const prepare = <A>(\n decision: E.EmailAddressDecision,\n project: E.PrepareEmailCommit<E.EmailAddressDecision, A>,\n ) =>\n Effect.gen(function* () {\n const receipt = project(decision, yield* CurrentCommitJournal);\n\n yield* registerSqlCommitReceipt(receipt);\n\n return receipt;\n });\n\n const target = Effect.fnUntraced(function* (\n input: Parameters<E.EmailAddressPersistence[\"Service\"][\"target\"]>[0],\n sourceIdentifier?: { readonly namespace: string; readonly value: string },\n ) {\n const state = yield* reader.read({\n moduleId: input.moduleId,\n subjectId: input.subjectId,\n identifier: input.target,\n ...(input.sourceCredentialId === undefined\n ? {}\n : { sourceCredentialId: input.sourceCredentialId }),\n ...(sourceIdentifier === undefined ? {} : { sourceIdentifier }),\n single: mapping.addressCardinality === \"single\",\n });\n\n if (state === undefined) return undefined;\n\n const owned =\n state.targetIdentifier !== undefined &&\n (yield* mapping.subjectId.toSubject(state.targetIdentifier[i.subjectId])) === input.subjectId;\n\n const mutable =\n owned &&\n mapping.identifier.isMutableTarget(state.targetIdentifier!) &&\n (state.targetCredential === undefined ||\n (!c.isActiveStatus(state.targetCredential[c.status]) &&\n (yield* mapping.subjectId.toSubject(state.targetCredential[c.subjectId])) ===\n input.subjectId));\n\n const source = state.sourceSnapshot;\n\n const eligible =\n (state.targetIdentifier === undefined ? state.targetCredential === undefined : mutable) &&\n (input.sourceCredentialId === undefined || source !== undefined) &&\n (source === undefined ||\n source.identifier.namespace !== input.target.namespace ||\n source.identifier.value !== input.target.value) &&\n !state.cardinalityConflict;\n\n const captured: E.EmailAddressTarget = {\n revision: state.revision,\n eligible,\n ...(source === undefined ? {} : { source }),\n ...(mutable\n ? {\n targetIdentifierRevision: yield* Schema.decodeUnknownEffect(SecurityRevision)(\n state.targetIdentifier![i.bindingRevision],\n ),\n }\n : {}),\n };\n\n return { ...state, target: captured };\n });\n\n const mutate = <A>(\n original: E.EmailAddressMutation,\n action: E.EmailAction,\n project: E.PrepareEmailCommit<E.EmailAddressDecision, A>,\n ) =>\n run(\n Effect.gen(function* () {\n const input = yield* snapshotEmailMutation(original);\n\n const redemption = yield* Schema.decodeEffect(Schema.toCodecIso(ProofRedemptionInput))(\n yield* Schema.encodeEffect(Schema.toCodecIso(ProofRedemptionInput))(\n original.redemption.input,\n ),\n );\n\n const native = yield* mapping.subjectId.toNative(input.captured.revision.subjectId);\n\n const locked =\n yield* sql`select ${subject.fields(\"email_locked_\")} from ${subject.name} where ${id(subject, s.id, native)} limit 2 ${batch === undefined ? lock : sql``}`;\n\n if (locked.length !== 1) return yield* prepare(\"rejected\", project);\n ensure(\n (yield* mapping.subjectId.toSubject(\n subject.decode(locked[0]!, \"email_locked_\")[s.id],\n )) === input.captured.revision.subjectId,\n );\n\n const current = yield* target(\n {\n moduleId: input.moduleId,\n subjectId: input.captured.revision.subjectId,\n target: input.target,\n ...(input.captured.source === undefined\n ? {}\n : { sourceCredentialId: input.captured.source.credentialId }),\n },\n input.captured.source?.identifier,\n );\n\n if (current === undefined || proofs === undefined)\n return yield* prepare(\"rejected\", project);\n const requirement = yield* s.decodeActionRequirement(current.subject, action);\n\n if (\n !(yield* validateEmailMutation(\n mapping,\n { ...input, redemption: { input: redemption, prepare: original.redemption.prepare } },\n action,\n { target: current.target, requirement: Effect.succeed(requirement) },\n current.now,\n ))\n )\n return yield* prepare(\"rejected\", project);\n const requirements = [input.authorization.requirement, requirement];\n\n const boundaries = [\n ...new Set(\n input.authorization.evidence.proofs.flatMap((proof) =>\n requirements.map(\n (r) => DateTime.toEpochMillis(proof.verifiedAt) + r.maximumAgeMillis,\n ),\n ),\n ),\n ]\n .filter((time) => time > current.now)\n .sort((a, b) => a - b);\n\n let deadline: number | undefined;\n\n for (const time of boundaries) {\n for (const requirement of requirements) {\n if (\n !(yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, requirement, time))\n ) {\n deadline = time;\n break;\n }\n }\n if (deadline !== undefined) break;\n }\n\n ensure(deadline !== undefined);\n const freshness = sql`${now} >= ${current.now} and ${now} < ${deadline}`;\n\n const factorCondition = (revision: AuthenticationRevision) =>\n sql.and([\n sql`(select count(*) from ${authority.name} where ${id(authority, a.subjectId, native)} and ${activeAuthority}) = ${revision.credentials.length}`,\n ...revision.credentials.map(\n (f) =>\n sql`exists(select 1 from ${authority.name} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, f.credentialId)} and ${exact(authority, a.revision, f.revision)} and ${activeAuthority})`,\n ),\n ]);\n\n if (batch !== undefined) {\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql.and([\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, current.revision.securityRevision)} and ${activeSubject})`,\n factorCondition(current.revision),\n freshness,\n ]),\n ),\n );\n if (current.targetIdentifier !== undefined) {\n ensure(i.d1MutableTargetCondition !== undefined);\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${tables.expression(i.d1MutableTargetCondition({ identifier: input.target, nativeSubjectId: native }))})`,\n ),\n );\n }\n if (current.target.source !== undefined) {\n ensure(i.d1CurrentCondition !== undefined);\n yield* appendSqlBatchStatement(\n sqlBatchAssertion(\n sql,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(current.target.source.identifier)} and ${id(identifier, i.subjectId, native)} and ${tables.expression(i.d1CurrentCondition({ identifier: current.target.source.identifier, nativeSubjectId: native, bindingRevision: current.target.source.identifierRevision }))})`,\n ),\n );\n }\n }\n\n const proof = yield* makeNativeProofStore(tables, proofs, batch !== undefined),\n redeemed = yield* proof.redeemLocked(redemption);\n\n yield* registerSqlCommitReceipt(\n original.redemption.prepare(\n redeemed.decision,\n yield* CurrentCommitJournal,\n (value) => value,\n ),\n );\n if (redeemed.decision !== \"redeemed\") return yield* prepare(\"rejected\", project);\n\n const allocate = () =>\n allocateEmailValue(mode, mapping.allocateRevision, mapping.allocateRevisionSync);\n\n const targetIdentifierRevision = yield* allocate(),\n targetCredentialRevision = yield* allocate(),\n sourceIdentifierRevision = yield* allocate(),\n sourceCredentialRevision = yield* allocate();\n\n const confirms =\n action === \"verify-address\" && current.target.targetIdentifierRevision !== undefined;\n\n const nextSecurityRevision = confirms\n ? current.revision.securityRevision\n : yield* allocateEmailSecurityRevision(mapping, mode, current.revision.securityRevision);\n\n ensure(confirms || nextSecurityRevision !== current.revision.securityRevision);\n\n const targetCredentialId =\n current.targetCredential === undefined\n ? yield* allocateEmailValue(\n mode,\n mapping.allocateCredentialId,\n mapping.allocateCredentialIdSync,\n )\n : yield* Schema.decodeUnknownEffect(Schema.String)(\n current.targetCredential[c.credentialId],\n );\n\n const source = current.target.source;\n\n if (action === \"change-address\") {\n ensure(source !== undefined);\n yield* stage(\n sql`${identifier.update(i.encodeRetirement({ source: source.identifier, bindingRevision: sourceIdentifierRevision }))} where ${identifierKey(source.identifier)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, source.identifierRevision)}`,\n );\n yield* stage(\n sql`${credential.update(c.encodeRetirement({ source: source.identifier, credentialRevision: sourceCredentialRevision }))} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, source.credentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, source.credentialRevision)} and ${activeCredential}`,\n );\n yield* stage(\n sql`${authority.update(a.encodeRetirement(sourceCredentialRevision))} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, source.credentialId)} and ${exact(authority, a.revision, source.credentialRevision)} and ${activeAuthority}`,\n );\n }\n if (current.targetIdentifier === undefined)\n yield* stage(\n identifier.insert(\n i.encodeVerifiedInsert({\n identifier: input.target,\n subjectId: native,\n verifiedAtMillis: current.now,\n bindingRevision: targetIdentifierRevision,\n }),\n ),\n );\n else\n yield* stage(\n sql`${identifier.update(i.encodeVerification({ verifiedAtMillis: current.now, bindingRevision: targetIdentifierRevision }))} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, current.targetIdentifier[i.bindingRevision])} and ${identifier.column(i.verifiedAt)} is null`,\n );\n if (current.targetCredential === undefined) {\n yield* stage(\n credential.insert(\n c.encodeVerifiedInsert({\n moduleId: input.moduleId,\n subjectId: native,\n credentialId: targetCredentialId,\n identifier: input.target,\n credentialRevision: targetCredentialRevision,\n }),\n ),\n );\n yield* stage(\n authority.insert(\n a.encodeInsert({\n subjectId: native,\n credentialId: targetCredentialId,\n revision: targetCredentialRevision,\n }),\n ),\n );\n } else {\n const old = current.targetCredential[c.credentialRevision];\n\n yield* stage(\n sql`${credential.update(c.encodeActivation({ identifier: input.target, credentialRevision: targetCredentialRevision }))} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, targetCredentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, old)}`,\n );\n yield* stage(\n sql`${authority.update(a.encodeActivation(targetCredentialRevision))} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, targetCredentialId)} and ${exact(authority, a.revision, old)}`,\n );\n }\n if (!confirms)\n yield* stage(\n sql`${subject.update({ [s.securityRevision]: nextSecurityRevision })} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, current.revision.securityRevision)} and ${activeSubject} and ${freshness}`,\n );\n\n const revision: AuthenticationRevision = {\n subjectId: current.revision.subjectId,\n securityRevision: nextSecurityRevision,\n credentials: [\n ...current.revision.credentials.filter(\n (f) =>\n f.credentialId !== targetCredentialId && f.credentialId !== source?.credentialId,\n ),\n { credentialId: targetCredentialId, revision: targetCredentialRevision },\n ].sort((a, b) => a.credentialId.localeCompare(b.credentialId)),\n };\n\n if (batch !== undefined) {\n ensure(i.d1CurrentCondition !== undefined);\n\n const conditions = [\n redeemed.validUntil,\n freshness,\n factorCondition(revision),\n sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, native)} and ${exact(subject, s.securityRevision, nextSecurityRevision)} and ${activeSubject})`,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${tables.expression(i.d1CurrentCondition({ identifier: input.target, nativeSubjectId: native, bindingRevision: targetIdentifierRevision }))})`,\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(input.target)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, targetIdentifierRevision)} and ${id(identifier, i.verifiedAt, mapping.clock.encodeInstant(current.now))})`,\n sql`exists(select 1 from ${credential.name} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, targetCredentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, targetCredentialRevision)} and ${exact(credential, c.identifierNamespace, input.target.namespace)} and ${exact(credential, c.identifierValue, input.target.value)} and ${activeCredential})`,\n ];\n\n if (source !== undefined)\n conditions.push(\n sql`exists(select 1 from ${identifier.name} where ${identifierKey(source.identifier)} and ${id(identifier, i.subjectId, native)} and ${exact(identifier, i.bindingRevision, sourceIdentifierRevision)} and not (${tables.expression(i.d1CurrentCondition({ identifier: source.identifier, nativeSubjectId: native, bindingRevision: sourceIdentifierRevision }))}))`,\n sql`exists(select 1 from ${credential.name} where ${exact(credential, c.moduleId, input.moduleId)} and ${exact(credential, c.credentialId, source.credentialId)} and ${id(credential, c.subjectId, native)} and ${exact(credential, c.credentialRevision, sourceCredentialRevision)} and not (${activeCredential}))`,\n sql`exists(select 1 from ${authority.name} where ${id(authority, a.subjectId, native)} and ${exact(authority, a.credentialId, source.credentialId)} and ${exact(authority, a.revision, sourceCredentialRevision)} and not (${activeAuthority}))`,\n );\n yield* registerSqlBatchPostcondition({\n name: \"email-address-authority\",\n statement: sqlBatchAssertion(sql, sql.and(conditions)),\n });\n } else\n yield* registerSqlPostcondition({\n name: \"email-address-authority\",\n check: Effect.gen(function* () {\n const final = yield* reader.read({\n validity: redeemed.validUntil,\n moduleId: input.moduleId,\n subjectId: revision.subjectId,\n identifier: input.target,\n ...(source === undefined\n ? {}\n : {\n sourceCredentialId: source.credentialId,\n sourceIdentifier: source.identifier,\n }),\n });\n\n ensure(\n final !== undefined &&\n sameEmailRevision(final.revision, revision) &&\n final.now >= current.now &&\n final.now < deadline &&\n final.targetSnapshot !== undefined &&\n final.targetSnapshot.credentialId === targetCredentialId &&\n final.targetSnapshot.credentialRevision === targetCredentialRevision &&\n final.targetSnapshot.identifierRevision === targetIdentifierRevision &&\n (yield* mapping.decodeInstant(final.targetIdentifier![i.verifiedAt])) ===\n current.now,\n );\n const policy = yield* s.decodeActionRequirement(final.subject, action);\n\n ensure(\n yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, policy, final.now),\n );\n if (source !== undefined)\n ensure(\n final.sourceIdentifier !== undefined &&\n final.sourceCredential !== undefined &&\n final.sourceAuthority !== undefined &&\n (yield* mapping.subjectId.toSubject(final.sourceAuthority[a.subjectId])) ===\n revision.subjectId &&\n final.sourceAuthority[a.credentialId] === source.credentialId &&\n final.sourceAuthority[a.revision] === sourceCredentialRevision &&\n !a.isActiveStatus(final.sourceAuthority[a.status]) &&\n (yield* mapping.subjectId.toSubject(final.sourceIdentifier[i.subjectId])) ===\n revision.subjectId &&\n final.sourceIdentifier[i.bindingRevision] === sourceIdentifierRevision &&\n !i.isCurrent(final.sourceIdentifier) &&\n final.sourceCredential[c.credentialRevision] === sourceCredentialRevision &&\n !c.isActiveStatus(final.sourceCredential[c.status]),\n );\n }).pipe(\n Effect.mapError((cause) =>\n PersistenceMappingError.make({ operation: \"decode\", cause }),\n ),\n ),\n });\n\n return yield* prepare(\"changed\", project);\n }),\n );\n\n return {\n emailAddressPersistence: E.EmailAddressPersistence.of({\n target: (input) =>\n executor.read(\n Effect.flatMap(target(input), (value) =>\n value === undefined ? Effect.fail(unavailable()) : Effect.succeed(value.target),\n ),\n ),\n verifyWithProof: (input, project) => mutate(input, \"verify-address\", project),\n changeWithProof: (input, project) => mutate(input, \"change-address\", project),\n }),\n };\n});\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
2
2
|
import { passwordEvidenceSatisfiedAt } from "./password-policy.mjs";
|
|
3
|
-
import { Effect } from "effect";
|
|
3
|
+
import { DateTime, Effect } from "effect";
|
|
4
4
|
import { EmailUnavailable, snapshotEmailCredential, snapshotEmailRequirement, snapshotEmailRevision } from "@yielded/auth/Email";
|
|
5
5
|
import { snapshotAuthenticationEvidence } from "@yielded/auth/Sessions";
|
|
6
6
|
//#region src/internal/email-policy.ts
|
|
@@ -49,7 +49,8 @@ const validateEmailMutation = Effect.fn("EmailAddressPersistence.validateAuthori
|
|
|
49
49
|
if (!input.captured.eligible || !emailRedemptionMatches(input, action) || input.authorization.challenge.moduleId !== input.moduleId || input.authorization.challenge.action !== action || input.authorization.challenge.commandId !== input.commandId || !sameIdentifier(input.authorization.challenge.target, input.target) || input.authorization.challenge.sourceCredentialId !== input.captured.source?.credentialId || !sameEmailRevision(input.authorization.challenge.revision, input.captured.revision) || input.authorization.challenge.targetIdentifierRevision !== input.captured.targetIdentifierRevision || input.authorization.evidence.flowId !== input.commandId || input.authorization.evidence.bindingDigest !== input.authorization.challenge.bindingDigest || !sameEmailRevision(input.authorization.evidence.revision, input.captured.revision) || action === "verify-address" && input.captured.source !== void 0 || action === "change-address" && input.captured.source === void 0 || confirmsExisting !== (input.invalidation === void 0) || input.invalidation?.existingSessions === "immediate" && policy.sessionInvalidation !== "same-authority-immediate") return false;
|
|
50
50
|
if (current === void 0 || !current.target.eligible || !sameEmailRevision(current.target.revision, input.captured.revision) || current.target.targetIdentifierRevision !== input.captured.targetIdentifierRevision || input.captured.source !== void 0 && (current.target.source === void 0 || current.target.source.identifierRevision !== input.captured.source.identifierRevision || current.target.source.credentialRevision !== input.captured.source.credentialRevision || !sameIdentifier(current.target.source.identifier, input.captured.source.identifier))) return false;
|
|
51
51
|
const currentRequirement = yield* current.requirement;
|
|
52
|
-
|
|
52
|
+
if (input.authorization.evidence.proofs.some((proof) => DateTime.toEpochMillis(proof.verifiedAt) > now)) return false;
|
|
53
|
+
return (yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, input.authorization.requirement, now)) && (yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, currentRequirement, now));
|
|
53
54
|
});
|
|
54
55
|
const allocateEmailValue = (mode, asynchronous, synchronous) => {
|
|
55
56
|
if (mode === "synchronous") return synchronous === void 0 ? Effect.fail(unavailable()) : Effect.try({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-policy.mjs","names":[],"sources":["../../src/internal/email-policy.ts"],"sourcesContent":["import {\n type EmailAddressMutation,\n type EmailAddressTarget,\n type EmailAction,\n EmailUnavailable,\n snapshotEmailCredential,\n snapshotEmailRequirement,\n snapshotEmailRevision,\n} from \"@yielded/auth/Email\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n snapshotAuthenticationEvidence,\n type AuthenticationRevision,\n type AuthenticationRequirement,\n type SecurityRevision,\n} from \"@yielded/auth/Sessions\";\nimport { Effect } from \"effect\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyEmailAddressMapping } from \"./models/email-model\";\nimport { passwordEvidenceSatisfiedAt } from \"./password-policy\";\n\nconst unavailable = () => EmailUnavailable.make({});\n\nconst sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>\n left.namespace === right.namespace && left.value === right.value;\n\nexport type EmailWorkflowPolicy = Pick<\n AnyEmailAddressMapping,\n | \"allocateCredentialId\"\n | \"allocateCredentialIdSync\"\n | \"allocateRevision\"\n | \"allocateRevisionSync\"\n | \"sessionInvalidation\"\n | \"isIdentifierConflict\"\n | \"isCredentialConflict\"\n> & {\n readonly subject: Pick<\n AnyEmailAddressMapping[\"subject\"],\n \"nextSecurityRevision\" | \"nextSecurityRevisionSync\"\n >;\n};\n\nexport const sameEmailRevision = (left: AuthenticationRevision, right: AuthenticationRevision) => {\n if (\n left.subjectId !== right.subjectId ||\n left.securityRevision !== right.securityRevision ||\n left.credentials.length !== right.credentials.length\n )\n return false;\n\n const expected = [...left.credentials].sort((a, b) =>\n a.credentialId.localeCompare(b.credentialId),\n );\n\n const actual = [...right.credentials].sort((a, b) =>\n a.credentialId.localeCompare(b.credentialId),\n );\n\n return expected.every(\n (item, index) =>\n item.credentialId === actual[index]!.credentialId &&\n item.revision === actual[index]!.revision,\n );\n};\n\nexport const emailActionModule = (moduleId: string, action: EmailAction) =>\n `${moduleId}/${action === \"verify-address\" ? \"verify-address\" : \"change-address\"}`;\n\nexport const emailActionPurpose = (action: EmailAction) =>\n action === \"verify-address\" ? \"email-address-verification\" : \"email-address-change\";\n\nexport const emailRedemptionMatches = (input: EmailAddressMutation, action: EmailAction) => {\n const completion = input.redemption.input;\n const binding = completion.binding;\n\n return (\n completion.moduleId === emailActionModule(input.moduleId, action) &&\n completion.purpose === emailActionPurpose(action) &&\n binding._tag === \"IdentifierChange\" &&\n sameIdentifier(binding.identifier, input.target) &&\n sameEmailRevision(binding.revision, input.captured.revision)\n );\n};\n\nexport const snapshotEmailMutation = Effect.fn(\"EmailAddressPersistence.snapshotMutation\")(\n function* (input: EmailAddressMutation) {\n const evidence = yield* snapshotAuthenticationEvidence(input.authorization.evidence).pipe(\n Effect.mapError(unavailable),\n );\n\n const requirement = yield* snapshotEmailRequirement(input.authorization.requirement);\n\n const source =\n input.captured.source === undefined\n ? undefined\n : yield* snapshotEmailCredential(input.captured.source);\n\n return Object.freeze({\n ...input,\n target: Object.freeze({ ...input.target }),\n ...(input.invalidation === undefined\n ? {}\n : { invalidation: Object.freeze({ ...input.invalidation }) }),\n captured: Object.freeze({\n eligible: input.captured.eligible,\n revision: snapshotEmailRevision(input.captured.revision),\n ...(input.captured.targetIdentifierRevision === undefined\n ? {}\n : { targetIdentifierRevision: input.captured.targetIdentifierRevision }),\n ...(source === undefined ? {} : { source }),\n }),\n authorization: Object.freeze({\n challenge: Object.freeze({\n ...input.authorization.challenge,\n target: Object.freeze({ ...input.authorization.challenge.target }),\n revision: snapshotEmailRevision(input.authorization.challenge.revision),\n }),\n evidence,\n requirement,\n }),\n });\n },\n);\n\nexport const validateEmailMutation = Effect.fn(\"EmailAddressPersistence.validateAuthority\")(\n function* (\n policy: EmailWorkflowPolicy,\n input: EmailAddressMutation,\n action: EmailAction,\n current: Pick<EmailMutationRead, \"target\" | \"requirement\"> | undefined,\n now: number,\n ) {\n const confirmsExisting =\n action === \"verify-address\" && input.captured.targetIdentifierRevision !== undefined;\n\n if (\n !input.captured.eligible ||\n !emailRedemptionMatches(input, action) ||\n input.authorization.challenge.moduleId !== input.moduleId ||\n input.authorization.challenge.action !== action ||\n input.authorization.challenge.commandId !== input.commandId ||\n !sameIdentifier(input.authorization.challenge.target, input.target) ||\n input.authorization.challenge.sourceCredentialId !== input.captured.source?.credentialId ||\n !sameEmailRevision(input.authorization.challenge.revision, input.captured.revision) ||\n input.authorization.challenge.targetIdentifierRevision !==\n input.captured.targetIdentifierRevision ||\n (input.authorization.evidence.flowId as string) !== (input.commandId as string) ||\n input.authorization.evidence.bindingDigest !== input.authorization.challenge.bindingDigest ||\n !sameEmailRevision(input.authorization.evidence.revision, input.captured.revision) ||\n (action === \"verify-address\" && input.captured.source !== undefined) ||\n (action === \"change-address\" && input.captured.source === undefined) ||\n confirmsExisting !== (input.invalidation === undefined) ||\n (input.invalidation?.existingSessions === \"immediate\" &&\n policy.sessionInvalidation !== \"same-authority-immediate\")\n )\n return false;\n\n if (\n current === undefined ||\n !current.target.eligible ||\n !sameEmailRevision(current.target.revision, input.captured.revision) ||\n current.target.targetIdentifierRevision !== input.captured.targetIdentifierRevision ||\n (input.captured.source !== undefined &&\n (current.target.source === undefined ||\n current.target.source.identifierRevision !== input.captured.source.identifierRevision ||\n current.target.source.credentialRevision !== input.captured.source.credentialRevision ||\n !sameIdentifier(current.target.source.identifier, input.captured.source.identifier)))\n )\n return false;\n\n const currentRequirement = yield* current.requirement;\n\n return (\n passwordEvidenceSatisfiedAt(\n input.authorization.evidence,\n input.authorization.requirement,\n now,\n ) && passwordEvidenceSatisfiedAt(input.authorization.evidence, currentRequirement, now)\n );\n },\n);\n\nexport const allocateEmailValue = <A>(\n mode: \"interactive\" | \"synchronous\",\n asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,\n synchronous: (() => A) | undefined,\n) => {\n if (mode === \"synchronous\")\n return synchronous === undefined\n ? Effect.fail(unavailable())\n : Effect.try({\n try: synchronous,\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n if (asynchronous !== undefined) return asynchronous;\n\n return synchronous === undefined\n ? Effect.fail(unavailable())\n : Effect.try({\n try: synchronous,\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n};\n\nexport const allocateEmailSecurityRevision = (\n mapping: EmailWorkflowPolicy,\n mode: \"interactive\" | \"synchronous\",\n current: SecurityRevision,\n) => {\n if (mode === \"synchronous\")\n return mapping.subject.nextSecurityRevisionSync === undefined\n ? Effect.fail(unavailable())\n : Effect.try({\n try: () => mapping.subject.nextSecurityRevisionSync!(current),\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n if (mapping.subject.nextSecurityRevision !== undefined)\n return mapping.subject.nextSecurityRevision(current);\n if (mapping.subject.nextSecurityRevisionSync !== undefined)\n return Effect.try({\n try: () => mapping.subject.nextSecurityRevisionSync!(current),\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n\n return Effect.fail(unavailable());\n};\n\nexport interface EmailMutationRead {\n readonly target: EmailAddressTarget;\n readonly requirement: Effect.Effect<\n AuthenticationRequirement,\n EmailUnavailable | PersistenceMappingError\n >;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"email-policy.mjs","names":[],"sources":["../../src/internal/email-policy.ts"],"sourcesContent":["import {\n type EmailAddressMutation,\n type EmailAddressTarget,\n type EmailAction,\n EmailUnavailable,\n snapshotEmailCredential,\n snapshotEmailRequirement,\n snapshotEmailRevision,\n} from \"@yielded/auth/Email\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n snapshotAuthenticationEvidence,\n type AuthenticationRevision,\n type AuthenticationRequirement,\n type SecurityRevision,\n} from \"@yielded/auth/Sessions\";\nimport { DateTime, Effect } from \"effect\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyEmailAddressMapping } from \"./models/email-model\";\nimport { passwordEvidenceSatisfiedAt } from \"./password-policy\";\n\nconst unavailable = () => EmailUnavailable.make({});\n\nconst sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>\n left.namespace === right.namespace && left.value === right.value;\n\nexport type EmailWorkflowPolicy = Pick<\n AnyEmailAddressMapping,\n | \"allocateCredentialId\"\n | \"allocateCredentialIdSync\"\n | \"allocateRevision\"\n | \"allocateRevisionSync\"\n | \"sessionInvalidation\"\n | \"isIdentifierConflict\"\n | \"isCredentialConflict\"\n> & {\n readonly subject: Pick<\n AnyEmailAddressMapping[\"subject\"],\n \"nextSecurityRevision\" | \"nextSecurityRevisionSync\"\n >;\n};\n\nexport const sameEmailRevision = (left: AuthenticationRevision, right: AuthenticationRevision) => {\n if (\n left.subjectId !== right.subjectId ||\n left.securityRevision !== right.securityRevision ||\n left.credentials.length !== right.credentials.length\n )\n return false;\n\n const expected = [...left.credentials].sort((a, b) =>\n a.credentialId.localeCompare(b.credentialId),\n );\n\n const actual = [...right.credentials].sort((a, b) =>\n a.credentialId.localeCompare(b.credentialId),\n );\n\n return expected.every(\n (item, index) =>\n item.credentialId === actual[index]!.credentialId &&\n item.revision === actual[index]!.revision,\n );\n};\n\nexport const emailActionModule = (moduleId: string, action: EmailAction) =>\n `${moduleId}/${action === \"verify-address\" ? \"verify-address\" : \"change-address\"}`;\n\nexport const emailActionPurpose = (action: EmailAction) =>\n action === \"verify-address\" ? \"email-address-verification\" : \"email-address-change\";\n\nexport const emailRedemptionMatches = (input: EmailAddressMutation, action: EmailAction) => {\n const completion = input.redemption.input;\n const binding = completion.binding;\n\n return (\n completion.moduleId === emailActionModule(input.moduleId, action) &&\n completion.purpose === emailActionPurpose(action) &&\n binding._tag === \"IdentifierChange\" &&\n sameIdentifier(binding.identifier, input.target) &&\n sameEmailRevision(binding.revision, input.captured.revision)\n );\n};\n\nexport const snapshotEmailMutation = Effect.fn(\"EmailAddressPersistence.snapshotMutation\")(\n function* (input: EmailAddressMutation) {\n const evidence = yield* snapshotAuthenticationEvidence(input.authorization.evidence).pipe(\n Effect.mapError(unavailable),\n );\n\n const requirement = yield* snapshotEmailRequirement(input.authorization.requirement);\n\n const source =\n input.captured.source === undefined\n ? undefined\n : yield* snapshotEmailCredential(input.captured.source);\n\n return Object.freeze({\n ...input,\n target: Object.freeze({ ...input.target }),\n ...(input.invalidation === undefined\n ? {}\n : { invalidation: Object.freeze({ ...input.invalidation }) }),\n captured: Object.freeze({\n eligible: input.captured.eligible,\n revision: snapshotEmailRevision(input.captured.revision),\n ...(input.captured.targetIdentifierRevision === undefined\n ? {}\n : { targetIdentifierRevision: input.captured.targetIdentifierRevision }),\n ...(source === undefined ? {} : { source }),\n }),\n authorization: Object.freeze({\n challenge: Object.freeze({\n ...input.authorization.challenge,\n target: Object.freeze({ ...input.authorization.challenge.target }),\n revision: snapshotEmailRevision(input.authorization.challenge.revision),\n }),\n evidence,\n requirement,\n }),\n });\n },\n);\n\nexport const validateEmailMutation = Effect.fn(\"EmailAddressPersistence.validateAuthority\")(\n function* (\n policy: EmailWorkflowPolicy,\n input: EmailAddressMutation,\n action: EmailAction,\n current: Pick<EmailMutationRead, \"target\" | \"requirement\"> | undefined,\n now: number,\n ) {\n const confirmsExisting =\n action === \"verify-address\" && input.captured.targetIdentifierRevision !== undefined;\n\n if (\n !input.captured.eligible ||\n !emailRedemptionMatches(input, action) ||\n input.authorization.challenge.moduleId !== input.moduleId ||\n input.authorization.challenge.action !== action ||\n input.authorization.challenge.commandId !== input.commandId ||\n !sameIdentifier(input.authorization.challenge.target, input.target) ||\n input.authorization.challenge.sourceCredentialId !== input.captured.source?.credentialId ||\n !sameEmailRevision(input.authorization.challenge.revision, input.captured.revision) ||\n input.authorization.challenge.targetIdentifierRevision !==\n input.captured.targetIdentifierRevision ||\n (input.authorization.evidence.flowId as string) !== (input.commandId as string) ||\n input.authorization.evidence.bindingDigest !== input.authorization.challenge.bindingDigest ||\n !sameEmailRevision(input.authorization.evidence.revision, input.captured.revision) ||\n (action === \"verify-address\" && input.captured.source !== undefined) ||\n (action === \"change-address\" && input.captured.source === undefined) ||\n confirmsExisting !== (input.invalidation === undefined) ||\n (input.invalidation?.existingSessions === \"immediate\" &&\n policy.sessionInvalidation !== \"same-authority-immediate\")\n )\n return false;\n\n if (\n current === undefined ||\n !current.target.eligible ||\n !sameEmailRevision(current.target.revision, input.captured.revision) ||\n current.target.targetIdentifierRevision !== input.captured.targetIdentifierRevision ||\n (input.captured.source !== undefined &&\n (current.target.source === undefined ||\n current.target.source.identifierRevision !== input.captured.source.identifierRevision ||\n current.target.source.credentialRevision !== input.captured.source.credentialRevision ||\n !sameIdentifier(current.target.source.identifier, input.captured.source.identifier)))\n )\n return false;\n\n const currentRequirement = yield* current.requirement;\n\n // Address custody retains strict future-proof rejection.\n if (\n input.authorization.evidence.proofs.some(\n (proof) => DateTime.toEpochMillis(proof.verifiedAt) > now,\n )\n )\n return false;\n\n return (\n (yield* passwordEvidenceSatisfiedAt(\n input.authorization.evidence,\n input.authorization.requirement,\n now,\n )) &&\n (yield* passwordEvidenceSatisfiedAt(input.authorization.evidence, currentRequirement, now))\n );\n },\n);\n\nexport const allocateEmailValue = <A>(\n mode: \"interactive\" | \"synchronous\",\n asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,\n synchronous: (() => A) | undefined,\n) => {\n if (mode === \"synchronous\")\n return synchronous === undefined\n ? Effect.fail(unavailable())\n : Effect.try({\n try: synchronous,\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n if (asynchronous !== undefined) return asynchronous;\n\n return synchronous === undefined\n ? Effect.fail(unavailable())\n : Effect.try({\n try: synchronous,\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n};\n\nexport const allocateEmailSecurityRevision = (\n mapping: EmailWorkflowPolicy,\n mode: \"interactive\" | \"synchronous\",\n current: SecurityRevision,\n) => {\n if (mode === \"synchronous\")\n return mapping.subject.nextSecurityRevisionSync === undefined\n ? Effect.fail(unavailable())\n : Effect.try({\n try: () => mapping.subject.nextSecurityRevisionSync!(current),\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n if (mapping.subject.nextSecurityRevision !== undefined)\n return mapping.subject.nextSecurityRevision(current);\n if (mapping.subject.nextSecurityRevisionSync !== undefined)\n return Effect.try({\n try: () => mapping.subject.nextSecurityRevisionSync!(current),\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n\n return Effect.fail(unavailable());\n};\n\nexport interface EmailMutationRead {\n readonly target: EmailAddressTarget;\n readonly requirement: Effect.Effect<\n AuthenticationRequirement,\n EmailUnavailable | PersistenceMappingError\n >;\n}\n"],"mappings":"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"}
|
|
@@ -3,10 +3,10 @@ import { PersistenceMappingError } from "../mapping-error.mjs";
|
|
|
3
3
|
import { SubjectIdCodec } from "./common.mjs";
|
|
4
4
|
import { Effect, Schema } from "effect";
|
|
5
5
|
import { PreparedCommit } from "@yielded/auth/Hooks";
|
|
6
|
+
import { OAuthAccountRevision, OAuthActionAuthorization, OAuthCleanupInput, OAuthCommandId, OAuthExternalIdentity, OAuthLinkedAccountsRead, OAuthRegistrationAccess, OAuthRegistrationDecision, OAuthRegistrationInspection, OAuthRegistrationIntent, OAuthUnavailable, PrepareOAuthCommit } from "@yielded/auth/OAuth";
|
|
6
7
|
import { CleanupResult } from "@yielded/auth/Persistence";
|
|
7
8
|
import { AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
|
|
8
9
|
import { TokenDigest } from "@yielded/auth/Schema";
|
|
9
|
-
import { OAuthAccountRevision, OAuthActionAuthorization, OAuthCleanupInput, OAuthCommandId, OAuthExternalIdentity, OAuthLinkedAccountsRead, OAuthRegistrationAccess, OAuthRegistrationDecision, OAuthRegistrationInspection, OAuthRegistrationIntent, OAuthUnavailable, PrepareOAuthCommit } from "@yielded/auth/OAuth";
|
|
10
10
|
declare namespace oauth_model_d_exports {
|
|
11
11
|
export { OAuthAccountsMapping, OAuthAction, OAuthAuthorityReadTable, OAuthAuthorityTable, OAuthCleanupDescriptor, OAuthCleanupTable, OAuthClock, OAuthCredentialReadTable, OAuthCredentialTable, OAuthD1Mapping, OAuthEligibilityDescriptor, OAuthEligibilityFact, OAuthEligibilityTable, OAuthFlowTable, OAuthOwnershipReadTable, OAuthOwnershipTable, OAuthRegistrationAuthority, OAuthRegistrationGuardDescriptor, OAuthRegistrationGuardTable, OAuthRegistrationIntentMapping, OAuthRegistrationIntentTable, OAuthRegistrationMapping, OAuthSignInMapping, OAuthSubjectReadTable, OAuthSubjectTable, oauthCleanupTable, oauthEligibilityTable, oauthRegistrationGuardTable, requiredOAuthRegistrationConstraints, requiredOAuthSignInConstraints };
|
|
12
12
|
}
|
|
@@ -30,7 +30,7 @@ export interface OAuthSubjectReadTable<S extends TableModel, Expression extends
|
|
|
30
30
|
readonly securityRevision: Column<S>;
|
|
31
31
|
readonly isActiveStatus: (value: unknown) => boolean;
|
|
32
32
|
readonly activeCondition: Expression;
|
|
33
|
-
readonly decodeAuthenticationRequirement: (row: S["select"]) => AuthenticationRequirement
|
|
33
|
+
readonly decodeAuthenticationRequirement: (row: S["select"]) => AuthenticationRequirement | Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;
|
|
34
34
|
}
|
|
35
35
|
export interface OAuthSubjectTable<S extends TableModel, Expression extends SqlExpression = SqlExpression> extends OAuthSubjectReadTable<S, Expression> {
|
|
36
36
|
readonly nextSecurityRevision: (current: SecurityRevision) => SecurityRevision;
|
|
@@ -303,6 +303,8 @@ export interface OAuthAccountsMapping<S extends TableModel, O extends TableModel
|
|
|
303
303
|
readonly allocateRevision: Effect.Effect<SecurityRevision, PersistenceMappingError>;
|
|
304
304
|
}
|
|
305
305
|
export interface OAuthRegistrationAuthority<Registration> {
|
|
306
|
+
/** Only the first confirmed registration supplies its private authentication handoff. */
|
|
307
|
+
readonly authentication?: "first-confirmed-registration";
|
|
306
308
|
readonly read: (access: OAuthRegistrationAccess) => Effect.Effect<OAuthRegistrationInspection | undefined, OAuthUnavailable>;
|
|
307
309
|
readonly inspect: (input: {
|
|
308
310
|
readonly intent: OAuthRegistrationIntent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-model.mjs","names":[],"sources":["../../../src/internal/models/oauth-model.ts"],"sourcesContent":["import type { PreparedCommit } from \"@yielded/auth/Hooks\";\nimport type {\n OAuthActionAuthorization,\n OAuthAccountRevision,\n OAuthLinkedAccountsRead,\n OAuthRegistrationIntent,\n OAuthExternalIdentity,\n OAuthRegistrationAccess,\n OAuthRegistrationInspection,\n OAuthUnavailable,\n OAuthCommandId,\n PrepareOAuthCommit,\n OAuthRegistrationDecision,\n OAuthCleanupInput,\n} from \"@yielded/auth/OAuth\";\nimport { AuthenticationFactor } from \"@yielded/auth/Operations\";\nimport type { CleanupResult } from \"@yielded/auth/Persistence\";\nimport type { TokenDigest } from \"@yielded/auth/Schema\";\nimport { type AuthenticationRequirement, SecurityRevision } from \"@yielded/auth/Sessions\";\nimport { type Effect, Schema } from \"effect\";\n\nimport type { TableModel as Table, SqlExpression } from \"../table-model\";\nimport type { PersistenceMappingError, SubjectIdCodec } from \"./common\";\n\ntype Column<T extends Table> = T[\"column\"];\nexport type OAuthAction = OAuthActionAuthorization[\"challenge\"][\"action\"];\n\nexport interface OAuthClock<Expression extends SqlExpression = SqlExpression> {\n readonly encodeInstant: (millis: number) => unknown;\n readonly decodeInstant: (value: unknown) => number;\n /** Actual engine wall clock sampled after locks, not transaction-start time.\n * PostgreSQL uses clock_timestamp(), never now()/CURRENT_TIMESTAMP. D1 repeats\n * its primary-engine sample bounds in final guards. Return integer milliseconds. */\n readonly engineNowMillis: Expression;\n /** Convert a stored timestamp expression to integer milliseconds. */\n readonly toMillis: (expression: Expression) => Expression;\n readonly fromMillis: (expression: Expression) => Expression;\n}\n\nexport interface OAuthSubjectReadTable<\n S extends Table,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: S[\"table\"];\n readonly id: Column<S>;\n readonly status: Column<S>;\n readonly securityRevision: Column<S>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly activeCondition: Expression;\n readonly decodeAuthenticationRequirement: (row: S[\"select\"]) => AuthenticationRequirement;\n}\n\nexport interface OAuthSubjectTable<\n S extends Table,\n Expression extends SqlExpression = SqlExpression,\n> extends OAuthSubjectReadTable<S, Expression> {\n readonly nextSecurityRevision: (current: SecurityRevision) => SecurityRevision;\n}\n\nexport interface OAuthOwnershipReadTable<O extends Table, N> {\n readonly table: O[\"table\"];\n readonly identityKey: Column<O>;\n readonly provider: Column<O>;\n readonly issuer: Column<O>;\n readonly externalSubject: Column<O>;\n readonly subjectId: Column<O>;\n readonly decodeSubjectId: (row: O[\"select\"]) => N;\n}\n\nexport interface OAuthOwnershipTable<O extends Table, N> extends OAuthOwnershipReadTable<O, N> {\n readonly encodeInsert: (input: {\n readonly identity: typeof OAuthExternalIdentity.Type;\n readonly identityKey: string;\n readonly subjectId: N;\n }) => O[\"insert\"];\n}\n\nexport interface OAuthCredentialReadTable<\n C extends Table,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: C[\"table\"];\n readonly moduleId: Column<C>;\n readonly credentialId: Column<C>;\n readonly subjectId: Column<C>;\n readonly identityKey: Column<C>;\n readonly credentialRevision: Column<C>;\n readonly status: Column<C>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly activeCondition: Expression;\n}\n\nexport interface OAuthCredentialTable<\n C extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> extends OAuthCredentialReadTable<C, Expression> {\n readonly encodeInsert: (input: {\n readonly moduleId: string;\n readonly subjectId: N;\n readonly identityKey: string;\n readonly credentialId: string;\n readonly credentialRevision: SecurityRevision;\n }) => C[\"insert\"];\n /** This slice removes login rows. Historical records belong in separate audit storage. */\n readonly removal: \"delete\";\n}\n\nexport interface OAuthAuthorityReadTable<\n C extends Table,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: C[\"table\"];\n readonly subjectId: Column<C>;\n readonly credentialId: Column<C>;\n readonly revision: Column<C>;\n readonly status: Column<C>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly activeCondition: Expression;\n}\n\nexport interface OAuthAuthorityTable<\n C extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> extends OAuthAuthorityReadTable<C, Expression> {\n readonly encodeInsert: (input: {\n readonly subjectId: N;\n readonly credentialId: string;\n readonly revision: SecurityRevision;\n }) => C[\"insert\"];\n}\n\n/** Live ceremonies only. Predicate columns enforce access before deletion; the\n * snapshot retains the exact authenticated context and encrypted protocol data. */\nexport interface OAuthFlowTable<F extends Table> {\n readonly table: F[\"table\"];\n readonly moduleId: Column<F>;\n readonly flowId: Column<F>;\n readonly purpose: Column<F>;\n readonly generation: Column<F>;\n readonly provider: Column<F>;\n readonly callbackId: Column<F>;\n readonly issuer: Column<F>;\n readonly responseIssuerMode: Column<F>;\n readonly subjectId: Column<F>;\n readonly stateDigest: Column<F>;\n readonly binderVerifier: Column<F>;\n readonly binderExpiresAt: Column<F>;\n readonly snapshot: Column<F>;\n readonly issuedAt: Column<F>;\n readonly expiresAt: Column<F>;\n readonly encodeInsert: (input: {\n readonly moduleId: string;\n readonly flowId: string;\n readonly purpose: \"sign-in\" | \"link\" | \"connect\";\n }) => F[\"insert\"];\n}\n\nexport const OAuthEligibilityFact = Schema.Struct({\n credentialId: Schema.NonEmptyString.check(Schema.isMaxLength(256)),\n revision: SecurityRevision,\n usablePrimary: Schema.Boolean,\n factors: Schema.Array(AuthenticationFactor).check(Schema.isMaxLength(8)),\n userVerified: Schema.Boolean,\n phishingResistant: Schema.Boolean,\n});\n\nexport type OAuthEligibilityFact = typeof OAuthEligibilityFact.Type;\n\n/** Each descriptor reads only its own subject-scoped method rows. The native row\n * projector and SQL condition must express the same installed method eligibility.\n * All source/identifier/policy mutations advance subject securityRevision. D1 also\n * guards exact selected IDs/versions and absence/count assumptions, never just a\n * stale boolean. An over-limit graph fails closed rather than truncating. */\nexport interface OAuthEligibilityTable<\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: T[\"table\"];\n readonly subjectId: Column<T>;\n readonly credentialId: Column<T>;\n readonly revision: Column<T>;\n readonly scope: string;\n readonly condition: (subjectId: N) => Expression;\n readonly decode: (row: T[\"select\"]) => OAuthEligibilityFact | undefined;\n}\n\nexport interface OAuthCleanupTable<\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: T[\"table\"];\n readonly subjectId: Column<T>;\n /** Bound to this subject's old revision; no opaque independently committing service. */\n readonly condition: (input: {\n readonly subjectId: N;\n readonly revision: OAuthAccountRevision;\n readonly removedCredentialId?: string;\n }) => Expression;\n readonly disposition: \"delete\";\n}\n\nexport interface OAuthRegistrationGuardTable<\n T extends Table,\n Registration,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: T[\"table\"];\n readonly orderBy: Column<T>;\n readonly condition: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly nativeSubjectId?: N;\n }) => Expression;\n}\n\nexport interface OAuthRegistrationGuardDescriptor<\n Registration,\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly table: Descriptor;\n readonly orderBy: string;\n readonly condition: OAuthRegistrationGuardTable<Table, Registration, N, Expression>[\"condition\"];\n}\n\nexport const oauthRegistrationGuardTable = <\n T extends Table,\n Registration,\n N,\n Expression extends SqlExpression = SqlExpression,\n>(\n input: OAuthRegistrationGuardTable<T, Registration, N, Expression>,\n): OAuthRegistrationGuardDescriptor<Registration, N, Expression, T[\"table\"]> =>\n Object.freeze({\n table: input.table,\n orderBy: input.orderBy,\n condition: input.condition,\n });\n\n/** Heterogeneous descriptors are made by typed factories and projected privately. */\nexport interface OAuthEligibilityDescriptor<\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly _tag: \"OAuthEligibilityDescriptor\";\n readonly scope: string;\n readonly table: Descriptor;\n readonly subjectId: string;\n readonly credentialId: string;\n readonly revision: string;\n readonly condition: (subjectId: N) => Expression;\n readonly decode: (row: unknown) => OAuthEligibilityFact | undefined;\n}\n\nexport const oauthEligibilityTable = <\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n>(\n input: OAuthEligibilityTable<T, N, Expression>,\n): OAuthEligibilityDescriptor<N, Expression, T[\"table\"]> => {\n const { table, subjectId, credentialId, revision, scope, condition, decode } = input;\n\n return Object.freeze({\n _tag: \"OAuthEligibilityDescriptor\",\n table,\n subjectId,\n credentialId,\n revision,\n scope,\n condition,\n // The query owner supplies rows selected from this descriptor's mapped table.\n decode: (row: unknown) => decode(row as T[\"select\"]),\n });\n};\n\nexport interface OAuthCleanupDescriptor<\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly table: Descriptor;\n readonly subjectId: string;\n readonly condition: OAuthCleanupTable<Table, N, Expression>[\"condition\"];\n}\n\nexport const oauthCleanupTable = <\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n>(\n input: OAuthCleanupTable<T, N, Expression>,\n): OAuthCleanupDescriptor<N, Expression, T[\"table\"]> =>\n Object.freeze({ table: input.table, subjectId: input.subjectId, condition: input.condition });\n\n/** Original application binding and retained decision are encoded in snapshot. */\nexport interface OAuthRegistrationIntentTable<I extends Table> {\n readonly table: I[\"table\"];\n readonly moduleId: Column<I>;\n readonly reference: Column<I>;\n readonly flowId: Column<I>;\n readonly identityKey: Column<I>;\n readonly snapshot: Column<I>;\n readonly expiresAt: Column<I>;\n readonly retentionUntil: Column<I>;\n readonly encodeInsert: (intent: OAuthRegistrationIntent) => I[\"insert\"];\n}\n\nexport interface OAuthD1Mapping {\n readonly d1: { readonly primary: true };\n}\n\nexport const requiredOAuthSignInConstraints = {\n flow: \"unique(flow.moduleId,flow.flowId)\",\n stateDigest: \"unique(flow.stateDigest)\",\n ownership: \"unique(ownership.identityKey)\",\n credentialId: \"unique(credential.credentialId)\",\n credentialIdentity: \"unique(credential.identityKey)\",\n authorityCredential: \"unique(authority.subjectId,authority.credentialId)\",\n} as const;\n\nexport const requiredOAuthRegistrationConstraints = {\n intentReference: \"unique(intent.moduleId,intent.reference)\",\n intentFlow: \"unique(intent.moduleId,intent.flowId)\",\n ownership: \"unique(ownership.identityKey)\",\n credentialId: \"unique(credential.credentialId)\",\n credentialIdentity: \"unique(credential.identityKey)\",\n authorityCredential: \"unique(authority.subjectId,authority.credentialId)\",\n} as const;\n\nexport interface OAuthSignInMapping<\n S extends Table,\n O extends Table,\n C extends Table,\n AC extends Table,\n F extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly subject: OAuthSubjectReadTable<S, Expression>;\n readonly ownership: OAuthOwnershipReadTable<O, N>;\n readonly credential: OAuthCredentialReadTable<C, Expression>;\n readonly authority: OAuthAuthorityReadTable<AC, Expression>;\n readonly flow: OAuthFlowTable<F>;\n readonly subjectId: SubjectIdCodec<N>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: typeof requiredOAuthSignInConstraints;\n}\n\nexport interface OAuthRegistrationIntentMapping<\n O extends Table,\n I extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly ownership: OAuthOwnershipReadTable<O, N>;\n readonly intent: OAuthRegistrationIntentTable<I>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: Pick<\n typeof requiredOAuthRegistrationConstraints,\n \"intentReference\" | \"intentFlow\" | \"ownership\"\n >;\n readonly eligible: (intent: OAuthRegistrationIntent) => Expression;\n}\n\nexport interface OAuthRegistrationMapping<\n Registration,\n S extends Table,\n O extends Table,\n C extends Table,\n AC extends Table,\n I extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly registration: Schema.Codec<Registration, unknown, never, never>;\n readonly ownership: OAuthOwnershipTable<O, N>;\n readonly intent: OAuthRegistrationIntentTable<I>;\n readonly subject: OAuthSubjectReadTable<S, Expression>;\n readonly credential: OAuthCredentialTable<C, N, Expression>;\n readonly authority: OAuthAuthorityTable<AC, N, Expression>;\n readonly subjectId: SubjectIdCodec<N>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: typeof requiredOAuthRegistrationConstraints;\n readonly inspect: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n }) => Effect.Effect<\n { readonly fingerprint: string; readonly eligible: boolean },\n PersistenceMappingError\n >;\n readonly eligibility: {\n readonly guards?: ReadonlyArray<\n OAuthRegistrationGuardDescriptor<Registration, N, Expression, Descriptor>\n >;\n readonly admission: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly nativeSubjectId?: N;\n }) => Expression;\n readonly postcondition: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly nativeSubjectId?: N;\n }) => Expression;\n };\n readonly allocateSubjectId: Effect.Effect<N, PersistenceMappingError>;\n readonly allocateCredentialId: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateRevision: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly encodeSubjectInsert: (\n input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly requestId: string;\n },\n ids: { readonly subjectId: N; readonly securityRevision: SecurityRevision },\n ) => S[\"insert\"];\n}\n\nexport interface OAuthAccountsMapping<\n S extends Table,\n O extends Table,\n C extends Table,\n AC extends Table,\n F extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly subject: OAuthSubjectTable<S, Expression>;\n readonly ownership: OAuthOwnershipTable<O, N>;\n readonly credential: OAuthCredentialTable<C, N, Expression>;\n readonly authority: OAuthAuthorityTable<AC, N, Expression>;\n readonly flow: OAuthFlowTable<F>;\n readonly subjectId: SubjectIdCodec<N>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: typeof requiredOAuthSignInConstraints;\n /** Self-contained SQL predicate rechecking this invocation's current permission\n * to view login metadata. Evaluated in every releasing read, alongside active\n * subject/credential authority and ownership. Do not trust public assurance as\n * credential provenance; bind session policy to application-owned rows as needed. */\n readonly metadataAccess: (input: {\n readonly invocation: OAuthLinkedAccountsRead[\"invocation\"];\n readonly moduleId: string;\n readonly subjectId: N;\n }) => Expression;\n readonly eligibility: ReadonlyArray<OAuthEligibilityDescriptor<N, Expression, Descriptor>>;\n readonly cleanup: ReadonlyArray<OAuthCleanupDescriptor<N, Expression, Descriptor>>;\n readonly sessionInvalidation: \"same-authority-immediate\" | \"original-absolute-expiry\";\n /** Indexed same-owner reference predicate for any installed grant/application references. */\n readonly otherReferences: (input: {\n readonly identityKey: string;\n readonly subjectId: N;\n }) => Expression;\n readonly allocateCredentialId: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateRevision: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n}\n\nexport interface OAuthRegistrationAuthority<Registration> {\n readonly read: (\n access: OAuthRegistrationAccess,\n ) => Effect.Effect<OAuthRegistrationInspection | undefined, OAuthUnavailable>;\n readonly inspect: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n }) => Effect.Effect<\n {\n readonly fingerprint: TokenDigest;\n readonly eligible: boolean;\n },\n OAuthUnavailable\n >;\n readonly register: <A>(\n input: {\n readonly access: OAuthRegistrationAccess;\n readonly intent: OAuthRegistrationIntent;\n readonly commandId: typeof OAuthCommandId.Type;\n readonly registration: Registration;\n readonly fingerprint: TokenDigest;\n readonly payload: string;\n readonly requestId: string;\n },\n prepare: PrepareOAuthCommit<OAuthRegistrationDecision, A>,\n ) => Effect.Effect<PreparedCommit<A>, OAuthUnavailable>;\n readonly cleanup: <A>(\n input: OAuthCleanupInput,\n prepare: PrepareOAuthCommit<CleanupResult, A>,\n ) => Effect.Effect<PreparedCommit<A>, OAuthUnavailable>;\n}\n"],"mappings":";;;;AA+JA,MAAa,uBAAuB,OAAO,OAAO;CAChD,cAAc,OAAO,eAAe,MAAM,OAAO,YAAY,GAAG,CAAC;CACjE,UAAU;CACV,eAAe,OAAO;CACtB,SAAS,OAAO,MAAM,oBAAoB,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;CACvE,cAAc,OAAO;CACrB,mBAAmB,OAAO;AAC5B,CAAC;AAiED,MAAa,+BAMX,UAEA,OAAO,OAAO;CACZ,OAAO,MAAM;CACb,SAAS,MAAM;CACf,WAAW,MAAM;AACnB,CAAC;AAkBH,MAAa,yBAKX,UAC0D;CAC1D,MAAM,EAAE,OAAO,WAAW,cAAc,UAAU,OAAO,WAAW,WAAW;CAE/E,OAAO,OAAO,OAAO;EACnB,MAAM;EACN;EACA;EACA;EACA;EACA;EACA;EAEA,SAAS,QAAiB,OAAO,GAAkB;CACrD,CAAC;AACH;AAYA,MAAa,qBAKX,UAEA,OAAO,OAAO;CAAE,OAAO,MAAM;CAAO,WAAW,MAAM;CAAW,WAAW,MAAM;AAAU,CAAC;AAmB9F,MAAa,iCAAiC;CAC5C,MAAM;CACN,aAAa;CACb,WAAW;CACX,cAAc;CACd,oBAAoB;CACpB,qBAAqB;AACvB;AAEA,MAAa,uCAAuC;CAClD,iBAAiB;CACjB,YAAY;CACZ,WAAW;CACX,cAAc;CACd,oBAAoB;CACpB,qBAAqB;AACvB"}
|
|
1
|
+
{"version":3,"file":"oauth-model.mjs","names":[],"sources":["../../../src/internal/models/oauth-model.ts"],"sourcesContent":["import type { PreparedCommit } from \"@yielded/auth/Hooks\";\nimport type {\n OAuthActionAuthorization,\n OAuthAccountRevision,\n OAuthLinkedAccountsRead,\n OAuthRegistrationIntent,\n OAuthExternalIdentity,\n OAuthRegistrationAccess,\n OAuthRegistrationInspection,\n OAuthUnavailable,\n OAuthCommandId,\n PrepareOAuthCommit,\n OAuthRegistrationDecision,\n OAuthCleanupInput,\n} from \"@yielded/auth/OAuth\";\nimport { AuthenticationFactor } from \"@yielded/auth/Operations\";\nimport type { CleanupResult } from \"@yielded/auth/Persistence\";\nimport type { TokenDigest } from \"@yielded/auth/Schema\";\nimport { type AuthenticationRequirement, SecurityRevision } from \"@yielded/auth/Sessions\";\nimport { type Effect, Schema } from \"effect\";\n\nimport type { TableModel as Table, SqlExpression } from \"../table-model\";\nimport type { PersistenceMappingError, SubjectIdCodec } from \"./common\";\n\ntype Column<T extends Table> = T[\"column\"];\nexport type OAuthAction = OAuthActionAuthorization[\"challenge\"][\"action\"];\n\nexport interface OAuthClock<Expression extends SqlExpression = SqlExpression> {\n readonly encodeInstant: (millis: number) => unknown;\n readonly decodeInstant: (value: unknown) => number;\n /** Actual engine wall clock sampled after locks, not transaction-start time.\n * PostgreSQL uses clock_timestamp(), never now()/CURRENT_TIMESTAMP. D1 repeats\n * its primary-engine sample bounds in final guards. Return integer milliseconds. */\n readonly engineNowMillis: Expression;\n /** Convert a stored timestamp expression to integer milliseconds. */\n readonly toMillis: (expression: Expression) => Expression;\n readonly fromMillis: (expression: Expression) => Expression;\n}\n\nexport interface OAuthSubjectReadTable<\n S extends Table,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: S[\"table\"];\n readonly id: Column<S>;\n readonly status: Column<S>;\n readonly securityRevision: Column<S>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly activeCondition: Expression;\n readonly decodeAuthenticationRequirement: (\n row: S[\"select\"],\n ) =>\n | AuthenticationRequirement\n | Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;\n}\n\nexport interface OAuthSubjectTable<\n S extends Table,\n Expression extends SqlExpression = SqlExpression,\n> extends OAuthSubjectReadTable<S, Expression> {\n readonly nextSecurityRevision: (current: SecurityRevision) => SecurityRevision;\n}\n\nexport interface OAuthOwnershipReadTable<O extends Table, N> {\n readonly table: O[\"table\"];\n readonly identityKey: Column<O>;\n readonly provider: Column<O>;\n readonly issuer: Column<O>;\n readonly externalSubject: Column<O>;\n readonly subjectId: Column<O>;\n readonly decodeSubjectId: (row: O[\"select\"]) => N;\n}\n\nexport interface OAuthOwnershipTable<O extends Table, N> extends OAuthOwnershipReadTable<O, N> {\n readonly encodeInsert: (input: {\n readonly identity: typeof OAuthExternalIdentity.Type;\n readonly identityKey: string;\n readonly subjectId: N;\n }) => O[\"insert\"];\n}\n\nexport interface OAuthCredentialReadTable<\n C extends Table,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: C[\"table\"];\n readonly moduleId: Column<C>;\n readonly credentialId: Column<C>;\n readonly subjectId: Column<C>;\n readonly identityKey: Column<C>;\n readonly credentialRevision: Column<C>;\n readonly status: Column<C>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly activeCondition: Expression;\n}\n\nexport interface OAuthCredentialTable<\n C extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> extends OAuthCredentialReadTable<C, Expression> {\n readonly encodeInsert: (input: {\n readonly moduleId: string;\n readonly subjectId: N;\n readonly identityKey: string;\n readonly credentialId: string;\n readonly credentialRevision: SecurityRevision;\n }) => C[\"insert\"];\n /** This slice removes login rows. Historical records belong in separate audit storage. */\n readonly removal: \"delete\";\n}\n\nexport interface OAuthAuthorityReadTable<\n C extends Table,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: C[\"table\"];\n readonly subjectId: Column<C>;\n readonly credentialId: Column<C>;\n readonly revision: Column<C>;\n readonly status: Column<C>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly activeCondition: Expression;\n}\n\nexport interface OAuthAuthorityTable<\n C extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> extends OAuthAuthorityReadTable<C, Expression> {\n readonly encodeInsert: (input: {\n readonly subjectId: N;\n readonly credentialId: string;\n readonly revision: SecurityRevision;\n }) => C[\"insert\"];\n}\n\n/** Live ceremonies only. Predicate columns enforce access before deletion; the\n * snapshot retains the exact authenticated context and encrypted protocol data. */\nexport interface OAuthFlowTable<F extends Table> {\n readonly table: F[\"table\"];\n readonly moduleId: Column<F>;\n readonly flowId: Column<F>;\n readonly purpose: Column<F>;\n readonly generation: Column<F>;\n readonly provider: Column<F>;\n readonly callbackId: Column<F>;\n readonly issuer: Column<F>;\n readonly responseIssuerMode: Column<F>;\n readonly subjectId: Column<F>;\n readonly stateDigest: Column<F>;\n readonly binderVerifier: Column<F>;\n readonly binderExpiresAt: Column<F>;\n readonly snapshot: Column<F>;\n readonly issuedAt: Column<F>;\n readonly expiresAt: Column<F>;\n readonly encodeInsert: (input: {\n readonly moduleId: string;\n readonly flowId: string;\n readonly purpose: \"sign-in\" | \"link\" | \"connect\";\n }) => F[\"insert\"];\n}\n\nexport const OAuthEligibilityFact = Schema.Struct({\n credentialId: Schema.NonEmptyString.check(Schema.isMaxLength(256)),\n revision: SecurityRevision,\n usablePrimary: Schema.Boolean,\n factors: Schema.Array(AuthenticationFactor).check(Schema.isMaxLength(8)),\n userVerified: Schema.Boolean,\n phishingResistant: Schema.Boolean,\n});\n\nexport type OAuthEligibilityFact = typeof OAuthEligibilityFact.Type;\n\n/** Each descriptor reads only its own subject-scoped method rows. The native row\n * projector and SQL condition must express the same installed method eligibility.\n * All source/identifier/policy mutations advance subject securityRevision. D1 also\n * guards exact selected IDs/versions and absence/count assumptions, never just a\n * stale boolean. An over-limit graph fails closed rather than truncating. */\nexport interface OAuthEligibilityTable<\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: T[\"table\"];\n readonly subjectId: Column<T>;\n readonly credentialId: Column<T>;\n readonly revision: Column<T>;\n readonly scope: string;\n readonly condition: (subjectId: N) => Expression;\n readonly decode: (row: T[\"select\"]) => OAuthEligibilityFact | undefined;\n}\n\nexport interface OAuthCleanupTable<\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: T[\"table\"];\n readonly subjectId: Column<T>;\n /** Bound to this subject's old revision; no opaque independently committing service. */\n readonly condition: (input: {\n readonly subjectId: N;\n readonly revision: OAuthAccountRevision;\n readonly removedCredentialId?: string;\n }) => Expression;\n readonly disposition: \"delete\";\n}\n\nexport interface OAuthRegistrationGuardTable<\n T extends Table,\n Registration,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: T[\"table\"];\n readonly orderBy: Column<T>;\n readonly condition: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly nativeSubjectId?: N;\n }) => Expression;\n}\n\nexport interface OAuthRegistrationGuardDescriptor<\n Registration,\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly table: Descriptor;\n readonly orderBy: string;\n readonly condition: OAuthRegistrationGuardTable<Table, Registration, N, Expression>[\"condition\"];\n}\n\nexport const oauthRegistrationGuardTable = <\n T extends Table,\n Registration,\n N,\n Expression extends SqlExpression = SqlExpression,\n>(\n input: OAuthRegistrationGuardTable<T, Registration, N, Expression>,\n): OAuthRegistrationGuardDescriptor<Registration, N, Expression, T[\"table\"]> =>\n Object.freeze({\n table: input.table,\n orderBy: input.orderBy,\n condition: input.condition,\n });\n\n/** Heterogeneous descriptors are made by typed factories and projected privately. */\nexport interface OAuthEligibilityDescriptor<\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly _tag: \"OAuthEligibilityDescriptor\";\n readonly scope: string;\n readonly table: Descriptor;\n readonly subjectId: string;\n readonly credentialId: string;\n readonly revision: string;\n readonly condition: (subjectId: N) => Expression;\n readonly decode: (row: unknown) => OAuthEligibilityFact | undefined;\n}\n\nexport const oauthEligibilityTable = <\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n>(\n input: OAuthEligibilityTable<T, N, Expression>,\n): OAuthEligibilityDescriptor<N, Expression, T[\"table\"]> => {\n const { table, subjectId, credentialId, revision, scope, condition, decode } = input;\n\n return Object.freeze({\n _tag: \"OAuthEligibilityDescriptor\",\n table,\n subjectId,\n credentialId,\n revision,\n scope,\n condition,\n // The query owner supplies rows selected from this descriptor's mapped table.\n decode: (row: unknown) => decode(row as T[\"select\"]),\n });\n};\n\nexport interface OAuthCleanupDescriptor<\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly table: Descriptor;\n readonly subjectId: string;\n readonly condition: OAuthCleanupTable<Table, N, Expression>[\"condition\"];\n}\n\nexport const oauthCleanupTable = <\n T extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n>(\n input: OAuthCleanupTable<T, N, Expression>,\n): OAuthCleanupDescriptor<N, Expression, T[\"table\"]> =>\n Object.freeze({ table: input.table, subjectId: input.subjectId, condition: input.condition });\n\n/** Original application binding and retained decision are encoded in snapshot. */\nexport interface OAuthRegistrationIntentTable<I extends Table> {\n readonly table: I[\"table\"];\n readonly moduleId: Column<I>;\n readonly reference: Column<I>;\n readonly flowId: Column<I>;\n readonly identityKey: Column<I>;\n readonly snapshot: Column<I>;\n readonly expiresAt: Column<I>;\n readonly retentionUntil: Column<I>;\n readonly encodeInsert: (intent: OAuthRegistrationIntent) => I[\"insert\"];\n}\n\nexport interface OAuthD1Mapping {\n readonly d1: { readonly primary: true };\n}\n\nexport const requiredOAuthSignInConstraints = {\n flow: \"unique(flow.moduleId,flow.flowId)\",\n stateDigest: \"unique(flow.stateDigest)\",\n ownership: \"unique(ownership.identityKey)\",\n credentialId: \"unique(credential.credentialId)\",\n credentialIdentity: \"unique(credential.identityKey)\",\n authorityCredential: \"unique(authority.subjectId,authority.credentialId)\",\n} as const;\n\nexport const requiredOAuthRegistrationConstraints = {\n intentReference: \"unique(intent.moduleId,intent.reference)\",\n intentFlow: \"unique(intent.moduleId,intent.flowId)\",\n ownership: \"unique(ownership.identityKey)\",\n credentialId: \"unique(credential.credentialId)\",\n credentialIdentity: \"unique(credential.identityKey)\",\n authorityCredential: \"unique(authority.subjectId,authority.credentialId)\",\n} as const;\n\nexport interface OAuthSignInMapping<\n S extends Table,\n O extends Table,\n C extends Table,\n AC extends Table,\n F extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly subject: OAuthSubjectReadTable<S, Expression>;\n readonly ownership: OAuthOwnershipReadTable<O, N>;\n readonly credential: OAuthCredentialReadTable<C, Expression>;\n readonly authority: OAuthAuthorityReadTable<AC, Expression>;\n readonly flow: OAuthFlowTable<F>;\n readonly subjectId: SubjectIdCodec<N>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: typeof requiredOAuthSignInConstraints;\n}\n\nexport interface OAuthRegistrationIntentMapping<\n O extends Table,\n I extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly ownership: OAuthOwnershipReadTable<O, N>;\n readonly intent: OAuthRegistrationIntentTable<I>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: Pick<\n typeof requiredOAuthRegistrationConstraints,\n \"intentReference\" | \"intentFlow\" | \"ownership\"\n >;\n readonly eligible: (intent: OAuthRegistrationIntent) => Expression;\n}\n\nexport interface OAuthRegistrationMapping<\n Registration,\n S extends Table,\n O extends Table,\n C extends Table,\n AC extends Table,\n I extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly registration: Schema.Codec<Registration, unknown, never, never>;\n readonly ownership: OAuthOwnershipTable<O, N>;\n readonly intent: OAuthRegistrationIntentTable<I>;\n readonly subject: OAuthSubjectReadTable<S, Expression>;\n readonly credential: OAuthCredentialTable<C, N, Expression>;\n readonly authority: OAuthAuthorityTable<AC, N, Expression>;\n readonly subjectId: SubjectIdCodec<N>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: typeof requiredOAuthRegistrationConstraints;\n readonly inspect: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n }) => Effect.Effect<\n { readonly fingerprint: string; readonly eligible: boolean },\n PersistenceMappingError\n >;\n readonly eligibility: {\n readonly guards?: ReadonlyArray<\n OAuthRegistrationGuardDescriptor<Registration, N, Expression, Descriptor>\n >;\n readonly admission: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly nativeSubjectId?: N;\n }) => Expression;\n readonly postcondition: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly nativeSubjectId?: N;\n }) => Expression;\n };\n readonly allocateSubjectId: Effect.Effect<N, PersistenceMappingError>;\n readonly allocateCredentialId: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateRevision: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly encodeSubjectInsert: (\n input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n readonly requestId: string;\n },\n ids: { readonly subjectId: N; readonly securityRevision: SecurityRevision },\n ) => S[\"insert\"];\n}\n\nexport interface OAuthAccountsMapping<\n S extends Table,\n O extends Table,\n C extends Table,\n AC extends Table,\n F extends Table,\n N,\n Expression extends SqlExpression = SqlExpression,\n Descriptor extends object = object,\n> {\n readonly subject: OAuthSubjectTable<S, Expression>;\n readonly ownership: OAuthOwnershipTable<O, N>;\n readonly credential: OAuthCredentialTable<C, N, Expression>;\n readonly authority: OAuthAuthorityTable<AC, N, Expression>;\n readonly flow: OAuthFlowTable<F>;\n readonly subjectId: SubjectIdCodec<N>;\n readonly clock: OAuthClock<Expression>;\n readonly constraints: typeof requiredOAuthSignInConstraints;\n /** Self-contained SQL predicate rechecking this invocation's current permission\n * to view login metadata. Evaluated in every releasing read, alongside active\n * subject/credential authority and ownership. Do not trust public assurance as\n * credential provenance; bind session policy to application-owned rows as needed. */\n readonly metadataAccess: (input: {\n readonly invocation: OAuthLinkedAccountsRead[\"invocation\"];\n readonly moduleId: string;\n readonly subjectId: N;\n }) => Expression;\n readonly eligibility: ReadonlyArray<OAuthEligibilityDescriptor<N, Expression, Descriptor>>;\n readonly cleanup: ReadonlyArray<OAuthCleanupDescriptor<N, Expression, Descriptor>>;\n readonly sessionInvalidation: \"same-authority-immediate\" | \"original-absolute-expiry\";\n /** Indexed same-owner reference predicate for any installed grant/application references. */\n readonly otherReferences: (input: {\n readonly identityKey: string;\n readonly subjectId: N;\n }) => Expression;\n readonly allocateCredentialId: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateRevision: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n}\n\nexport interface OAuthRegistrationAuthority<Registration> {\n /** Only the first confirmed registration supplies its private authentication handoff. */\n readonly authentication?: \"first-confirmed-registration\";\n readonly read: (\n access: OAuthRegistrationAccess,\n ) => Effect.Effect<OAuthRegistrationInspection | undefined, OAuthUnavailable>;\n readonly inspect: (input: {\n readonly intent: OAuthRegistrationIntent;\n readonly registration: Registration;\n }) => Effect.Effect<\n {\n readonly fingerprint: TokenDigest;\n readonly eligible: boolean;\n },\n OAuthUnavailable\n >;\n readonly register: <A>(\n input: {\n readonly access: OAuthRegistrationAccess;\n readonly intent: OAuthRegistrationIntent;\n readonly commandId: typeof OAuthCommandId.Type;\n readonly registration: Registration;\n readonly fingerprint: TokenDigest;\n readonly payload: string;\n readonly requestId: string;\n },\n prepare: PrepareOAuthCommit<OAuthRegistrationDecision, A>,\n ) => Effect.Effect<PreparedCommit<A>, OAuthUnavailable>;\n readonly cleanup: <A>(\n input: OAuthCleanupInput,\n prepare: PrepareOAuthCommit<CleanupResult, A>,\n ) => Effect.Effect<PreparedCommit<A>, OAuthUnavailable>;\n}\n"],"mappings":";;;;AAmKA,MAAa,uBAAuB,OAAO,OAAO;CAChD,cAAc,OAAO,eAAe,MAAM,OAAO,YAAY,GAAG,CAAC;CACjE,UAAU;CACV,eAAe,OAAO;CACtB,SAAS,OAAO,MAAM,oBAAoB,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;CACvE,cAAc,OAAO;CACrB,mBAAmB,OAAO;AAC5B,CAAC;AAiED,MAAa,+BAMX,UAEA,OAAO,OAAO;CACZ,OAAO,MAAM;CACb,SAAS,MAAM;CACf,WAAW,MAAM;AACnB,CAAC;AAkBH,MAAa,yBAKX,UAC0D;CAC1D,MAAM,EAAE,OAAO,WAAW,cAAc,UAAU,OAAO,WAAW,WAAW;CAE/E,OAAO,OAAO,OAAO;EACnB,MAAM;EACN;EACA;EACA;EACA;EACA;EACA;EAEA,SAAS,QAAiB,OAAO,GAAkB;CACrD,CAAC;AACH;AAYA,MAAa,qBAKX,UAEA,OAAO,OAAO;CAAE,OAAO,MAAM;CAAO,WAAW,MAAM;CAAW,WAAW,MAAM;AAAU,CAAC;AAmB9F,MAAa,iCAAiC;CAC5C,MAAM;CACN,aAAa;CACb,WAAW;CACX,cAAc;CACd,oBAAoB;CACpB,qBAAqB;AACvB;AAEA,MAAa,uCAAuC;CAClD,iBAAiB;CACjB,YAAY;CACZ,WAAW;CACX,cAAc;CACd,oBAAoB;CACpB,qBAAqB;AACvB"}
|
|
@@ -15,7 +15,8 @@ export interface PasswordSubjectTable<Subject extends TableModel, _NativeSubject
|
|
|
15
15
|
readonly securityRevision: ColumnKey<Subject>;
|
|
16
16
|
readonly isActiveStatus: (value: unknown) => boolean;
|
|
17
17
|
readonly decodeRequirement: (row: Subject["select"]) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;
|
|
18
|
-
/** Current policy for this exact credential mutation
|
|
18
|
+
/** Current policy for this exact credential mutation. Interactive drivers hold
|
|
19
|
+
* the subject lock; D1 guards the physical application columns through commit. */
|
|
19
20
|
readonly decodeActionRequirement: (row: Subject["select"], action: PasswordAction) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;
|
|
20
21
|
readonly nextSecurityRevision?: (current: SecurityRevision) => Effect.Effect<SecurityRevision, PersistenceMappingError>;
|
|
21
22
|
readonly nextSecurityRevisionSync?: (current: SecurityRevision) => SecurityRevision;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-model.mjs","names":[],"sources":["../../../src/internal/models/password-model.ts"],"sourcesContent":["import { type LoginIdentifier } from \"@yielded/auth/Identity\";\nimport type {\n PasswordAction,\n PasswordCredentialSnapshot,\n PasswordReplacement,\n EncodedPasswordHash,\n} from \"@yielded/auth/Password\";\nimport type { AuthenticationRequirement, SecurityRevision } from \"@yielded/auth/Sessions\";\nimport type { Effect, Redacted } from \"effect\";\n\nimport type { AnyTableModel, TableModel as Table, SqlExpression } from \"../table-model\";\nimport type { PersistenceMappingError, SubjectIdCodec } from \"./common\";\nimport type { ProofClock } from \"./proof-model\";\n\ntype ColumnKey<T extends Table> = T[\"column\"];\n\nexport interface PasswordSubjectTable<Subject extends Table, _NativeSubjectId> {\n readonly table: Subject[\"table\"];\n readonly id: ColumnKey<Subject>;\n readonly status: ColumnKey<Subject>;\n readonly securityRevision: ColumnKey<Subject>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly decodeRequirement: (\n row: Subject[\"select\"],\n ) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;\n /** Current policy for this exact credential mutation, decoded under the subject lock. */\n readonly decodeActionRequirement: (\n row: Subject[\"select\"],\n action: PasswordAction,\n ) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;\n readonly nextSecurityRevision?: (\n current: SecurityRevision,\n ) => Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly nextSecurityRevisionSync?: (current: SecurityRevision) => SecurityRevision;\n /** Required only by D1 conditional statements. */\n readonly d1ActiveStatusValue?: unknown;\n}\n\nexport interface PasswordIdentifierTable<\n Identifier extends Table,\n NativeSubjectId,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: Identifier[\"table\"];\n readonly namespace: ColumnKey<Identifier>;\n readonly value: ColumnKey<Identifier>;\n readonly subjectId: ColumnKey<Identifier>;\n readonly verifiedAt: ColumnKey<Identifier>;\n readonly bindingRevision: ColumnKey<Identifier>;\n /** Consumer-owned current login/recovery eligibility, including row status. */\n readonly isCurrent: (row: Identifier[\"select\"]) => boolean;\n /** Required by D1 so current ownership/eligibility is rechecked in the batch. */\n readonly d1CurrentCondition?: (input: {\n readonly identifier: LoginIdentifier;\n readonly nativeSubjectId: NativeSubjectId;\n }) => Expression;\n readonly encodeInitialInsert: (\n identifier: LoginIdentifier,\n subjectId: NativeSubjectId,\n bindingRevision: SecurityRevision,\n ) => Identifier[\"insert\"];\n}\n\nexport interface PasswordAuthorityCredentialTable<Credential extends Table, NativeSubjectId> {\n readonly table: Credential[\"table\"];\n readonly subjectId: ColumnKey<Credential>;\n readonly credentialId: ColumnKey<Credential>;\n readonly revision: ColumnKey<Credential>;\n readonly status?: ColumnKey<Credential>;\n readonly isActiveStatus?: (value: unknown) => boolean;\n readonly d1ActiveStatusValue?: unknown;\n readonly encodeInsert: (input: {\n readonly subjectId: NativeSubjectId;\n readonly credentialId: string;\n readonly revision: SecurityRevision;\n }) => Credential[\"insert\"];\n readonly encodeRevision: (revision: SecurityRevision) => Partial<Credential[\"insert\"]>;\n}\n\nexport interface PasswordCredentialTable<\n Subject extends Table,\n Identifier extends Table,\n Credential extends Table,\n NativeSubjectId,\n> {\n readonly table: Credential[\"table\"];\n readonly moduleId: ColumnKey<Credential>;\n readonly subjectId: ColumnKey<Credential>;\n readonly credentialId: ColumnKey<Credential>;\n readonly credentialRevision: ColumnKey<Credential>;\n readonly verifierVersion: ColumnKey<Credential>;\n readonly verifier: ColumnKey<Credential>;\n readonly normalization: ColumnKey<Credential>;\n readonly encodeInsert: (input: {\n readonly moduleId: string;\n readonly subjectId: NativeSubjectId;\n readonly credentialId: string;\n readonly credentialRevision: SecurityRevision;\n readonly verifierVersion: SecurityRevision;\n readonly replacement: PasswordReplacement;\n }) => Credential[\"insert\"];\n readonly encodeVerifier: (\n verifier: Redacted.Redacted<EncodedPasswordHash>,\n verifierVersion: SecurityRevision,\n ) => Partial<Credential[\"insert\"]>;\n readonly encodeReplacement: (input: {\n readonly replacement: PasswordReplacement;\n readonly credentialRevision: SecurityRevision;\n readonly verifierVersion: SecurityRevision;\n }) => Partial<Credential[\"insert\"]>;\n readonly decode: (input: {\n readonly moduleId: string;\n readonly subject: Subject[\"select\"];\n readonly identifier: Identifier[\"select\"];\n readonly credential: Credential[\"select\"];\n }) => Effect.Effect<PasswordCredentialSnapshot, PersistenceMappingError>;\n}\n\nexport interface RequiredPasswordConstraints {\n readonly identifier: \"unique(identifier.namespace,identifier.value)\";\n readonly authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\";\n readonly credentialSubject: \"unique(credential.moduleId,credential.subjectId)\";\n readonly credentialId: \"unique(credential.moduleId,credential.credentialId)\";\n}\n\nexport const requiredPasswordConstraints: RequiredPasswordConstraints = {\n identifier: \"unique(identifier.namespace,identifier.value)\",\n authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\",\n credentialSubject: \"unique(credential.moduleId,credential.subjectId)\",\n credentialId: \"unique(credential.moduleId,credential.credentialId)\",\n};\n\nexport interface PasswordPersistenceMapping<\n Subject extends Table,\n Identifier extends Table,\n Credential extends Table,\n AuthorityCredential extends Table,\n NativeSubjectId,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly subject: PasswordSubjectTable<Subject, NativeSubjectId>;\n readonly identifier: PasswordIdentifierTable<Identifier, NativeSubjectId, Expression>;\n readonly credential: PasswordCredentialTable<Subject, Identifier, Credential, NativeSubjectId>;\n /** Every factor revision named by action evidence, including non-password factors. */\n readonly authorityCredential: PasswordAuthorityCredentialTable<\n AuthorityCredential,\n NativeSubjectId\n >;\n readonly subjectId: SubjectIdCodec<NativeSubjectId>;\n readonly constraints: RequiredPasswordConstraints;\n readonly encodeInstant: (epochMillis: number) => unknown;\n readonly decodeInstant: (native: unknown) => Effect.Effect<number, PersistenceMappingError>;\n readonly allocateCredentialId?: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateCredentialIdSync?: () => string;\n readonly allocateRevision?: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly allocateRevisionSync?: () => SecurityRevision;\n /** All semantic replacements still bump subject revision. This flag declares\n * authoritative verification's invalidation latency; cookie exposure is separate. */\n readonly sessionInvalidation: \"same-authority-immediate\" | \"original-absolute-expiry\";\n readonly clock: ProofClock<Expression>;\n readonly d1?: { readonly primary: true };\n}\n\nexport interface PasswordRegistrationIntent<Registration> {\n readonly moduleId: string;\n readonly requestId: string;\n readonly identifier: LoginIdentifier;\n readonly registration: Registration;\n readonly replacement: PasswordReplacement;\n}\n\nexport interface PasswordRegistrationProvisioning<\n Registration,\n Subject extends Table,\n NativeSubjectId,\n> {\n readonly encodeSubjectInsert: (\n input: PasswordRegistrationIntent<Registration>,\n values: {\n readonly nativeSubjectId: NativeSubjectId | undefined;\n readonly securityRevision: SecurityRevision;\n },\n ) => Subject[\"insert\"];\n readonly allocateSubjectId?: Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n readonly allocateSubjectIdSync?: () => NativeSubjectId;\n /** Interactive targets use this for database-generated/autoincrement keys. */\n readonly decodeGeneratedId?: (\n rows: ReadonlyArray<unknown>,\n ) => Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n}\n\nexport interface RequiredPasswordRegistrationConstraints {\n readonly identifier: \"unique(identifier.namespace,identifier.value)\";\n readonly credentialSubject: \"unique(credential.moduleId,credential.subjectId)\";\n readonly authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\";\n}\n\nexport const requiredPasswordRegistrationConstraints: RequiredPasswordRegistrationConstraints = {\n identifier: \"unique(identifier.namespace,identifier.value)\",\n credentialSubject: \"unique(credential.moduleId,credential.subjectId)\",\n authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\",\n};\n\nexport interface PasswordRegistrationConstraintClassifier {\n readonly isIdentifierConflict: (cause: unknown) => boolean;\n readonly isCredentialConflict: (cause: unknown) => boolean;\n}\n\nexport type PasswordRegistrationMapping<\n Registration,\n Subject extends Table,\n Identifier extends Table,\n Credential extends Table,\n AuthorityCredential extends Table,\n NativeSubjectId,\n Expression extends SqlExpression = SqlExpression,\n> = PasswordRegistrationConstraintClassifier & {\n readonly subject: PasswordSubjectTable<Subject, NativeSubjectId>;\n readonly identifier: PasswordIdentifierTable<Identifier, NativeSubjectId, Expression>;\n readonly credential: PasswordCredentialTable<Subject, Identifier, Credential, NativeSubjectId>;\n readonly authorityCredential: PasswordAuthorityCredentialTable<\n AuthorityCredential,\n NativeSubjectId\n >;\n readonly subjectId: SubjectIdCodec<NativeSubjectId>;\n readonly constraints: RequiredPasswordRegistrationConstraints;\n readonly allocateCredentialId?: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateCredentialIdSync?: () => string;\n readonly allocateRevision?: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly allocateRevisionSync?: () => SecurityRevision;\n} & {\n readonly mode: \"atomic\";\n readonly provisioning: PasswordRegistrationProvisioning<Registration, Subject, NativeSubjectId> &\n (\n | {\n readonly idMode: \"allocated\";\n readonly allocateSubjectId: Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n }\n | {\n readonly idMode: \"synchronous\";\n readonly allocateSubjectIdSync: () => NativeSubjectId;\n }\n | {\n readonly idMode: \"generated\";\n readonly decodeGeneratedId: (\n rows: ReadonlyArray<unknown>,\n ) => Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n }\n );\n};\n\nexport type AnyPasswordPersistenceMapping<Expression extends SqlExpression = SqlExpression> =\n PasswordPersistenceMapping<\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n Expression\n >;\n\nexport type AnyPasswordRegistrationMapping<\n Registration = unknown,\n Expression extends SqlExpression = SqlExpression,\n> = PasswordRegistrationMapping<\n Registration,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n Expression\n>;\n"],"mappings":";;AA6HA,MAAa,8BAA2D;CACtE,YAAY;CACZ,qBAAqB;CACrB,mBAAmB;CACnB,cAAc;AAChB;AAmEA,MAAa,0CAAmF;CAC9F,YAAY;CACZ,mBAAmB;CACnB,qBAAqB;AACvB"}
|
|
1
|
+
{"version":3,"file":"password-model.mjs","names":[],"sources":["../../../src/internal/models/password-model.ts"],"sourcesContent":["import { type LoginIdentifier } from \"@yielded/auth/Identity\";\nimport type {\n PasswordAction,\n PasswordCredentialSnapshot,\n PasswordReplacement,\n EncodedPasswordHash,\n} from \"@yielded/auth/Password\";\nimport type { AuthenticationRequirement, SecurityRevision } from \"@yielded/auth/Sessions\";\nimport type { Effect, Redacted } from \"effect\";\n\nimport type { AnyTableModel, TableModel as Table, SqlExpression } from \"../table-model\";\nimport type { PersistenceMappingError, SubjectIdCodec } from \"./common\";\nimport type { ProofClock } from \"./proof-model\";\n\ntype ColumnKey<T extends Table> = T[\"column\"];\n\nexport interface PasswordSubjectTable<Subject extends Table, _NativeSubjectId> {\n readonly table: Subject[\"table\"];\n readonly id: ColumnKey<Subject>;\n readonly status: ColumnKey<Subject>;\n readonly securityRevision: ColumnKey<Subject>;\n readonly isActiveStatus: (value: unknown) => boolean;\n readonly decodeRequirement: (\n row: Subject[\"select\"],\n ) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;\n /** Current policy for this exact credential mutation. Interactive drivers hold\n * the subject lock; D1 guards the physical application columns through commit. */\n readonly decodeActionRequirement: (\n row: Subject[\"select\"],\n action: PasswordAction,\n ) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;\n readonly nextSecurityRevision?: (\n current: SecurityRevision,\n ) => Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly nextSecurityRevisionSync?: (current: SecurityRevision) => SecurityRevision;\n /** Required only by D1 conditional statements. */\n readonly d1ActiveStatusValue?: unknown;\n}\n\nexport interface PasswordIdentifierTable<\n Identifier extends Table,\n NativeSubjectId,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly table: Identifier[\"table\"];\n readonly namespace: ColumnKey<Identifier>;\n readonly value: ColumnKey<Identifier>;\n readonly subjectId: ColumnKey<Identifier>;\n readonly verifiedAt: ColumnKey<Identifier>;\n readonly bindingRevision: ColumnKey<Identifier>;\n /** Consumer-owned current login/recovery eligibility, including row status. */\n readonly isCurrent: (row: Identifier[\"select\"]) => boolean;\n /** Required by D1 so current ownership/eligibility is rechecked in the batch. */\n readonly d1CurrentCondition?: (input: {\n readonly identifier: LoginIdentifier;\n readonly nativeSubjectId: NativeSubjectId;\n }) => Expression;\n readonly encodeInitialInsert: (\n identifier: LoginIdentifier,\n subjectId: NativeSubjectId,\n bindingRevision: SecurityRevision,\n ) => Identifier[\"insert\"];\n}\n\nexport interface PasswordAuthorityCredentialTable<Credential extends Table, NativeSubjectId> {\n readonly table: Credential[\"table\"];\n readonly subjectId: ColumnKey<Credential>;\n readonly credentialId: ColumnKey<Credential>;\n readonly revision: ColumnKey<Credential>;\n readonly status?: ColumnKey<Credential>;\n readonly isActiveStatus?: (value: unknown) => boolean;\n readonly d1ActiveStatusValue?: unknown;\n readonly encodeInsert: (input: {\n readonly subjectId: NativeSubjectId;\n readonly credentialId: string;\n readonly revision: SecurityRevision;\n }) => Credential[\"insert\"];\n readonly encodeRevision: (revision: SecurityRevision) => Partial<Credential[\"insert\"]>;\n}\n\nexport interface PasswordCredentialTable<\n Subject extends Table,\n Identifier extends Table,\n Credential extends Table,\n NativeSubjectId,\n> {\n readonly table: Credential[\"table\"];\n readonly moduleId: ColumnKey<Credential>;\n readonly subjectId: ColumnKey<Credential>;\n readonly credentialId: ColumnKey<Credential>;\n readonly credentialRevision: ColumnKey<Credential>;\n readonly verifierVersion: ColumnKey<Credential>;\n readonly verifier: ColumnKey<Credential>;\n readonly normalization: ColumnKey<Credential>;\n readonly encodeInsert: (input: {\n readonly moduleId: string;\n readonly subjectId: NativeSubjectId;\n readonly credentialId: string;\n readonly credentialRevision: SecurityRevision;\n readonly verifierVersion: SecurityRevision;\n readonly replacement: PasswordReplacement;\n }) => Credential[\"insert\"];\n readonly encodeVerifier: (\n verifier: Redacted.Redacted<EncodedPasswordHash>,\n verifierVersion: SecurityRevision,\n ) => Partial<Credential[\"insert\"]>;\n readonly encodeReplacement: (input: {\n readonly replacement: PasswordReplacement;\n readonly credentialRevision: SecurityRevision;\n readonly verifierVersion: SecurityRevision;\n }) => Partial<Credential[\"insert\"]>;\n readonly decode: (input: {\n readonly moduleId: string;\n readonly subject: Subject[\"select\"];\n readonly identifier: Identifier[\"select\"];\n readonly credential: Credential[\"select\"];\n }) => Effect.Effect<PasswordCredentialSnapshot, PersistenceMappingError>;\n}\n\nexport interface RequiredPasswordConstraints {\n readonly identifier: \"unique(identifier.namespace,identifier.value)\";\n readonly authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\";\n readonly credentialSubject: \"unique(credential.moduleId,credential.subjectId)\";\n readonly credentialId: \"unique(credential.moduleId,credential.credentialId)\";\n}\n\nexport const requiredPasswordConstraints: RequiredPasswordConstraints = {\n identifier: \"unique(identifier.namespace,identifier.value)\",\n authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\",\n credentialSubject: \"unique(credential.moduleId,credential.subjectId)\",\n credentialId: \"unique(credential.moduleId,credential.credentialId)\",\n};\n\nexport interface PasswordPersistenceMapping<\n Subject extends Table,\n Identifier extends Table,\n Credential extends Table,\n AuthorityCredential extends Table,\n NativeSubjectId,\n Expression extends SqlExpression = SqlExpression,\n> {\n readonly subject: PasswordSubjectTable<Subject, NativeSubjectId>;\n readonly identifier: PasswordIdentifierTable<Identifier, NativeSubjectId, Expression>;\n readonly credential: PasswordCredentialTable<Subject, Identifier, Credential, NativeSubjectId>;\n /** Every factor revision named by action evidence, including non-password factors. */\n readonly authorityCredential: PasswordAuthorityCredentialTable<\n AuthorityCredential,\n NativeSubjectId\n >;\n readonly subjectId: SubjectIdCodec<NativeSubjectId>;\n readonly constraints: RequiredPasswordConstraints;\n readonly encodeInstant: (epochMillis: number) => unknown;\n readonly decodeInstant: (native: unknown) => Effect.Effect<number, PersistenceMappingError>;\n readonly allocateCredentialId?: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateCredentialIdSync?: () => string;\n readonly allocateRevision?: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly allocateRevisionSync?: () => SecurityRevision;\n /** All semantic replacements still bump subject revision. This flag declares\n * authoritative verification's invalidation latency; cookie exposure is separate. */\n readonly sessionInvalidation: \"same-authority-immediate\" | \"original-absolute-expiry\";\n readonly clock: ProofClock<Expression>;\n readonly d1?: { readonly primary: true };\n}\n\nexport interface PasswordRegistrationIntent<Registration> {\n readonly moduleId: string;\n readonly requestId: string;\n readonly identifier: LoginIdentifier;\n readonly registration: Registration;\n readonly replacement: PasswordReplacement;\n}\n\nexport interface PasswordRegistrationProvisioning<\n Registration,\n Subject extends Table,\n NativeSubjectId,\n> {\n readonly encodeSubjectInsert: (\n input: PasswordRegistrationIntent<Registration>,\n values: {\n readonly nativeSubjectId: NativeSubjectId | undefined;\n readonly securityRevision: SecurityRevision;\n },\n ) => Subject[\"insert\"];\n readonly allocateSubjectId?: Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n readonly allocateSubjectIdSync?: () => NativeSubjectId;\n /** Interactive targets use this for database-generated/autoincrement keys. */\n readonly decodeGeneratedId?: (\n rows: ReadonlyArray<unknown>,\n ) => Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n}\n\nexport interface RequiredPasswordRegistrationConstraints {\n readonly identifier: \"unique(identifier.namespace,identifier.value)\";\n readonly credentialSubject: \"unique(credential.moduleId,credential.subjectId)\";\n readonly authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\";\n}\n\nexport const requiredPasswordRegistrationConstraints: RequiredPasswordRegistrationConstraints = {\n identifier: \"unique(identifier.namespace,identifier.value)\",\n credentialSubject: \"unique(credential.moduleId,credential.subjectId)\",\n authorityCredential: \"unique(authorityCredential.subjectId,authorityCredential.credentialId)\",\n};\n\nexport interface PasswordRegistrationConstraintClassifier {\n readonly isIdentifierConflict: (cause: unknown) => boolean;\n readonly isCredentialConflict: (cause: unknown) => boolean;\n}\n\nexport type PasswordRegistrationMapping<\n Registration,\n Subject extends Table,\n Identifier extends Table,\n Credential extends Table,\n AuthorityCredential extends Table,\n NativeSubjectId,\n Expression extends SqlExpression = SqlExpression,\n> = PasswordRegistrationConstraintClassifier & {\n readonly subject: PasswordSubjectTable<Subject, NativeSubjectId>;\n readonly identifier: PasswordIdentifierTable<Identifier, NativeSubjectId, Expression>;\n readonly credential: PasswordCredentialTable<Subject, Identifier, Credential, NativeSubjectId>;\n readonly authorityCredential: PasswordAuthorityCredentialTable<\n AuthorityCredential,\n NativeSubjectId\n >;\n readonly subjectId: SubjectIdCodec<NativeSubjectId>;\n readonly constraints: RequiredPasswordRegistrationConstraints;\n readonly allocateCredentialId?: Effect.Effect<string, PersistenceMappingError>;\n readonly allocateCredentialIdSync?: () => string;\n readonly allocateRevision?: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n readonly allocateRevisionSync?: () => SecurityRevision;\n} & {\n readonly mode: \"atomic\";\n readonly provisioning: PasswordRegistrationProvisioning<Registration, Subject, NativeSubjectId> &\n (\n | {\n readonly idMode: \"allocated\";\n readonly allocateSubjectId: Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n }\n | {\n readonly idMode: \"synchronous\";\n readonly allocateSubjectIdSync: () => NativeSubjectId;\n }\n | {\n readonly idMode: \"generated\";\n readonly decodeGeneratedId: (\n rows: ReadonlyArray<unknown>,\n ) => Effect.Effect<NativeSubjectId, PersistenceMappingError>;\n }\n );\n};\n\nexport type AnyPasswordPersistenceMapping<Expression extends SqlExpression = SqlExpression> =\n PasswordPersistenceMapping<\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n Expression\n >;\n\nexport type AnyPasswordRegistrationMapping<\n Registration = unknown,\n Expression extends SqlExpression = SqlExpression,\n> = PasswordRegistrationMapping<\n Registration,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n AnyTableModel,\n unknown,\n Expression\n>;\n"],"mappings":";;AA8HA,MAAa,8BAA2D;CACtE,YAAY;CACZ,qBAAqB;CACrB,mBAAmB;CACnB,cAAc;AAChB;AAmEA,MAAa,0CAAmF;CAC9F,YAAY;CACZ,mBAAmB;CACnB,qBAAqB;AACvB"}
|
|
@@ -3,8 +3,8 @@ import { NativeSqlTables } from "../native-sql-table.mjs";
|
|
|
3
3
|
import { OAuthAccountsMapping } from "../models/oauth-model.mjs";
|
|
4
4
|
import { Crypto, Effect } from "effect";
|
|
5
5
|
import { LifecycleHooks } from "@yielded/auth/Hooks";
|
|
6
|
-
import { SqlClient } from "effect/sql/SqlClient";
|
|
7
6
|
import * as M from "@yielded/auth/OAuth";
|
|
7
|
+
import { SqlClient } from "effect/sql/SqlClient";
|
|
8
8
|
//#region src/internal/oauth/native-accounts.d.ts
|
|
9
9
|
export type OAuthNativeAccountsMapping = OAuthAccountsMapping<TableModel, TableModel, TableModel, TableModel, TableModel, unknown, SqlExpression>;
|
|
10
10
|
export declare const makeNativeOAuthAccountsServices: (tables: NativeSqlTables, mapping: OAuthNativeAccountsMapping, batch?: {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { anySqlCondition, exactSqlText, sqlTextBytes } from "../sql-change.mjs";
|
|
2
2
|
import { CurrentSqlCommit, SqlBatchCommit, makeSqlCommitExecutor } from "../sql-commit.mjs";
|
|
3
|
-
import { makeConditionalSqlInsert } from "../session-native-record.mjs";
|
|
4
3
|
import { OAuthEligibilityFact } from "../models/oauth-model.mjs";
|
|
4
|
+
import { makeConditionalSqlInsert } from "../session-native-record.mjs";
|
|
5
5
|
import { invariant, oauthIdentityKey, sameRevision, satisfies, unavailable } from "./state.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { makeOAuthNativeState } from "./native-state.mjs";
|
|
6
|
+
import { decodeOAuthRequirement, makeOAuthNativeState } from "./native-state.mjs";
|
|
8
7
|
import { makeOAuthNativeMutation } from "./native-mutation.mjs";
|
|
8
|
+
import { makeOAuthNativeFlow } from "./native-flow.mjs";
|
|
9
9
|
import { prepareOAuthNative } from "./native-sign-in.mjs";
|
|
10
10
|
import { Crypto, Effect, Option, Schema } from "effect";
|
|
11
|
-
import { SessionInvalidationWindow } from "@yielded/auth/Sessions";
|
|
12
11
|
import * as M from "@yielded/auth/OAuth";
|
|
12
|
+
import { SessionInvalidationWindow } from "@yielded/auth/Sessions";
|
|
13
13
|
//#region src/internal/oauth/native-accounts.ts
|
|
14
14
|
const makeNativeOAuthAccountsServices = Effect.fnUntraced(function* (tables, mapping, batch) {
|
|
15
15
|
const conditionalInsert = yield* makeConditionalSqlInsert();
|
|
@@ -174,7 +174,7 @@ const makeNativeOAuthAccountsServices = Effect.fnUntraced(function* (tables, map
|
|
|
174
174
|
credentialId,
|
|
175
175
|
credentialRevision,
|
|
176
176
|
revision,
|
|
177
|
-
requirement: mapping.subject
|
|
177
|
+
requirement: yield* decodeOAuthRequirement(mapping.subject, current.subject)
|
|
178
178
|
});
|
|
179
179
|
return yield* prepareOAuthNative({
|
|
180
180
|
_tag: "Linked",
|
|
@@ -250,7 +250,7 @@ const makeNativeOAuthAccountsServices = Effect.fnUntraced(function* (tables, map
|
|
|
250
250
|
}
|
|
251
251
|
const methods = [...eligible.values()];
|
|
252
252
|
const facts = methods.map((entry) => entry.fact);
|
|
253
|
-
const requirement = mapping.subject
|
|
253
|
+
const requirement = yield* decodeOAuthRequirement(mapping.subject, current.subject);
|
|
254
254
|
if (!facts.some((fact) => fact.usablePrimary) || !satisfies(facts, requirement)) return yield* prepareOAuthNative({ _tag: "LastSignInMethod" }, prepare);
|
|
255
255
|
const present = (entry) => anySqlCondition(entry.conditions);
|
|
256
256
|
const remaining = sql.and([anySqlCondition(methods.filter((entry) => entry.fact.usablePrimary).map(present)), anySqlCondition(requirement.alternatives.map((alternative) => sql.and([
|