@yielded/auth-persistence 0.1.0-beta.30 → 0.1.0-beta.31
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 +3 -6
- package/dist/Adapter.d.mts +1 -1
- package/dist/internal/configuration.d.mts +18 -8
- package/dist/internal/configuration.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/passkey-managed.mjs +4 -2
- package/dist/internal/passkey-managed.mjs.map +1 -1
- package/dist/internal/password-native.mjs +14 -0
- package/dist/internal/password-native.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 +4 -6
- package/dist/internal/persistence.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 +2 -1
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.mjs +2 -1
- package/dist/internal/session-native-login.mjs.map +1 -1
- package/dist/internal/session-native-pending-read.mjs +1 -1
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +21 -8
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.mjs +2 -1
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.mjs +4 -2
- package/dist/internal/session-native-step-up.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/storage-mapping.d.mts +9 -5
- package/dist/internal/storage-mapping.mjs +16 -4
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/package.json +1 -1
- package/src/internal/configuration.ts +26 -8
- package/src/internal/models/password-model.ts +2 -1
- package/src/internal/passkey-managed.ts +8 -2
- package/src/internal/password-native.ts +28 -2
- package/src/internal/persistence.ts +174 -164
- package/src/internal/registration.ts +53 -40
- package/src/internal/session-native-authority.ts +3 -1
- package/src/internal/session-native-login.ts +3 -1
- package/src/internal/session-native-pending-read.ts +1 -1
- package/src/internal/session-native-state.ts +29 -10
- package/src/internal/session-native-stateful.ts +3 -1
- package/src/internal/session-native-step-up.ts +5 -2
- package/src/internal/sqlite-policy-guard.ts +34 -0
- package/src/internal/storage-mapping.ts +40 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PersistenceMappingError } from "./mapping-error.mjs";
|
|
2
|
+
import { exactSqlText } from "./sql-change.mjs";
|
|
2
3
|
import { randomId } from "./crypto.mjs";
|
|
3
4
|
import { storageTables } from "./storage-tables.mjs";
|
|
4
5
|
import { requiredEmailAddressConstraints } from "./models/email-model.mjs";
|
|
@@ -6,6 +7,7 @@ import { requiredPasswordConstraints } from "./models/password-model.mjs";
|
|
|
6
7
|
import { requiredProofConstraints } from "./models/proof-model.mjs";
|
|
7
8
|
import { makeStorageClock } from "./storage-clock.mjs";
|
|
8
9
|
import { Crypto, DateTime, Effect, Redacted, Schema, Struct } from "effect";
|
|
10
|
+
import { SqlClient } from "effect/sql";
|
|
9
11
|
import { EmailCredentialSnapshot } from "@yielded/auth/Email";
|
|
10
12
|
import { EncodedPasswordHash, PasswordCredentialSnapshot } from "@yielded/auth/Password";
|
|
11
13
|
import { AuthenticationEvidence, SecurityRevision, SessionAuthenticationProvenance, SessionCredentialVersion, SessionId, SessionMetadata } from "@yielded/auth/Sessions";
|
|
@@ -20,12 +22,13 @@ const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe
|
|
|
20
22
|
const string = (value) => Schema.decodeUnknownSync(Schema.String)(value);
|
|
21
23
|
/** Derive shared row mappings from a managed or custom storage layout.
|
|
22
24
|
* Acquire Effect Crypto once; the returned allocators retain that implementation.
|
|
23
|
-
* Each factory requires its role tables to exist.
|
|
24
|
-
*
|
|
25
|
-
* authority policy for explicit services.
|
|
25
|
+
* Each factory requires its role tables to exist. Supplying native tables derives
|
|
26
|
+
* batch commit predicates and subject policy columns. Adapter authors may refine
|
|
27
|
+
* clocks and authority policy for explicit services.
|
|
26
28
|
*/
|
|
27
|
-
const makeMappings = Effect.fnUntraced(function* (input) {
|
|
29
|
+
const makeMappings = Effect.fnUntraced(function* (input, tables) {
|
|
28
30
|
const crypto = yield* Crypto.Crypto;
|
|
31
|
+
const sql = (yield* SqlClient.SqlClient).withoutTransforms();
|
|
29
32
|
const allocate = randomId.pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.mapError(failure));
|
|
30
33
|
const table = (role) => {
|
|
31
34
|
const found = input.tables[role];
|
|
@@ -42,11 +45,14 @@ const makeMappings = Effect.fnUntraced(function* (input) {
|
|
|
42
45
|
toSubject: s.toSubject,
|
|
43
46
|
equals: Object.is
|
|
44
47
|
};
|
|
48
|
+
const identifierTable = tables?.(table("identifiers"));
|
|
49
|
+
const activeIdentifier = identifierTable === void 0 ? void 0 : sql`${identifierTable.column("active")} = ${identifierTable.value("active", true)}`;
|
|
45
50
|
const subject = {
|
|
46
51
|
table: s.table,
|
|
47
52
|
id: s.id,
|
|
48
53
|
status: s.status,
|
|
49
54
|
securityRevision: s.securityRevision,
|
|
55
|
+
...tables === void 0 ? {} : { requirementColumns: tables(s.table).keys.filter((key) => key !== s.id && key !== s.status && key !== s.securityRevision) },
|
|
50
56
|
isActiveStatus: (value) => Object.is(value, s.activeValue),
|
|
51
57
|
d1ActiveStatusValue: s.activeValue,
|
|
52
58
|
activeStatusValue: s.activeValue,
|
|
@@ -61,6 +67,7 @@ const makeMappings = Effect.fnUntraced(function* (input) {
|
|
|
61
67
|
revision: "revision",
|
|
62
68
|
status: "active",
|
|
63
69
|
activeStatusValue: true,
|
|
70
|
+
d1ActiveStatusValue: true,
|
|
64
71
|
isActiveStatus: (value) => value === true,
|
|
65
72
|
encodeInsert: (row) => ({
|
|
66
73
|
...row,
|
|
@@ -120,6 +127,7 @@ const makeMappings = Effect.fnUntraced(function* (input) {
|
|
|
120
127
|
verifiedAt: "verifiedAt",
|
|
121
128
|
bindingRevision: "revision",
|
|
122
129
|
isCurrent: (row) => row.active === true,
|
|
130
|
+
...activeIdentifier === void 0 ? {} : { d1CurrentCondition: () => activeIdentifier },
|
|
123
131
|
encodeInitialInsert: (identifier, subjectId, revision) => ({
|
|
124
132
|
...identifier,
|
|
125
133
|
subjectId,
|
|
@@ -216,6 +224,10 @@ const makeMappings = Effect.fnUntraced(function* (input) {
|
|
|
216
224
|
bindingRevision: "revision",
|
|
217
225
|
isCurrent: (row) => row.active === true && row.verifiedAt !== null,
|
|
218
226
|
isMutableTarget: (row) => row.active === true && row.verifiedAt === null,
|
|
227
|
+
...identifierTable === void 0 || activeIdentifier === void 0 ? {} : {
|
|
228
|
+
d1CurrentCondition: ({ bindingRevision }) => sql`${activeIdentifier} and ${identifierTable.column("verifiedAt")} is not null and ${exactSqlText(sql, identifierTable.column("revision"), identifierTable.value("revision", bindingRevision))}`,
|
|
229
|
+
d1MutableTargetCondition: () => sql`${activeIdentifier} and ${identifierTable.column("verifiedAt")} is null`
|
|
230
|
+
},
|
|
219
231
|
encodeVerifiedInsert: ({ identifier, subjectId, verifiedAtMillis, bindingRevision }) => ({
|
|
220
232
|
...identifier,
|
|
221
233
|
subjectId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-mapping.mjs","names":[],"sources":["../../src/internal/storage-mapping.ts"],"sourcesContent":["import { EmailCredentialSnapshot } from \"@yielded/auth/Email\";\nimport { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n PasswordCredentialSnapshot,\n EncodedPasswordHash,\n type PasswordReplacement,\n} from \"@yielded/auth/Password\";\nimport { TokenDigest } from \"@yielded/auth/Schema\";\nimport {\n SecurityRevision,\n AuthenticationEvidence,\n SessionId,\n SessionMetadata,\n SessionAuthenticationProvenance,\n SessionCredentialVersion,\n type StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport { Crypto, DateTime, Effect, Redacted, Schema, Struct } from \"effect\";\n\nimport type { MappingInput } from \"./configuration\";\nimport { randomId } from \"./crypto\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport { requiredEmailAddressConstraints, type AnyEmailAddressMapping } from \"./models/email-model\";\nimport {\n requiredPasswordConstraints,\n type AnyPasswordPersistenceMapping,\n} from \"./models/password-model\";\nimport { requiredProofConstraints, type AnyProofPersistenceMapping } from \"./models/proof-model\";\nimport type { StatefulSessionMapping, PendingAuthenticationMapping } from \"./models/session-model\";\nimport { makeStorageClock } from \"./storage-clock\";\nimport { storageTables, type StorageRole } from \"./storage-tables\";\nimport type { TableModel, SqlExpression } from \"./table-model\";\n\nconst failure = (cause: unknown) => PersistenceMappingError.make({ operation: \"decode\", cause });\n\nconst decode = <S extends Schema.Codec<unknown, unknown>>(schema: S, value: unknown) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(failure));\n\nconst string = (value: unknown) => Schema.decodeUnknownSync(Schema.String)(value);\n\n/** Derive shared row mappings from a managed or custom storage layout.\n * Acquire Effect Crypto once; the returned allocators retain that implementation.\n * Each factory requires its role tables to exist. Adapter authors supply typed\n * table handles and dialect-specific clocks/commit predicates, and may refine\n * authority policy for explicit services. This does not alter composed defaults.\n */\nexport const makeMappings = Effect.fnUntraced(function* (input: MappingInput) {\n const crypto = yield* Crypto.Crypto;\n\n const allocate = randomId.pipe(\n Effect.provideService(Crypto.Crypto, crypto),\n Effect.mapError(failure),\n );\n\n const table = (role: StorageRole) => {\n const found = input.tables[role];\n\n if (found === undefined) throw failure(`Missing ${role} mapping`);\n\n return found;\n };\n\n const mapped = (role: StorageRole) => ({\n table: table(role),\n ...Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key])),\n });\n\n const { subjects: s, encodeInstant: instant, decodeInstant: readInstant } = input;\n const subjectId = { toNative: s.toNative, toSubject: s.toSubject, equals: Object.is };\n\n const subject = {\n table: s.table,\n id: s.id,\n status: s.status,\n securityRevision: s.securityRevision,\n isActiveStatus: (value: unknown) => Object.is(value, s.activeValue),\n d1ActiveStatusValue: s.activeValue,\n activeStatusValue: s.activeValue,\n decodeRequirement: s.requirements,\n decodeActionRequirement: s.actionRequirements,\n nextSecurityRevision: () => allocate.pipe(Effect.map(SecurityRevision.make)),\n };\n\n const authorityCredential = () => ({\n ...mapped(\"credentials\"),\n subjectId: \"subjectId\",\n credentialId: \"credentialId\",\n revision: \"revision\",\n status: \"active\",\n activeStatusValue: true,\n isActiveStatus: (value: unknown) => value === true,\n encodeInsert: (row: object) => ({ ...row, active: true }),\n encodeRevision: (revision: SecurityRevision) => ({ revision }),\n });\n\n const authority = () => ({ subjectId, subject, credential: authorityCredential() });\n\n const clock = yield* makeStorageClock(input);\n\n const proofs = (): AnyProofPersistenceMapping<SqlExpression> => ({\n constraints: requiredProofConstraints,\n subjectId,\n subject: { table: s.table, id: s.id },\n clock,\n proof: {\n ...mapped(\"proofs\"),\n moduleId: \"moduleId\",\n purpose: \"purpose\",\n seriesKey: \"seriesKey\",\n proofId: \"proofId\",\n binding: \"binding\",\n verifierKeyId: \"verifierKeyId\",\n verifierDigest: \"verifierDigest\",\n issuedAt: \"issuedAt\",\n expiresAt: \"expiresAt\",\n failedAttempts: \"failedAttempts\",\n encodeInsert: () => ({}),\n },\n });\n\n const passwords = (): AnyPasswordPersistenceMapping<SqlExpression> => {\n const verifier = (replacement: PasswordReplacement) => ({\n verifier: Redacted.value(replacement.verifier),\n normalization: replacement.normalization,\n });\n\n return {\n subjectId,\n subject,\n authorityCredential: authorityCredential(),\n constraints: requiredPasswordConstraints,\n encodeInstant: instant,\n decodeInstant: readInstant,\n allocateCredentialId: allocate,\n allocateRevision: allocate.pipe(Effect.map(SecurityRevision.make)),\n sessionInvalidation: \"same-authority-immediate\",\n identifier: {\n table: table(\"identifiers\"),\n namespace: \"namespace\",\n value: \"value\",\n subjectId: \"subjectId\",\n verifiedAt: \"verifiedAt\",\n bindingRevision: \"revision\",\n isCurrent: (row) => row.active === true,\n encodeInitialInsert: (identifier, subjectId, revision) => ({\n ...identifier,\n subjectId,\n revision,\n verifiedAt: null,\n active: true,\n }),\n },\n credential: {\n ...mapped(\"passwords\"),\n moduleId: \"moduleId\",\n subjectId: \"subjectId\",\n credentialId: \"credentialId\",\n credentialRevision: \"credentialRevision\",\n verifierVersion: \"verifierVersion\",\n verifier: \"verifier\",\n normalization: \"normalization\",\n encodeInsert: ({ replacement, ...row }) => ({ ...row, ...verifier(replacement) }),\n encodeVerifier: (hash, verifierVersion) => ({\n verifier: Redacted.value(hash),\n verifierVersion,\n }),\n encodeReplacement: ({ replacement, ...row }) => ({ ...row, ...verifier(replacement) }),\n decode: ({ moduleId, subject: row, identifier, credential }) =>\n Effect.gen(function* () {\n const id = yield* s.toSubject(row[s.id]);\n const hash = yield* decode(EncodedPasswordHash, credential.verifier);\n\n return yield* decode(PasswordCredentialSnapshot, {\n moduleId,\n revision: {\n subjectId: id,\n securityRevision: row[s.securityRevision],\n credentials: [\n {\n credentialId: credential.credentialId,\n revision: credential.credentialRevision,\n },\n ],\n },\n credentialId: credential.credentialId,\n credentialRevision: credential.credentialRevision,\n verifierVersion: credential.verifierVersion,\n verifier: hash,\n normalization: credential.normalization,\n identifier: yield* decode(LoginIdentifier, {\n namespace: identifier.namespace,\n value: identifier.value,\n }),\n identifierBindingRevision: identifier.revision,\n ...(identifier.verifiedAt === null\n ? {}\n : { identifierVerifiedAtMillis: yield* readInstant(identifier.verifiedAt) }),\n });\n }),\n },\n clock,\n };\n };\n\n const emails = (): AnyEmailAddressMapping<SqlExpression> => ({\n subjectId,\n subject: { ...subject, activeStatusValue: s.activeValue },\n constraints: requiredEmailAddressConstraints,\n addressCardinality: \"single\",\n changeDisposition: \"retire-source\",\n clock,\n encodeInstant: instant,\n decodeInstant: readInstant,\n allocateCredentialId: allocate,\n allocateRevision: allocate.pipe(Effect.map(SecurityRevision.make)),\n sessionInvalidation: \"same-authority-immediate\",\n isIdentifierConflict: () => false,\n isCredentialConflict: () => false,\n authorityCredential: {\n ...authorityCredential(),\n encodeActivation: (revision) => ({ revision, active: true }),\n encodeRetirement: (revision) => ({ revision, active: false }),\n },\n identifier: {\n table: table(\"identifiers\"),\n namespace: \"namespace\",\n value: \"value\",\n subjectId: \"subjectId\",\n verifiedAt: \"verifiedAt\",\n bindingRevision: \"revision\",\n isCurrent: (row) => row.active === true && row.verifiedAt !== null,\n isMutableTarget: (row) => row.active === true && row.verifiedAt === null,\n encodeVerifiedInsert: ({ identifier, subjectId, verifiedAtMillis, bindingRevision }) => ({\n ...identifier,\n subjectId,\n verifiedAt: instant(verifiedAtMillis),\n revision: bindingRevision,\n active: true,\n }),\n encodeVerification: ({ verifiedAtMillis, bindingRevision }) => ({\n verifiedAt: instant(verifiedAtMillis),\n revision: bindingRevision,\n active: true,\n }),\n encodeRetirement: ({ bindingRevision }) => ({ revision: bindingRevision, active: false }),\n },\n credential: {\n activeStatusValue: true,\n table: table(\"emailCredentials\"),\n moduleId: \"moduleId\",\n subjectId: \"subjectId\",\n credentialId: \"credentialId\",\n identifierNamespace: \"identifierNamespace\",\n identifierValue: \"identifierValue\",\n credentialRevision: \"credentialRevision\",\n status: \"active\",\n isActiveStatus: (value) => value === true,\n encodeVerifiedInsert: ({ identifier, ...row }) => ({\n ...row,\n identifierNamespace: identifier.namespace,\n identifierValue: identifier.value,\n active: true,\n }),\n encodeActivation: ({ identifier, credentialRevision }) => ({\n identifierNamespace: identifier.namespace,\n identifierValue: identifier.value,\n credentialRevision,\n active: true,\n }),\n encodeRetirement: ({ credentialRevision }) => ({ credentialRevision, active: false }),\n decode: ({ moduleId, subject: row, identifier, credential }) =>\n Effect.gen(function* () {\n return yield* decode(\n Schema.Struct(Struct.omit(EmailCredentialSnapshot.fields, [\"requirement\"])),\n {\n moduleId,\n identifier: { namespace: identifier.namespace, value: identifier.value },\n identifierRevision: identifier.revision,\n verifiedAtMillis: yield* readInstant(identifier.verifiedAt),\n credentialId: credential.credentialId,\n credentialRevision: credential.credentialRevision,\n revision: {\n subjectId: yield* s.toSubject(row[s.id]),\n securityRevision: row[s.securityRevision],\n credentials: [\n {\n credentialId: credential.credentialId,\n revision: credential.credentialRevision,\n },\n ],\n },\n },\n );\n }),\n },\n });\n\n const pending = <C extends Schema.Codec<unknown, unknown, never, never>>(\n claims: C,\n moduleId: string,\n ): PendingAuthenticationMapping<C[\"Type\"], TableModel, TableModel, TableModel, unknown> => {\n const record = Schema.fromJsonString(\n Schema.Struct({\n digest: TokenDigest,\n version: SecurityRevision,\n evidence: AuthenticationEvidence,\n expiresAt: Schema.DateTimeUtcFromMillis,\n attemptLimit: Schema.Int.check(Schema.isGreaterThan(0)),\n claims: Schema.Unknown,\n }),\n );\n\n return {\n ...authority(),\n moduleId,\n clock,\n isConstraintConflict: () => false,\n constraints: { pendingDigest: \"unique(pending.digest)\" },\n pending: {\n table: table(\"pending\"),\n moduleId: \"moduleId\",\n kind: \"kind\",\n digest: \"digest\",\n version: \"version\",\n flowId: \"flowId\",\n subjectId: \"subjectId\",\n bindingDigest: \"bindingDigest\",\n snapshot: \"snapshot\",\n expiresAt: \"expiresAt\",\n attemptLimit: \"attemptLimit\",\n failedAttempts: \"failedAttempts\",\n consumed: \"consumed\",\n encodeInstant: (date) => instant(DateTime.toEpochMillis(date)),\n allocateVersion: allocate.pipe(Effect.map(SecurityRevision.make)),\n encodeInsert: (value) => ({\n ...value,\n expiresAt: instant(DateTime.toEpochMillis(value.expiresAt)),\n failedAttempts: 0,\n consumed: false,\n }),\n },\n login: {\n encode: (value) =>\n Schema.encodeEffect(claims)(value.claims).pipe(\n Effect.flatMap((saved) => Schema.encodeEffect(record)({ ...value, claims: saved })),\n Effect.mapError(failure),\n ),\n decode: (value) =>\n decode(record, value).pipe(\n Effect.flatMap((saved) =>\n decode(claims, saved.claims).pipe(\n Effect.map((decoded) => ({ ...saved, claims: decoded })),\n ),\n ),\n ),\n },\n };\n };\n\n const sessions = <C extends Schema.Codec<unknown, unknown, never, never>>(\n claims: C,\n moduleId: string,\n ): StatefulSessionMapping<\n C[\"Type\"],\n TableModel,\n TableModel,\n TableModel,\n TableModel,\n unknown,\n unknown\n > => {\n const record = Schema.fromJsonString(\n Schema.Struct({\n ...SessionMetadata.fields,\n claims: Schema.Unknown,\n digest: TokenDigest,\n provenance: SessionAuthenticationProvenance,\n credentialVersion: SessionCredentialVersion,\n }),\n );\n\n const encodeRow = (value: StatefulSessionRecord<C[\"Type\"]>) => ({\n digest: value.digest,\n securityRevision: value.securityRevision,\n issuedAt: instant(DateTime.toEpochMillis(value.issuedAt)),\n expiresAt: instant(DateTime.toEpochMillis(value.expiresAt)),\n absoluteExpiresAt: instant(DateTime.toEpochMillis(value.absoluteExpiresAt)),\n record: Schema.encodeSync(record)({\n ...value,\n claims: Schema.encodeSync(claims)(value.claims),\n }),\n });\n\n const login = pending(claims, moduleId);\n\n return {\n ...authority(),\n moduleId,\n clock,\n isConstraintConflict: () => false,\n pending: { pending: login.pending, login: login.login },\n constraints: {\n sessionDigest: \"unique(session.digest)\",\n pendingDigest: \"unique(pending.digest)\",\n },\n sessionId: {\n toNative: (id) => Effect.succeed(id),\n toSession: (id) => decode(SessionId, id),\n equals: Object.is,\n },\n session: {\n table: table(\"sessions\"),\n sessionId: \"sessionId\",\n subjectId: \"subjectId\",\n digest: \"digest\",\n securityRevision: \"securityRevision\",\n issuedAt: \"issuedAt\",\n expiresAt: \"expiresAt\",\n absoluteExpiresAt: \"absoluteExpiresAt\",\n encodeInstant: (date) => instant(DateTime.toEpochMillis(date)),\n allocateId: allocate,\n encodeInsert: (value, ids) => ({ ...encodeRow(value), ...ids }),\n encodeRotation: encodeRow,\n decode: (row) =>\n Effect.gen(function* () {\n const saved = yield* decode(record, row.record),\n decodedClaims = yield* decode(claims, saved.claims);\n\n return {\n ...saved,\n issuedAt: DateTime.makeUnsafe(yield* readInstant(row.issuedAt)),\n claims: decodedClaims,\n };\n }),\n },\n };\n };\n\n return { table, authority, proofs, passwords, emails, sessions, pending, string };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage-mapping.mjs","names":[],"sources":["../../src/internal/storage-mapping.ts"],"sourcesContent":["import { EmailCredentialSnapshot } from \"@yielded/auth/Email\";\nimport { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n PasswordCredentialSnapshot,\n EncodedPasswordHash,\n type PasswordReplacement,\n} from \"@yielded/auth/Password\";\nimport { TokenDigest } from \"@yielded/auth/Schema\";\nimport {\n SecurityRevision,\n AuthenticationEvidence,\n SessionId,\n SessionMetadata,\n SessionAuthenticationProvenance,\n SessionCredentialVersion,\n type StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport { Crypto, DateTime, Effect, Redacted, Schema, Struct } from \"effect\";\nimport { SqlClient } from \"effect/sql\";\n\nimport type { MappingInput } from \"./configuration\";\nimport { randomId } from \"./crypto\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport { requiredEmailAddressConstraints, type AnyEmailAddressMapping } from \"./models/email-model\";\nimport {\n requiredPasswordConstraints,\n type AnyPasswordPersistenceMapping,\n} from \"./models/password-model\";\nimport { requiredProofConstraints, type AnyProofPersistenceMapping } from \"./models/proof-model\";\nimport type { StatefulSessionMapping, PendingAuthenticationMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { exactSqlText } from \"./sql-change\";\nimport { makeStorageClock } from \"./storage-clock\";\nimport { storageTables, type StorageRole } from \"./storage-tables\";\nimport type { TableModel, SqlExpression } from \"./table-model\";\n\nconst failure = (cause: unknown) => PersistenceMappingError.make({ operation: \"decode\", cause });\n\nconst decode = <S extends Schema.Codec<unknown, unknown>>(schema: S, value: unknown) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(failure));\n\nconst string = (value: unknown) => Schema.decodeUnknownSync(Schema.String)(value);\n\n/** Derive shared row mappings from a managed or custom storage layout.\n * Acquire Effect Crypto once; the returned allocators retain that implementation.\n * Each factory requires its role tables to exist. Supplying native tables derives\n * batch commit predicates and subject policy columns. Adapter authors may refine\n * clocks and authority policy for explicit services.\n */\nexport const makeMappings = Effect.fnUntraced(function* (\n input: MappingInput,\n tables?: NativeSqlTables,\n) {\n const crypto = yield* Crypto.Crypto;\n const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n\n const allocate = randomId.pipe(\n Effect.provideService(Crypto.Crypto, crypto),\n Effect.mapError(failure),\n );\n\n const table = (role: StorageRole) => {\n const found = input.tables[role];\n\n if (found === undefined) throw failure(`Missing ${role} mapping`);\n\n return found;\n };\n\n const mapped = (role: StorageRole) => ({\n table: table(role),\n ...Object.fromEntries(Object.keys(storageTables[role].columns).map((key) => [key, key])),\n });\n\n const { subjects: s, encodeInstant: instant, decodeInstant: readInstant } = input;\n const subjectId = { toNative: s.toNative, toSubject: s.toSubject, equals: Object.is };\n const identifierTable = tables?.(table(\"identifiers\"));\n\n const activeIdentifier =\n identifierTable === undefined\n ? undefined\n : sql`${identifierTable.column(\"active\")} = ${identifierTable.value(\"active\", true)}`;\n\n const subject = {\n table: s.table,\n id: s.id,\n status: s.status,\n securityRevision: s.securityRevision,\n // Policy callbacks may read any application column. Identity, status and\n // security revision have their own final checks and can be intentionally revised.\n ...(tables === undefined\n ? {}\n : {\n requirementColumns: tables(s.table).keys.filter(\n (key) => key !== s.id && key !== s.status && key !== s.securityRevision,\n ),\n }),\n isActiveStatus: (value: unknown) => Object.is(value, s.activeValue),\n d1ActiveStatusValue: s.activeValue,\n activeStatusValue: s.activeValue,\n decodeRequirement: s.requirements,\n decodeActionRequirement: s.actionRequirements,\n nextSecurityRevision: () => allocate.pipe(Effect.map(SecurityRevision.make)),\n };\n\n const authorityCredential = () => ({\n ...mapped(\"credentials\"),\n subjectId: \"subjectId\",\n credentialId: \"credentialId\",\n revision: \"revision\",\n status: \"active\",\n activeStatusValue: true,\n d1ActiveStatusValue: true,\n isActiveStatus: (value: unknown) => value === true,\n encodeInsert: (row: object) => ({ ...row, active: true }),\n encodeRevision: (revision: SecurityRevision) => ({ revision }),\n });\n\n const authority = () => ({ subjectId, subject, credential: authorityCredential() });\n\n const clock = yield* makeStorageClock(input);\n\n const proofs = (): AnyProofPersistenceMapping<SqlExpression> => ({\n constraints: requiredProofConstraints,\n subjectId,\n subject: { table: s.table, id: s.id },\n clock,\n proof: {\n ...mapped(\"proofs\"),\n moduleId: \"moduleId\",\n purpose: \"purpose\",\n seriesKey: \"seriesKey\",\n proofId: \"proofId\",\n binding: \"binding\",\n verifierKeyId: \"verifierKeyId\",\n verifierDigest: \"verifierDigest\",\n issuedAt: \"issuedAt\",\n expiresAt: \"expiresAt\",\n failedAttempts: \"failedAttempts\",\n encodeInsert: () => ({}),\n },\n });\n\n const passwords = (): AnyPasswordPersistenceMapping<SqlExpression> => {\n const verifier = (replacement: PasswordReplacement) => ({\n verifier: Redacted.value(replacement.verifier),\n normalization: replacement.normalization,\n });\n\n return {\n subjectId,\n subject,\n authorityCredential: authorityCredential(),\n constraints: requiredPasswordConstraints,\n encodeInstant: instant,\n decodeInstant: readInstant,\n allocateCredentialId: allocate,\n allocateRevision: allocate.pipe(Effect.map(SecurityRevision.make)),\n sessionInvalidation: \"same-authority-immediate\",\n identifier: {\n table: table(\"identifiers\"),\n namespace: \"namespace\",\n value: \"value\",\n subjectId: \"subjectId\",\n verifiedAt: \"verifiedAt\",\n bindingRevision: \"revision\",\n isCurrent: (row) => row.active === true,\n ...(activeIdentifier === undefined\n ? {}\n : {\n d1CurrentCondition: () => activeIdentifier,\n }),\n encodeInitialInsert: (identifier, subjectId, revision) => ({\n ...identifier,\n subjectId,\n revision,\n verifiedAt: null,\n active: true,\n }),\n },\n credential: {\n ...mapped(\"passwords\"),\n moduleId: \"moduleId\",\n subjectId: \"subjectId\",\n credentialId: \"credentialId\",\n credentialRevision: \"credentialRevision\",\n verifierVersion: \"verifierVersion\",\n verifier: \"verifier\",\n normalization: \"normalization\",\n encodeInsert: ({ replacement, ...row }) => ({ ...row, ...verifier(replacement) }),\n encodeVerifier: (hash, verifierVersion) => ({\n verifier: Redacted.value(hash),\n verifierVersion,\n }),\n encodeReplacement: ({ replacement, ...row }) => ({ ...row, ...verifier(replacement) }),\n decode: ({ moduleId, subject: row, identifier, credential }) =>\n Effect.gen(function* () {\n const id = yield* s.toSubject(row[s.id]);\n const hash = yield* decode(EncodedPasswordHash, credential.verifier);\n\n return yield* decode(PasswordCredentialSnapshot, {\n moduleId,\n revision: {\n subjectId: id,\n securityRevision: row[s.securityRevision],\n credentials: [\n {\n credentialId: credential.credentialId,\n revision: credential.credentialRevision,\n },\n ],\n },\n credentialId: credential.credentialId,\n credentialRevision: credential.credentialRevision,\n verifierVersion: credential.verifierVersion,\n verifier: hash,\n normalization: credential.normalization,\n identifier: yield* decode(LoginIdentifier, {\n namespace: identifier.namespace,\n value: identifier.value,\n }),\n identifierBindingRevision: identifier.revision,\n ...(identifier.verifiedAt === null\n ? {}\n : { identifierVerifiedAtMillis: yield* readInstant(identifier.verifiedAt) }),\n });\n }),\n },\n clock,\n };\n };\n\n const emails = (): AnyEmailAddressMapping<SqlExpression> => ({\n subjectId,\n subject: { ...subject, activeStatusValue: s.activeValue },\n constraints: requiredEmailAddressConstraints,\n addressCardinality: \"single\",\n changeDisposition: \"retire-source\",\n clock,\n encodeInstant: instant,\n decodeInstant: readInstant,\n allocateCredentialId: allocate,\n allocateRevision: allocate.pipe(Effect.map(SecurityRevision.make)),\n sessionInvalidation: \"same-authority-immediate\",\n isIdentifierConflict: () => false,\n isCredentialConflict: () => false,\n authorityCredential: {\n ...authorityCredential(),\n encodeActivation: (revision) => ({ revision, active: true }),\n encodeRetirement: (revision) => ({ revision, active: false }),\n },\n identifier: {\n table: table(\"identifiers\"),\n namespace: \"namespace\",\n value: \"value\",\n subjectId: \"subjectId\",\n verifiedAt: \"verifiedAt\",\n bindingRevision: \"revision\",\n isCurrent: (row) => row.active === true && row.verifiedAt !== null,\n isMutableTarget: (row) => row.active === true && row.verifiedAt === null,\n ...(identifierTable === undefined || activeIdentifier === undefined\n ? {}\n : {\n d1CurrentCondition: ({ bindingRevision }) =>\n sql`${activeIdentifier} and ${identifierTable.column(\"verifiedAt\")} is not null and ${exactSqlText(sql, identifierTable.column(\"revision\"), identifierTable.value(\"revision\", bindingRevision))}`,\n d1MutableTargetCondition: () =>\n sql`${activeIdentifier} and ${identifierTable.column(\"verifiedAt\")} is null`,\n }),\n encodeVerifiedInsert: ({ identifier, subjectId, verifiedAtMillis, bindingRevision }) => ({\n ...identifier,\n subjectId,\n verifiedAt: instant(verifiedAtMillis),\n revision: bindingRevision,\n active: true,\n }),\n encodeVerification: ({ verifiedAtMillis, bindingRevision }) => ({\n verifiedAt: instant(verifiedAtMillis),\n revision: bindingRevision,\n active: true,\n }),\n encodeRetirement: ({ bindingRevision }) => ({ revision: bindingRevision, active: false }),\n },\n credential: {\n activeStatusValue: true,\n table: table(\"emailCredentials\"),\n moduleId: \"moduleId\",\n subjectId: \"subjectId\",\n credentialId: \"credentialId\",\n identifierNamespace: \"identifierNamespace\",\n identifierValue: \"identifierValue\",\n credentialRevision: \"credentialRevision\",\n status: \"active\",\n isActiveStatus: (value) => value === true,\n encodeVerifiedInsert: ({ identifier, ...row }) => ({\n ...row,\n identifierNamespace: identifier.namespace,\n identifierValue: identifier.value,\n active: true,\n }),\n encodeActivation: ({ identifier, credentialRevision }) => ({\n identifierNamespace: identifier.namespace,\n identifierValue: identifier.value,\n credentialRevision,\n active: true,\n }),\n encodeRetirement: ({ credentialRevision }) => ({ credentialRevision, active: false }),\n decode: ({ moduleId, subject: row, identifier, credential }) =>\n Effect.gen(function* () {\n return yield* decode(\n Schema.Struct(Struct.omit(EmailCredentialSnapshot.fields, [\"requirement\"])),\n {\n moduleId,\n identifier: { namespace: identifier.namespace, value: identifier.value },\n identifierRevision: identifier.revision,\n verifiedAtMillis: yield* readInstant(identifier.verifiedAt),\n credentialId: credential.credentialId,\n credentialRevision: credential.credentialRevision,\n revision: {\n subjectId: yield* s.toSubject(row[s.id]),\n securityRevision: row[s.securityRevision],\n credentials: [\n {\n credentialId: credential.credentialId,\n revision: credential.credentialRevision,\n },\n ],\n },\n },\n );\n }),\n },\n });\n\n const pending = <C extends Schema.Codec<unknown, unknown, never, never>>(\n claims: C,\n moduleId: string,\n ): PendingAuthenticationMapping<C[\"Type\"], TableModel, TableModel, TableModel, unknown> => {\n const record = Schema.fromJsonString(\n Schema.Struct({\n digest: TokenDigest,\n version: SecurityRevision,\n evidence: AuthenticationEvidence,\n expiresAt: Schema.DateTimeUtcFromMillis,\n attemptLimit: Schema.Int.check(Schema.isGreaterThan(0)),\n claims: Schema.Unknown,\n }),\n );\n\n return {\n ...authority(),\n moduleId,\n clock,\n isConstraintConflict: () => false,\n constraints: { pendingDigest: \"unique(pending.digest)\" },\n pending: {\n table: table(\"pending\"),\n moduleId: \"moduleId\",\n kind: \"kind\",\n digest: \"digest\",\n version: \"version\",\n flowId: \"flowId\",\n subjectId: \"subjectId\",\n bindingDigest: \"bindingDigest\",\n snapshot: \"snapshot\",\n expiresAt: \"expiresAt\",\n attemptLimit: \"attemptLimit\",\n failedAttempts: \"failedAttempts\",\n consumed: \"consumed\",\n encodeInstant: (date) => instant(DateTime.toEpochMillis(date)),\n allocateVersion: allocate.pipe(Effect.map(SecurityRevision.make)),\n encodeInsert: (value) => ({\n ...value,\n expiresAt: instant(DateTime.toEpochMillis(value.expiresAt)),\n failedAttempts: 0,\n consumed: false,\n }),\n },\n login: {\n encode: (value) =>\n Schema.encodeEffect(claims)(value.claims).pipe(\n Effect.flatMap((saved) => Schema.encodeEffect(record)({ ...value, claims: saved })),\n Effect.mapError(failure),\n ),\n decode: (value) =>\n decode(record, value).pipe(\n Effect.flatMap((saved) =>\n decode(claims, saved.claims).pipe(\n Effect.map((decoded) => ({ ...saved, claims: decoded })),\n ),\n ),\n ),\n },\n };\n };\n\n const sessions = <C extends Schema.Codec<unknown, unknown, never, never>>(\n claims: C,\n moduleId: string,\n ): StatefulSessionMapping<\n C[\"Type\"],\n TableModel,\n TableModel,\n TableModel,\n TableModel,\n unknown,\n unknown\n > => {\n const record = Schema.fromJsonString(\n Schema.Struct({\n ...SessionMetadata.fields,\n claims: Schema.Unknown,\n digest: TokenDigest,\n provenance: SessionAuthenticationProvenance,\n credentialVersion: SessionCredentialVersion,\n }),\n );\n\n const encodeRow = (value: StatefulSessionRecord<C[\"Type\"]>) => ({\n digest: value.digest,\n securityRevision: value.securityRevision,\n issuedAt: instant(DateTime.toEpochMillis(value.issuedAt)),\n expiresAt: instant(DateTime.toEpochMillis(value.expiresAt)),\n absoluteExpiresAt: instant(DateTime.toEpochMillis(value.absoluteExpiresAt)),\n record: Schema.encodeSync(record)({\n ...value,\n claims: Schema.encodeSync(claims)(value.claims),\n }),\n });\n\n const login = pending(claims, moduleId);\n\n return {\n ...authority(),\n moduleId,\n clock,\n isConstraintConflict: () => false,\n pending: { pending: login.pending, login: login.login },\n constraints: {\n sessionDigest: \"unique(session.digest)\",\n pendingDigest: \"unique(pending.digest)\",\n },\n sessionId: {\n toNative: (id) => Effect.succeed(id),\n toSession: (id) => decode(SessionId, id),\n equals: Object.is,\n },\n session: {\n table: table(\"sessions\"),\n sessionId: \"sessionId\",\n subjectId: \"subjectId\",\n digest: \"digest\",\n securityRevision: \"securityRevision\",\n issuedAt: \"issuedAt\",\n expiresAt: \"expiresAt\",\n absoluteExpiresAt: \"absoluteExpiresAt\",\n encodeInstant: (date) => instant(DateTime.toEpochMillis(date)),\n allocateId: allocate,\n encodeInsert: (value, ids) => ({ ...encodeRow(value), ...ids }),\n encodeRotation: encodeRow,\n decode: (row) =>\n Effect.gen(function* () {\n const saved = yield* decode(record, row.record),\n decodedClaims = yield* decode(claims, saved.claims);\n\n return {\n ...saved,\n issuedAt: DateTime.makeUnsafe(yield* readInstant(row.issuedAt)),\n claims: decodedClaims,\n };\n }),\n },\n };\n };\n\n return { table, authority, proofs, passwords, emails, sessions, pending, string };\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@yielded/auth-persistence","version":"0.1.0-beta.
|
|
1
|
+
{"name":"@yielded/auth-persistence","version":"0.1.0-beta.31","dependencies":{"@yielded/auth":"0.1.0-beta.31","@yielded/crypto":"0.1.0-beta.31"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/sql-libsql":"4.0.0","@effect/sql-sqlite-node":"4.0.0","@effect/vitest":"4.0.0","@yielded/jose":"0.1.0-beta.31","effect":"4.0.0","typescript":"7.0.2","vite":"npm:@voidzero-dev/vite-plus-core@1.1.0","vite-plus":"1.1.0","vitest":"5.0.3"},"peerDependencies":{"effect":"^4.0.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Adapter":{"types":"./dist/Adapter.d.mts","default":"./dist/Adapter.mjs"},"./OAuthPersistence":{"types":"./dist/OAuthPersistence.d.mts","default":"./dist/OAuthPersistence.mjs"},"./Testing":{"types":"./dist/Testing.d.mts","default":"./dist/Testing.mjs"}},"description":"Direct Effect SQL persistence and shared storage contracts for Yielded Auth.","homepage":"https://yielded.dev/auth/","bugs":{"url":"https://github.com/yielded-dev/auth/issues"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/yielded-dev/auth.git","directory":"packages/auth-persistence"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
|
|
@@ -97,6 +97,27 @@ export interface ProvisioningId<Id extends string> {
|
|
|
97
97
|
readonly service: "PersistenceProvisioning";
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export interface SubjectProvisioningInput<Registration> {
|
|
101
|
+
readonly requestId: string;
|
|
102
|
+
readonly identifier: LoginIdentifier;
|
|
103
|
+
readonly registration: Registration;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Return application subject values for insertion by the auth commit owner.
|
|
107
|
+
* Include the allocated native ID, active status, and initial security revision.
|
|
108
|
+
* Preparing values may allocate IDs, but must not write to the database or perform
|
|
109
|
+
* external side effects. Interactive drivers also accept an existing insert callback.
|
|
110
|
+
*/
|
|
111
|
+
export type SubjectProvisioning<Registration> =
|
|
112
|
+
| {
|
|
113
|
+
readonly values: (
|
|
114
|
+
input: SubjectProvisioningInput<Registration>,
|
|
115
|
+
) => Effect.Effect<Readonly<Record<string, unknown>>, PasswordUnavailable>;
|
|
116
|
+
}
|
|
117
|
+
| ((
|
|
118
|
+
input: SubjectProvisioningInput<Registration>,
|
|
119
|
+
) => Effect.Effect<SubjectId, PasswordUnavailable>);
|
|
120
|
+
|
|
100
121
|
export type Provisioning<A extends { readonly strategies: Readonly<Record<string, Strategy>> }> = {
|
|
101
122
|
readonly [
|
|
102
123
|
K in keyof A["strategies"] as A["strategies"][K] extends {
|
|
@@ -104,11 +125,7 @@ export type Provisioning<A extends { readonly strategies: Readonly<Record<string
|
|
|
104
125
|
}
|
|
105
126
|
? K
|
|
106
127
|
: never
|
|
107
|
-
]:
|
|
108
|
-
readonly requestId: string;
|
|
109
|
-
readonly identifier: LoginIdentifier;
|
|
110
|
-
readonly registration: Registration<A["strategies"][K]>;
|
|
111
|
-
}) => Effect.Effect<SubjectId, PasswordUnavailable>;
|
|
128
|
+
]: SubjectProvisioning<Registration<A["strategies"][K]>>;
|
|
112
129
|
};
|
|
113
130
|
|
|
114
131
|
export type ProvisioningRequirement<
|
|
@@ -217,9 +234,10 @@ export interface BoundPersistence<
|
|
|
217
234
|
value: StorageLayout<T, Roles<C, Id, A>>,
|
|
218
235
|
) => Layer.Layer<ConfigId<A["namespace"]>>;
|
|
219
236
|
};
|
|
220
|
-
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
237
|
+
/** Supply subject values through `{ values }` on every driver, including D1.
|
|
238
|
+
* Interactive drivers also accept callbacks that insert through the owning
|
|
239
|
+
* SqlClient and return the new subject ID. Never open an independent transaction
|
|
240
|
+
* or perform external side effects while preparing a subject.
|
|
223
241
|
*/
|
|
224
242
|
readonly Provisioning: Context.Service<ProvisioningRequirement<A>, Provisioning<A>>;
|
|
225
243
|
/** Capture dependencies at acquisition; validate SQL metadata and configure storage
|
|
@@ -23,7 +23,8 @@ export interface PasswordSubjectTable<Subject extends Table, _NativeSubjectId> {
|
|
|
23
23
|
readonly decodeRequirement: (
|
|
24
24
|
row: Subject["select"],
|
|
25
25
|
) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;
|
|
26
|
-
/** Current policy for this exact credential mutation
|
|
26
|
+
/** Current policy for this exact credential mutation. Interactive drivers hold
|
|
27
|
+
* the subject lock; D1 guards the physical application columns through commit. */
|
|
27
28
|
readonly decodeActionRequirement: (
|
|
28
29
|
row: Subject["select"],
|
|
29
30
|
action: PasswordAction,
|
|
@@ -14,13 +14,14 @@ import {
|
|
|
14
14
|
requiredPasskeyPersistenceConstraints,
|
|
15
15
|
} from "./models/passkey-model";
|
|
16
16
|
import type { NativeSqlTables } from "./native-sql-table";
|
|
17
|
-
import { makeNativePasskeyServices } from "./passkey-native";
|
|
17
|
+
import { makeBatchPasskeyServices, makeNativePasskeyServices } from "./passkey-native";
|
|
18
18
|
import type { PasskeyNativeMapping, PasskeyNativeRead } from "./passkey-native-state";
|
|
19
19
|
import {
|
|
20
20
|
makePasskeyNativeManagement,
|
|
21
21
|
type NativePasskeyManagementMapping,
|
|
22
22
|
} from "./passkey-native-write";
|
|
23
23
|
import { anySqlCondition } from "./sql-change";
|
|
24
|
+
import { SqlBatchCommit } from "./sql-commit";
|
|
24
25
|
import { storageTables, type StorageRole } from "./storage-tables";
|
|
25
26
|
|
|
26
27
|
export interface ComposedPasskeyInput {
|
|
@@ -255,6 +256,8 @@ const mappings = Effect.fnUntraced(function* (
|
|
|
255
256
|
/** Managed and mapped tables feed the same native passkey statements. */
|
|
256
257
|
export const makeManagedPasskeys = Effect.fnUntraced(
|
|
257
258
|
function* (input: ComposedPasskeyInput, tables: NativeSqlTables) {
|
|
259
|
+
const batch = yield* SqlBatchCommit;
|
|
260
|
+
|
|
258
261
|
const policies = yield* Effect.forEach(input.features, (feature) =>
|
|
259
262
|
Effect.map(feature.policy, (policy) => ({ feature, policy })),
|
|
260
263
|
);
|
|
@@ -275,7 +278,10 @@ export const makeManagedPasskeys = Effect.fnUntraced(
|
|
|
275
278
|
|
|
276
279
|
for (const feature of input.features) {
|
|
277
280
|
const selected = feature.management ? mapping.management(feature) : mapping.base(feature);
|
|
278
|
-
|
|
281
|
+
|
|
282
|
+
const base = yield* batch === undefined
|
|
283
|
+
? makeNativePasskeyServices(tables, selected)
|
|
284
|
+
: makeBatchPasskeyServices(tables, selected);
|
|
279
285
|
|
|
280
286
|
services.set(feature.moduleId, base);
|
|
281
287
|
if (feature.management)
|
|
@@ -6,13 +6,13 @@ import type { SubjectId } from "@yielded/auth/Schema";
|
|
|
6
6
|
import { SecurityRevision, type AuthenticationRequirement } from "@yielded/auth/Sessions";
|
|
7
7
|
import { DateTime, Effect, Option, Redacted, Schema, Crypto } from "effect";
|
|
8
8
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
9
|
-
import type
|
|
9
|
+
import { fragment, parameter, type Fragment } from "effect/sql/Statement";
|
|
10
10
|
|
|
11
11
|
import { sqlBatchAssertion } from "./d1-planning";
|
|
12
12
|
import { PersistenceMappingError } from "./mapping-error";
|
|
13
13
|
import type { AnyPasswordPersistenceMapping } from "./models/password-model";
|
|
14
14
|
import type { AnyProofPersistenceMapping } from "./models/proof-model";
|
|
15
|
-
import type
|
|
15
|
+
import { nativeSqlAlias, type NativeSqlTables, type SqlTable } from "./native-sql-table";
|
|
16
16
|
import { makePasswordCredentials, samePasswordCredentialSnapshot } from "./password-credentials";
|
|
17
17
|
import {
|
|
18
18
|
allocatePasswordNextSecurityRevision,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
registerSqlPostcondition,
|
|
34
34
|
SqlBatchCommit,
|
|
35
35
|
} from "./sql-commit";
|
|
36
|
+
import { guardSqlitePolicy } from "./sqlite-policy-guard";
|
|
36
37
|
import { requireStandalone } from "./standalone";
|
|
37
38
|
|
|
38
39
|
const unavailable = () => P.PasswordUnavailable.make({});
|
|
@@ -70,6 +71,12 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
|
|
|
70
71
|
rawPassword = tables(c.table),
|
|
71
72
|
rawAuthority = tables(a.table);
|
|
72
73
|
|
|
74
|
+
// Action policy may inspect any application column. Authority columns have
|
|
75
|
+
// separate checks and securityRevision intentionally changes in this mutation.
|
|
76
|
+
const policyColumns = rawSubject.keys.filter(
|
|
77
|
+
(key) => key !== s.id && key !== s.status && key !== s.securityRevision,
|
|
78
|
+
);
|
|
79
|
+
|
|
73
80
|
const now = tables.expression(mapping.clock.engineNowMillis);
|
|
74
81
|
const lock = sql.onDialectOrElse({ sqlite: () => sql``, orElse: () => sql`for update` });
|
|
75
82
|
|
|
@@ -193,6 +200,17 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
|
|
|
193
200
|
return {
|
|
194
201
|
nativeId,
|
|
195
202
|
subjectRow,
|
|
203
|
+
policyValues:
|
|
204
|
+
batch === undefined
|
|
205
|
+
? []
|
|
206
|
+
: policyColumns.map((key) => ({
|
|
207
|
+
column: rawSubject.selectedColumn(key),
|
|
208
|
+
value: fragment([
|
|
209
|
+
parameter(
|
|
210
|
+
rows[0][nativeSqlAlias("password_subject_", rawSubject.keys.indexOf(key))],
|
|
211
|
+
),
|
|
212
|
+
]),
|
|
213
|
+
})),
|
|
196
214
|
identifierRow,
|
|
197
215
|
passwordRow,
|
|
198
216
|
revision,
|
|
@@ -344,6 +362,14 @@ export const makeNativePasswordServices = Effect.fnUntraced(function* (
|
|
|
344
362
|
);
|
|
345
363
|
};
|
|
346
364
|
|
|
365
|
+
if (batch !== undefined)
|
|
366
|
+
yield* guardSqlitePolicy({
|
|
367
|
+
name: "password-subject-policy",
|
|
368
|
+
table: rawSubject.name,
|
|
369
|
+
owner: subjectKey(rawSubject, s.id, nativeId),
|
|
370
|
+
values: state.policyValues,
|
|
371
|
+
});
|
|
372
|
+
|
|
347
373
|
const requestedDeadline = freshUntil(input.authorization.requirement),
|
|
348
374
|
currentDeadline = freshUntil(requirement);
|
|
349
375
|
|