@yielded/auth-persistence 0.1.0-beta.22 → 0.1.0-beta.23

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.
Files changed (48) hide show
  1. package/dist/Adapter.d.mts +4 -2
  2. package/dist/Adapter.mjs +3 -1
  3. package/dist/internal/authority-snapshot.mjs +53 -0
  4. package/dist/internal/authority-snapshot.mjs.map +1 -0
  5. package/dist/internal/configuration.d.mts +1 -1
  6. package/dist/internal/configuration.mjs.map +1 -1
  7. package/dist/internal/models/password-model.d.mts +3 -53
  8. package/dist/internal/models/password-model.mjs +0 -2
  9. package/dist/internal/models/password-model.mjs.map +1 -1
  10. package/dist/internal/native-sql-table.d.mts +30 -0
  11. package/dist/internal/native-sql-table.mjs +104 -0
  12. package/dist/internal/native-sql-table.mjs.map +1 -0
  13. package/dist/internal/passkey/target.d.mts +1 -1
  14. package/dist/internal/passkeys.mjs +1 -1
  15. package/dist/internal/password-attempts.d.mts +12 -0
  16. package/dist/internal/password-attempts.mjs +197 -0
  17. package/dist/internal/password-attempts.mjs.map +1 -0
  18. package/dist/internal/password-kernel.d.mts +14 -11
  19. package/dist/internal/password-kernel.mjs +39 -202
  20. package/dist/internal/password-kernel.mjs.map +1 -1
  21. package/dist/internal/persistence.d.mts +2 -0
  22. package/dist/internal/persistence.mjs +4 -4
  23. package/dist/internal/persistence.mjs.map +1 -1
  24. package/dist/internal/session-kernel.d.mts +3 -1
  25. package/dist/internal/session-kernel.mjs +22 -12
  26. package/dist/internal/session-kernel.mjs.map +1 -1
  27. package/dist/internal/sql-persistence.mjs +3 -1
  28. package/dist/internal/sql-persistence.mjs.map +1 -1
  29. package/dist/internal/storage-mapping.mjs +1 -30
  30. package/dist/internal/storage-mapping.mjs.map +1 -1
  31. package/dist/internal/storage-tables.d.mts +0 -43
  32. package/dist/internal/storage-tables.mjs +0 -26
  33. package/dist/internal/storage-tables.mjs.map +1 -1
  34. package/dist/internal/transaction-execution-kernel.d.mts +1 -1
  35. package/dist/internal/transaction-kernel.d.mts +1 -1
  36. package/package.json +1 -1
  37. package/src/Adapter.ts +9 -4
  38. package/src/internal/authority-snapshot.ts +91 -0
  39. package/src/internal/configuration.ts +1 -6
  40. package/src/internal/models/password-model.ts +0 -64
  41. package/src/internal/native-sql-table.ts +164 -0
  42. package/src/internal/password-attempts.ts +466 -0
  43. package/src/internal/password-kernel.ts +102 -623
  44. package/src/internal/persistence.ts +5 -10
  45. package/src/internal/session-kernel.ts +89 -27
  46. package/src/internal/sql-persistence.ts +2 -0
  47. package/src/internal/storage-mapping.ts +0 -29
  48. package/src/internal/storage-tables.ts +0 -15
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-sql-table.mjs","names":["Statement"],"sources":["../../src/internal/native-sql-table.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\nimport * as Statement from \"effect/sql/Statement\";\n\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport { Table, identifier } from \"./sql-table\";\n\n/** Capture synchronous mapping callbacks without changing SQL execution errors. */\nexport const sqlMapping = <A>(thunk: () => A): Effect.Effect<A, PersistenceMappingError> =>\n Effect.try({\n try: thunk,\n catch: (cause) => PersistenceMappingError.make({ operation: \"mapping\", cause }),\n });\n\n/** Physical SQL representation only. Semantic row codecs and policy remain in\n * the owning mapping. Execute assembled statements without name/result transforms. */\nexport interface SqlTable {\n readonly name: Statement.Fragment;\n /** Alias reads only; writes continue to target the original physical table. */\n readonly as: (alias: string) => SqlTable;\n readonly column: (key: string) => Statement.Fragment;\n /** Project every column under an ordinal alias, using a distinct prefix per table. */\n readonly fields: (prefix: string) => Statement.Fragment;\n readonly decode: (\n row: Readonly<Record<string, unknown>>,\n prefix: string,\n ) => Record<string, unknown>;\n readonly value: (key: string, value: unknown) => Statement.Fragment;\n /** Complete INSERT prefix; append conflict/returning clauses in the owning operation. */\n readonly insert: (values: Readonly<Record<string, unknown>>) => Statement.Fragment;\n /** Complete UPDATE ... SET prefix; the owning operation supplies its predicate. */\n readonly update: (values: Readonly<Record<string, unknown>>) => Statement.Fragment;\n}\n\nexport type NativeSqlTables = (table: object) => SqlTable;\n\n/** Prefixes are short ASCII identifiers so ordinal aliases cannot be truncated\n * by PostgreSQL. Callers distinguish tables by prefix, never by physical names. */\nexport const nativeSqlAlias = (prefix: string, index: number): string => {\n if (!/^[A-Za-z_][A-Za-z0-9_]{0,39}$/.test(prefix))\n throw PersistenceMappingError.make({ operation: \"mapping\", cause: \"Invalid SQL alias prefix\" });\n\n return `${prefix}${index}`;\n};\n\nconst directTable = (client: SqlClient, physical: object): SqlTable => {\n if (!(physical instanceof Table))\n throw PersistenceMappingError.make({ operation: \"mapping\", cause: \"Expected a SQL table\" });\n\n const quote = (name: string) => {\n if (name.includes(\"\\0\"))\n throw PersistenceMappingError.make({ operation: \"mapping\", cause: \"NUL in SQL identifier\" });\n\n return client.literal(identifier(name));\n };\n\n const name =\n physical.schema === undefined\n ? quote(physical.name)\n : client`${quote(physical.schema)}.${quote(physical.name)}`;\n\n const columns = Object.entries(physical.columns);\n\n const getColumn = (key: string) => {\n const column = Object.hasOwn(physical.columns, key) ? physical.columns[key] : undefined;\n\n if (column === undefined)\n throw PersistenceMappingError.make({ operation: \"column\", cause: \"Missing SQL column\" });\n\n return column;\n };\n\n const value = (key: string, input: unknown): Statement.Fragment => {\n const mapped = getColumn(key);\n\n if (Statement.isFragment(input)) return input;\n const encoded = input === null ? null : mapped.mapToDriverValue(input);\n\n return Statement.fragment([\n Statement.parameter(\n typeof encoded === \"boolean\"\n ? client.onDialectOrElse({ sqlite: () => Number(encoded), orElse: () => encoded })\n : encoded,\n ),\n ]);\n };\n\n const entries = (values: Readonly<Record<string, unknown>>) =>\n Object.entries(values).filter(\n ([key, input]) => Object.hasOwn(physical.columns, key) && input !== undefined,\n );\n\n const bind = (alias?: string): SqlTable => {\n const reference = alias === undefined ? name : quote(alias);\n const column = (key: string) => client`${reference}.${quote(getColumn(key).options.name)}`;\n\n return {\n name: alias === undefined ? name : client`${name} AS ${reference}`,\n as: bind,\n column,\n value,\n fields: (prefix) =>\n client.join(\n \", \",\n false,\n )(\n columns.map(\n ([key], index) => client`${column(key)} AS ${quote(nativeSqlAlias(prefix, index))}`,\n ),\n ),\n decode: (row, prefix) =>\n Object.fromEntries(\n columns.map(([key, field], index) => {\n const alias = nativeSqlAlias(prefix, index);\n\n if (!Object.hasOwn(row, alias))\n throw PersistenceMappingError.make({\n operation: \"decode\",\n cause: \"Missing SQL projection field\",\n });\n\n return [key, field.decode(row[alias])];\n }),\n ),\n insert: (values) => {\n const fields = entries(values);\n\n if (fields.length === 0) return client`INSERT INTO ${name} DEFAULT VALUES`;\n\n return client`INSERT INTO ${name} (${client.join(\", \", false)(fields.map(([key]) => quote(getColumn(key).options.name)))}) VALUES (${client.join(\", \", false)(fields.map(([key, input]) => value(key, input)))})`;\n },\n update: (values) => {\n const fields = entries(values);\n\n if (fields.length === 0)\n throw PersistenceMappingError.make({ operation: \"mapping\", cause: \"Empty SQL update\" });\n\n return client`UPDATE ${name} SET ${client.join(\", \", false)(fields.map(([key, input]) => client`${quote(getColumn(key).options.name)} = ${value(key, input)}`))}`;\n },\n };\n };\n\n return bind();\n};\n\n/** Cache physical metadata only; insert/update materialize values and hooks at\n * each call. Construction acquires no client and owns no transaction. */\nexport const makeNativeSqlTables = (\n client: SqlClient,\n convert: (client: SqlClient, table: object) => SqlTable = directTable,\n): NativeSqlTables => {\n const tables = new WeakMap<object, SqlTable>();\n\n return (table) => {\n const existing = tables.get(table);\n\n if (existing !== undefined) return existing;\n const mapped = convert(client, table);\n\n tables.set(table, mapped);\n\n return mapped;\n };\n};\n"],"mappings":"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"}
@@ -9,9 +9,9 @@ import "./flow.mjs";
9
9
  import { TransactionCoordinatorError, TransactionExecution, TransactionTargetConfiguration } from "../transaction-execution-kernel.mjs";
10
10
  import "./registration-ceremony.mjs";
11
11
  import { Crypto, Effect, Schema } from "effect";
12
+ import { Statement } from "effect/sql/Statement";
12
13
  import { PasskeyConfigurationError, PasskeyPersistence, PasskeyUnavailable, PreparePasskeyCommit } from "@yielded/auth/Passkey";
13
14
  import { LifecycleHooks, PreparedCommit } from "@yielded/auth/Hooks";
14
- import { Statement } from "effect/sql/Statement";
15
15
  //#region src/internal/passkey/target.d.ts
16
16
  export type PasskeyTargetConfiguration = TransactionTargetConfiguration<PasskeyUnavailable>;
17
17
  export type PasskeyCoordinatorError<E> = TransactionCoordinatorError<E, PasskeyUnavailable> | PasskeyConfigurationError | PersistenceMappingError;
@@ -1,6 +1,6 @@
1
1
  import { PersistenceConfigurationError } from "./configuration.mjs";
2
- import { digest } from "./crypto.mjs";
3
2
  import { PersistenceMappingError } from "./mapping-error.mjs";
3
+ import { digest } from "./crypto.mjs";
4
4
  import { requiredPasskeyCredentialConstraints, requiredPasskeyPersistenceConstraints } from "./models/passkey-model.mjs";
5
5
  import { requiredPasskeyManagementConstraints } from "./models/passkey-write-model.mjs";
6
6
  import { requireStandalone } from "./standalone.mjs";
@@ -0,0 +1,12 @@
1
+ import "./transaction-kernel.mjs";
2
+ import "./models/password-model.mjs";
3
+ import "./native-sql-table.mjs";
4
+ import "./password-kernel.mjs";
5
+ import { Effect, Option, Redacted } from "effect";
6
+ import { LifecycleHooks } from "@yielded/auth/Hooks";
7
+ import { PasswordCredentialSnapshot, PasswordUnavailable, PreparePasswordCommit } from "@yielded/auth/Password";
8
+ import { LoginIdentifier } from "@yielded/auth/Identity";
9
+ //#region src/internal/password-attempts.d.ts
10
+ export declare const samePasswordCredentialSnapshot: (a: PasswordCredentialSnapshot, b: PasswordCredentialSnapshot) => boolean;
11
+ //#endregion
12
+ //# sourceMappingURL=password-attempts.d.mts.map
@@ -0,0 +1,197 @@
1
+ import { sqlMapping } from "./native-sql-table.mjs";
2
+ import { NativeDatabase } from "./transaction-kernel.mjs";
3
+ import { PasswordPreparedPostconditions } from "./PasswordPreparedPostconditions.mjs";
4
+ import { DateTime, Effect, Option, Redacted, Schema } from "effect";
5
+ import { CurrentCommitJournal, LifecycleHooks, coordinateCommit, hasCommitScope } from "@yielded/auth/Hooks";
6
+ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
7
+ import { PasswordUnavailable, snapshotPasswordCredential } from "@yielded/auth/Password";
8
+ //#region src/internal/password-attempts.ts
9
+ const unavailable = () => PasswordUnavailable.make({});
10
+ const nowMillis = DateTime.now.pipe(Effect.map(DateTime.toEpochMillis));
11
+ const failure = (effect) => reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(Effect.mapError(unavailable));
12
+ const samePasswordCredentialSnapshot = (a, b) => a.moduleId === b.moduleId && a.revision.subjectId === b.revision.subjectId && a.revision.securityRevision === b.revision.securityRevision && a.revision.credentials.length === b.revision.credentials.length && new Set(a.revision.credentials.map((entry) => entry.credentialId)).size === a.revision.credentials.length && new Set(b.revision.credentials.map((entry) => entry.credentialId)).size === b.revision.credentials.length && a.revision.credentials.every((entry) => b.revision.credentials.some((other) => entry.credentialId === other.credentialId && entry.revision === other.revision)) && a.credentialId === b.credentialId && a.credentialRevision === b.credentialRevision && a.verifierVersion === b.verifierVersion && Redacted.value(a.verifier) === Redacted.value(b.verifier) && a.normalization === b.normalization && a.identifier.namespace === b.identifier.namespace && a.identifier.value === b.identifier.value && a.identifierBindingRevision === b.identifierBindingRevision && a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;
13
+ /** The interactive attempt workflow uses the owning Effect SQL client directly.
14
+ * Table adapters supply representation only; admission and settlement policy live here.
15
+ */
16
+ const makePasswordAttempts = Effect.fnUntraced(function* (table, mapping, options) {
17
+ const sql = (yield* NativeDatabase).$client.withoutTransforms();
18
+ const hooks = yield* LifecycleHooks;
19
+ const parent = yield* Effect.serviceOption(CurrentCommitJournal);
20
+ const [s, i, c, a] = yield* sqlMapping(() => [
21
+ table(mapping.subject.table),
22
+ table(mapping.identifier.table),
23
+ table(mapping.credential.table),
24
+ table(mapping.attempt.table)
25
+ ]).pipe(failure);
26
+ const lock = options.locking ? sql.literal(" FOR UPDATE") : sql.literal("");
27
+ const standalone = Effect.gen(function* () {
28
+ if (options.coordinated) {
29
+ const current = yield* Effect.serviceOption(CurrentCommitJournal);
30
+ if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value) return yield* unavailable();
31
+ } else {
32
+ if (yield* hasCommitScope) return yield* unavailable();
33
+ yield* options.standaloneGuard;
34
+ }
35
+ });
36
+ const owned = (work) => standalone.pipe(Effect.andThen(coordinateCommit(() => sql.withTransaction(work), { mode: options.mode })), Effect.map((result) => result.value), Effect.provideService(LifecycleHooks, hooks), failure);
37
+ const allocate = (asynchronous, sync) => {
38
+ if (options.mode !== "synchronous" && asynchronous !== void 0) return asynchronous;
39
+ return sync === void 0 ? Effect.fail(unavailable()) : Effect.try({
40
+ try: sync,
41
+ catch: unavailable
42
+ });
43
+ };
44
+ const snapshot = Effect.fnUntraced(function* (nativeId, moduleId, identifier, locking, attemptId) {
45
+ const subjectTable = s.as("password_subject");
46
+ const identifierTable = i.as("password_identifier");
47
+ const credentialTable = c.as("password_credential");
48
+ const attemptTable = a.as("password_attempt");
49
+ const rows = yield* sqlMapping(() => sql`select ${subjectTable.fields("subject_")}, ${identifierTable.fields("identifier_")}, ${credentialTable.fields("credential_")}
50
+ ${attemptId === void 0 ? sql.literal("") : sql`, ${attemptTable.fields("attempt_")}`}
51
+ from ${subjectTable.name}
52
+ inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}
53
+ and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}
54
+ and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}
55
+ inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}
56
+ and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}
57
+ ${attemptId === void 0 ? sql.literal("") : sql`inner join ${attemptTable.name} on
58
+ ${attemptTable.column(mapping.attempt.moduleId)} = ${attemptTable.value(mapping.attempt.moduleId, moduleId)}
59
+ and ${attemptTable.column(mapping.attempt.attemptId)} = ${attemptTable.value(mapping.attempt.attemptId, attemptId)}`}
60
+ where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}
61
+ ${locking ? lock : sql.literal("")}`).pipe(Effect.flatten);
62
+ if (rows.length !== 1) return void 0;
63
+ const [subject, identifierRow, credential] = yield* sqlMapping(() => [
64
+ s.decode(rows[0], "subject_"),
65
+ i.decode(rows[0], "identifier_"),
66
+ c.decode(rows[0], "credential_")
67
+ ]);
68
+ if (yield* sqlMapping(() => !mapping.subject.isActiveStatus(subject[mapping.subject.status]) || !mapping.identifier.isCurrent(identifierRow) || !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) || !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) || !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]))) return void 0;
69
+ return {
70
+ nativeId,
71
+ captured: yield* sqlMapping(() => mapping.credential.decode({
72
+ moduleId,
73
+ subject,
74
+ identifier: identifierRow,
75
+ credential
76
+ })).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential)),
77
+ attempt: attemptId === void 0 ? void 0 : yield* sqlMapping(() => a.decode(rows[0], "attempt_"))
78
+ };
79
+ });
80
+ const attemptRow = Effect.fnUntraced(function* (moduleId, attemptId, locking) {
81
+ const rows = yield* sqlMapping(() => sql`select ${a.fields("a_")} from ${a.name}
82
+ where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, moduleId)}
83
+ and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, attemptId)}
84
+ ${locking ? lock : sql.literal("")}`).pipe(Effect.flatten);
85
+ return rows.length === 1 ? yield* sqlMapping(() => a.decode(rows[0], "a_")) : void 0;
86
+ });
87
+ const attemptMatches = (row, candidate, nativeId) => row[mapping.attempt.moduleId] === candidate.moduleId && row[mapping.attempt.identifierNamespace] === candidate.identifier.namespace && row[mapping.attempt.identifierValue] === candidate.identifier.value && mapping.subjectId.equals(row[mapping.attempt.subjectId], nativeId) && row[mapping.attempt.credentialId] === candidate.credentialId && row[mapping.attempt.securityRevision] === candidate.revision.securityRevision && row[mapping.attempt.credentialRevision] === candidate.credentialRevision && row[mapping.attempt.verifierVersion] === candidate.verifierVersion && row[mapping.attempt.identifierBindingRevision] === candidate.identifierBindingRevision;
88
+ return {
89
+ prepareAttempt: (uncaptured) => Effect.gen(function* () {
90
+ const input = Object.freeze({
91
+ ...uncaptured,
92
+ identifier: Object.freeze({ ...uncaptured.identifier })
93
+ });
94
+ yield* standalone;
95
+ const identifiers = yield* sqlMapping(() => sql`select ${i.fields("i_")} from ${i.name}
96
+ where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}
97
+ and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`).pipe(Effect.flatten);
98
+ const identifier = identifiers.length === 1 ? yield* sqlMapping(() => i.decode(identifiers[0], "i_")) : void 0;
99
+ const resolved = identifier === void 0 || !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier))) ? void 0 : yield* snapshot(identifier[mapping.identifier.subjectId], input.moduleId, input.identifier, false);
100
+ const candidate = resolved !== void 0 && (input.subjectId === void 0 || resolved.captured.revision.subjectId === input.subjectId) ? resolved : void 0;
101
+ const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
102
+ let used = false;
103
+ return Object.freeze({
104
+ ...candidate === void 0 ? {} : { credential: candidate.captured },
105
+ admit: (prepare) => Effect.suspend(() => {
106
+ if (used) return Effect.fail(unavailable());
107
+ used = true;
108
+ return owned(Effect.gen(function* () {
109
+ const journal = yield* CurrentCommitJournal;
110
+ const now = yield* nowMillis;
111
+ const captured = candidate?.captured;
112
+ yield* sqlMapping(() => sql`${a.insert(mapping.attempt.encodeInsert({
113
+ moduleId: input.moduleId,
114
+ action: input.action,
115
+ attemptId,
116
+ identifier: input.identifier,
117
+ ...captured === void 0 ? {} : {
118
+ subjectId: captured.revision.subjectId,
119
+ credentialId: captured.credentialId,
120
+ securityRevision: captured.revision.securityRevision,
121
+ credentialRevision: captured.credentialRevision,
122
+ verifierVersion: captured.verifierVersion,
123
+ identifierBindingRevision: captured.identifierBindingRevision
124
+ },
125
+ admittedAtMillis: now,
126
+ deadlineMillis: now + input.attemptLifetimeMillis,
127
+ retentionUntilMillis: now + input.attemptLifetimeMillis
128
+ }, {
129
+ ...candidate === void 0 ? {} : { nativeSubjectId: candidate.nativeId },
130
+ state: "pending"
131
+ }))}`).pipe(Effect.flatten);
132
+ return prepare({
133
+ _tag: "Admitted",
134
+ attemptId,
135
+ ...captured === void 0 ? {} : { credential: captured }
136
+ }, journal);
137
+ }));
138
+ })
139
+ });
140
+ }).pipe(failure),
141
+ settleAttempt: (uncaptured, prepare) => owned(Effect.gen(function* () {
142
+ const input = Object.freeze({
143
+ ...uncaptured,
144
+ ...uncaptured.rehash === void 0 ? {} : { rehash: Object.freeze({ ...uncaptured.rehash }) }
145
+ });
146
+ const journal = yield* CurrentCommitJournal;
147
+ const captured = input.captured === void 0 ? void 0 : yield* snapshotPasswordCredential(input.captured);
148
+ const discovered = yield* attemptRow(input.moduleId, input.attemptId, false);
149
+ if (discovered === void 0 || discovered[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
150
+ const current = input.outcome !== "verified" || captured === void 0 ? void 0 : yield* snapshot(discovered[mapping.attempt.subjectId], input.moduleId, captured.identifier, true);
151
+ const row = options.locking ? yield* attemptRow(input.moduleId, input.attemptId, true) : discovered;
152
+ if (row === void 0 || row[mapping.attempt.state] !== "pending") return prepare("rejected", journal);
153
+ const now = yield* nowMillis;
154
+ const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
155
+ const verified = captured !== void 0 && current !== void 0 && captured.moduleId === input.moduleId && deadline > now && attemptMatches(row, captured, current.nativeId) && samePasswordCredentialSnapshot(current.captured, captured);
156
+ const decision = verified ? "verified" : "rejected";
157
+ const receipt = prepare(decision, journal);
158
+ yield* sqlMapping(() => sql`${a.update({ [mapping.attempt.state]: decision })}
159
+ where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, input.moduleId)}
160
+ and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, input.attemptId)}
161
+ and ${a.column(mapping.attempt.state)} = ${a.value(mapping.attempt.state, "pending")}`).pipe(Effect.flatten);
162
+ if (verified && current !== void 0 && captured !== void 0) {
163
+ let expected = current.captured;
164
+ if (input.rehash !== void 0) {
165
+ if (input.rehash.expectedVersion !== expected.verifierVersion || Redacted.value(input.rehash.expectedVerifier) !== Redacted.value(expected.verifier)) return yield* unavailable();
166
+ const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
167
+ const rehash = input.rehash;
168
+ yield* sqlMapping(() => sql`${c.update(mapping.credential.encodeVerifier(rehash.nextVerifier, version))}
169
+ where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, input.moduleId)}
170
+ and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, current.nativeId)}
171
+ and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, captured.credentialId)}
172
+ and ${c.column(mapping.credential.credentialRevision)} = ${c.value(mapping.credential.credentialRevision, captured.credentialRevision)}
173
+ and ${c.column(mapping.credential.verifierVersion)} = ${c.value(mapping.credential.verifierVersion, rehash.expectedVersion)}
174
+ and ${c.column(mapping.credential.verifier)} = ${c.value(mapping.credential.verifier, Redacted.value(rehash.expectedVerifier))}`).pipe(Effect.flatten);
175
+ expected = {
176
+ ...expected,
177
+ verifierVersion: version,
178
+ verifier: input.rehash.nextVerifier
179
+ };
180
+ }
181
+ const settled = expected;
182
+ const applied = Effect.gen(function* () {
183
+ const final = yield* snapshot(current.nativeId, input.moduleId, captured.identifier, false, input.attemptId);
184
+ return final !== void 0 && samePasswordCredentialSnapshot(final.captured, settled) && final.attempt !== void 0 && final.attempt[mapping.attempt.state] === "verified" && attemptMatches(final.attempt, captured, current.nativeId) && (yield* mapping.decodeInstant(final.attempt[mapping.attempt.deadline])) === deadline && deadline > (yield* nowMillis);
185
+ }).pipe(failure);
186
+ if (!(yield* applied)) return yield* unavailable();
187
+ const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);
188
+ if (Option.isSome(postconditions) && !postconditions.value.register(Effect.flatMap(applied, (holds) => holds ? Effect.void : Effect.fail(unavailable())))) return yield* unavailable();
189
+ }
190
+ return receipt;
191
+ }))
192
+ };
193
+ });
194
+ //#endregion
195
+ export { makePasswordAttempts, samePasswordCredentialSnapshot };
196
+
197
+ //# sourceMappingURL=password-attempts.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-attempts.mjs","names":[],"sources":["../../src/internal/password-attempts.ts"],"sourcesContent":["import {\n coordinateCommit,\n CurrentCommitJournal,\n hasCommitScope,\n LifecycleHooks,\n} from \"@yielded/auth/Hooks\";\nimport type { LoginIdentifier } from \"@yielded/auth/Identity\";\nimport {\n type PasswordPersistence,\n PasswordUnavailable,\n snapshotPasswordCredential,\n type PasswordCredentialSnapshot,\n type PreparePasswordCommit,\n type PasswordAttemptAdmission,\n} from \"@yielded/auth/Password\";\nimport { reportPersistenceFailure } from \"@yielded/auth/Persistence\";\nimport { DateTime, Effect, Option, Redacted, Schema } from \"effect\";\n\nimport type { PersistenceMappingError } from \"./mapping-error\";\nimport type { AnyPasswordPersistenceMapping } from \"./models/password-model\";\nimport { sqlMapping, type SqlTable } from \"./native-sql-table\";\nimport type { PasswordSqlConfiguration } from \"./password-kernel\";\nimport { PasswordPreparedPostconditions } from \"./PasswordPreparedPostconditions\";\nimport { NativeDatabase } from \"./transaction-kernel\";\n\ntype Row = Readonly<Record<string, unknown>>;\ntype Mapping = AnyPasswordPersistenceMapping;\n\nconst unavailable = () => PasswordUnavailable.make({});\nconst nowMillis = DateTime.now.pipe(Effect.map(DateTime.toEpochMillis));\n\nconst failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n reportPersistenceFailure(effect, Schema.is(PasswordUnavailable)).pipe(\n Effect.mapError(unavailable),\n );\n\nexport const samePasswordCredentialSnapshot = (\n a: PasswordCredentialSnapshot,\n b: PasswordCredentialSnapshot,\n) =>\n a.moduleId === b.moduleId &&\n a.revision.subjectId === b.revision.subjectId &&\n a.revision.securityRevision === b.revision.securityRevision &&\n a.revision.credentials.length === b.revision.credentials.length &&\n new Set(a.revision.credentials.map((entry) => entry.credentialId)).size ===\n a.revision.credentials.length &&\n new Set(b.revision.credentials.map((entry) => entry.credentialId)).size ===\n b.revision.credentials.length &&\n a.revision.credentials.every((entry) =>\n b.revision.credentials.some(\n (other) => entry.credentialId === other.credentialId && entry.revision === other.revision,\n ),\n ) &&\n a.credentialId === b.credentialId &&\n a.credentialRevision === b.credentialRevision &&\n a.verifierVersion === b.verifierVersion &&\n Redacted.value(a.verifier) === Redacted.value(b.verifier) &&\n a.normalization === b.normalization &&\n a.identifier.namespace === b.identifier.namespace &&\n a.identifier.value === b.identifier.value &&\n a.identifierBindingRevision === b.identifierBindingRevision &&\n a.identifierVerifiedAtMillis === b.identifierVerifiedAtMillis;\n\n/** The interactive attempt workflow uses the owning Effect SQL client directly.\n * Table adapters supply representation only; admission and settlement policy live here.\n */\nexport const makePasswordAttempts = Effect.fnUntraced(function* (\n table: (table: object) => SqlTable,\n mapping: Mapping,\n options: PasswordSqlConfiguration,\n): Effect.fn.Return<\n Pick<PasswordPersistence[\"Service\"], \"prepareAttempt\" | \"settleAttempt\">,\n PasswordUnavailable,\n LifecycleHooks | NativeDatabase\n> {\n const sql = (yield* NativeDatabase).$client.withoutTransforms();\n const hooks = yield* LifecycleHooks;\n const parent = yield* Effect.serviceOption(CurrentCommitJournal);\n\n const [s, i, c, a] = yield* sqlMapping(\n () =>\n [\n table(mapping.subject.table),\n table(mapping.identifier.table),\n table(mapping.credential.table),\n table(mapping.attempt.table),\n ] as const,\n ).pipe(failure);\n\n const lock = options.locking ? sql.literal(\" FOR UPDATE\") : sql.literal(\"\");\n\n const standalone = Effect.gen(function* () {\n if (options.coordinated) {\n const current = yield* Effect.serviceOption(CurrentCommitJournal);\n\n if (Option.isNone(parent) || Option.isNone(current) || parent.value !== current.value)\n return yield* unavailable();\n } else {\n if (yield* hasCommitScope) return yield* unavailable();\n yield* options.standaloneGuard;\n }\n });\n\n const owned = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n standalone.pipe(\n Effect.andThen(coordinateCommit(() => sql.withTransaction(work), { mode: options.mode })),\n Effect.map((result) => result.value),\n Effect.provideService(LifecycleHooks, hooks),\n failure,\n );\n\n const allocate = <A>(\n asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,\n sync: (() => A) | undefined,\n ) => {\n if (options.mode !== \"synchronous\" && asynchronous !== undefined) return asynchronous;\n\n return sync === undefined\n ? Effect.fail(unavailable())\n : Effect.try({ try: sync, catch: unavailable });\n };\n\n // Bind each native ID through its own column codec. Physical encodings of the\n // same application ID need not match across subject, identifier and password.\n const snapshot = Effect.fnUntraced(function* (\n nativeId: unknown,\n moduleId: string,\n identifier: LoginIdentifier,\n locking: boolean,\n attemptId?: string,\n ) {\n const subjectTable = s.as(\"password_subject\");\n const identifierTable = i.as(\"password_identifier\");\n const credentialTable = c.as(\"password_credential\");\n const attemptTable = a.as(\"password_attempt\");\n\n const rows = yield* sqlMapping(\n () =>\n sql<Row>`select ${subjectTable.fields(\"subject_\")}, ${identifierTable.fields(\"identifier_\")}, ${credentialTable.fields(\"credential_\")}\n ${attemptId === undefined ? sql.literal(\"\") : sql`, ${attemptTable.fields(\"attempt_\")}`}\n from ${subjectTable.name}\n inner join ${identifierTable.name} on ${identifierTable.column(mapping.identifier.namespace)} = ${identifierTable.value(mapping.identifier.namespace, identifier.namespace)}\n and ${identifierTable.column(mapping.identifier.value)} = ${identifierTable.value(mapping.identifier.value, identifier.value)}\n and ${identifierTable.column(mapping.identifier.subjectId)} = ${identifierTable.value(mapping.identifier.subjectId, nativeId)}\n inner join ${credentialTable.name} on ${credentialTable.column(mapping.credential.moduleId)} = ${credentialTable.value(mapping.credential.moduleId, moduleId)}\n and ${credentialTable.column(mapping.credential.subjectId)} = ${credentialTable.value(mapping.credential.subjectId, nativeId)}\n ${\n attemptId === undefined\n ? sql.literal(\"\")\n : sql`inner join ${attemptTable.name} on\n ${attemptTable.column(mapping.attempt.moduleId)} = ${attemptTable.value(mapping.attempt.moduleId, moduleId)}\n and ${attemptTable.column(mapping.attempt.attemptId)} = ${attemptTable.value(mapping.attempt.attemptId, attemptId)}`\n }\n where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeId)}\n ${locking ? lock : sql.literal(\"\")}`,\n ).pipe(Effect.flatten);\n\n if (rows.length !== 1) return undefined;\n\n const [subject, identifierRow, credential] = yield* sqlMapping(\n () =>\n [\n s.decode(rows[0]!, \"subject_\"),\n i.decode(rows[0]!, \"identifier_\"),\n c.decode(rows[0]!, \"credential_\"),\n ] as const,\n );\n\n if (\n yield* sqlMapping(\n () =>\n !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||\n !mapping.identifier.isCurrent(identifierRow) ||\n !mapping.subjectId.equals(nativeId, identifierRow[mapping.identifier.subjectId]) ||\n !mapping.subjectId.equals(nativeId, subject[mapping.subject.id]) ||\n !mapping.subjectId.equals(nativeId, credential[mapping.credential.subjectId]),\n )\n )\n return undefined;\n\n const captured = yield* sqlMapping(() =>\n mapping.credential.decode({ moduleId, subject, identifier: identifierRow, credential }),\n ).pipe(Effect.flatten, Effect.flatMap(snapshotPasswordCredential));\n\n return {\n nativeId,\n captured,\n attempt:\n attemptId === undefined\n ? undefined\n : yield* sqlMapping(() => a.decode(rows[0]!, \"attempt_\")),\n };\n });\n\n const attemptRow = Effect.fnUntraced(function* (\n moduleId: string,\n attemptId: string,\n locking: boolean,\n ) {\n const rows = yield* sqlMapping(\n () => sql<Row>`select ${a.fields(\"a_\")} from ${a.name}\n where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, moduleId)}\n and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, attemptId)}\n ${locking ? lock : sql.literal(\"\")}`,\n ).pipe(Effect.flatten);\n\n return rows.length === 1 ? yield* sqlMapping(() => a.decode(rows[0]!, \"a_\")) : undefined;\n });\n\n const attemptMatches = (row: Row, candidate: PasswordCredentialSnapshot, nativeId: unknown) =>\n row[mapping.attempt.moduleId] === candidate.moduleId &&\n row[mapping.attempt.identifierNamespace] === candidate.identifier.namespace &&\n row[mapping.attempt.identifierValue] === candidate.identifier.value &&\n mapping.subjectId.equals(row[mapping.attempt.subjectId], nativeId) &&\n row[mapping.attempt.credentialId] === candidate.credentialId &&\n row[mapping.attempt.securityRevision] === candidate.revision.securityRevision &&\n row[mapping.attempt.credentialRevision] === candidate.credentialRevision &&\n row[mapping.attempt.verifierVersion] === candidate.verifierVersion &&\n row[mapping.attempt.identifierBindingRevision] === candidate.identifierBindingRevision;\n\n const service: Pick<PasswordPersistence[\"Service\"], \"prepareAttempt\" | \"settleAttempt\"> = {\n // The remaining operations are composed by the persistence owner.\n prepareAttempt: (uncaptured) =>\n Effect.gen(function* () {\n const input = Object.freeze({\n ...uncaptured,\n identifier: Object.freeze({ ...uncaptured.identifier }),\n });\n\n yield* standalone;\n\n const identifiers = yield* sqlMapping(\n () => sql<Row>`select ${i.fields(\"i_\")} from ${i.name}\n where ${i.column(mapping.identifier.namespace)} = ${i.value(mapping.identifier.namespace, input.identifier.namespace)}\n and ${i.column(mapping.identifier.value)} = ${i.value(mapping.identifier.value, input.identifier.value)}`,\n ).pipe(Effect.flatten);\n\n const identifier =\n identifiers.length === 1\n ? yield* sqlMapping(() => i.decode(identifiers[0]!, \"i_\"))\n : undefined;\n\n const resolved =\n identifier === undefined ||\n !(yield* sqlMapping(() => mapping.identifier.isCurrent(identifier)))\n ? undefined\n : yield* snapshot(\n identifier[mapping.identifier.subjectId],\n input.moduleId,\n input.identifier,\n false,\n );\n\n const candidate =\n resolved !== undefined &&\n (input.subjectId === undefined ||\n resolved.captured.revision.subjectId === input.subjectId)\n ? resolved\n : undefined;\n\n const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);\n let used = false;\n\n return Object.freeze({\n ...(candidate === undefined ? {} : { credential: candidate.captured }),\n admit: <A>(prepare: PreparePasswordCommit<PasswordAttemptAdmission, A>) =>\n Effect.suspend(() => {\n if (used) return Effect.fail(unavailable());\n used = true;\n\n return owned(\n Effect.gen(function* () {\n const journal = yield* CurrentCommitJournal;\n const now = yield* nowMillis;\n const captured = candidate?.captured;\n\n // This receipt records what was inspected; it grants no authority.\n // Settlement checks the current database against this exact snapshot.\n yield* sqlMapping(\n () =>\n sql`${a.insert(\n mapping.attempt.encodeInsert(\n {\n moduleId: input.moduleId,\n action: input.action,\n attemptId,\n identifier: input.identifier,\n ...(captured === undefined\n ? {}\n : {\n subjectId: captured.revision.subjectId,\n credentialId: captured.credentialId,\n securityRevision: captured.revision.securityRevision,\n credentialRevision: captured.credentialRevision,\n verifierVersion: captured.verifierVersion,\n identifierBindingRevision: captured.identifierBindingRevision,\n }),\n admittedAtMillis: now,\n deadlineMillis: now + input.attemptLifetimeMillis,\n retentionUntilMillis: now + input.attemptLifetimeMillis,\n },\n {\n ...(candidate === undefined\n ? {}\n : { nativeSubjectId: candidate.nativeId }),\n state: \"pending\",\n },\n ),\n )}`,\n ).pipe(Effect.flatten);\n\n return prepare(\n {\n _tag: \"Admitted\",\n attemptId,\n ...(captured === undefined ? {} : { credential: captured }),\n },\n journal,\n );\n }),\n );\n }),\n });\n }).pipe(failure),\n settleAttempt: (uncaptured, prepare) =>\n owned(\n Effect.gen(function* () {\n const input = Object.freeze({\n ...uncaptured,\n ...(uncaptured.rehash === undefined\n ? {}\n : { rehash: Object.freeze({ ...uncaptured.rehash }) }),\n });\n\n const journal = yield* CurrentCommitJournal;\n\n const captured =\n input.captured === undefined\n ? undefined\n : yield* snapshotPasswordCredential(input.captured);\n\n const discovered = yield* attemptRow(input.moduleId, input.attemptId, false);\n\n if (discovered === undefined || discovered[mapping.attempt.state] !== \"pending\")\n return prepare(\"rejected\", journal);\n\n // Lock the subject before the attempt: a coordinated application\n // transaction may already hold the subject lock. Without row locks\n // the discovery read is already serialized, so it is not repeated.\n const current =\n input.outcome !== \"verified\" || captured === undefined\n ? undefined\n : yield* snapshot(\n discovered[mapping.attempt.subjectId],\n input.moduleId,\n captured.identifier,\n true,\n );\n\n const row = options.locking\n ? yield* attemptRow(input.moduleId, input.attemptId, true)\n : discovered;\n\n if (row === undefined || row[mapping.attempt.state] !== \"pending\")\n return prepare(\"rejected\", journal);\n const now = yield* nowMillis;\n const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);\n\n const verified =\n captured !== undefined &&\n current !== undefined &&\n captured.moduleId === input.moduleId &&\n deadline > now &&\n attemptMatches(row, captured, current.nativeId) &&\n samePasswordCredentialSnapshot(current.captured, captured);\n\n const decision = verified ? \"verified\" : \"rejected\";\n const receipt = prepare(decision, journal);\n\n yield* sqlMapping(\n () => sql`${a.update({ [mapping.attempt.state]: decision })}\n where ${a.column(mapping.attempt.moduleId)} = ${a.value(mapping.attempt.moduleId, input.moduleId)}\n and ${a.column(mapping.attempt.attemptId)} = ${a.value(mapping.attempt.attemptId, input.attemptId)}\n and ${a.column(mapping.attempt.state)} = ${a.value(mapping.attempt.state, \"pending\")}`,\n ).pipe(Effect.flatten);\n\n if (verified && current !== undefined && captured !== undefined) {\n let expected = current.captured;\n\n if (input.rehash !== undefined) {\n if (\n input.rehash.expectedVersion !== expected.verifierVersion ||\n Redacted.value(input.rehash.expectedVerifier) !== Redacted.value(expected.verifier)\n )\n return yield* unavailable();\n\n const version = yield* allocate(\n mapping.allocateRevision,\n mapping.allocateRevisionSync,\n );\n\n const rehash = input.rehash;\n\n yield* sqlMapping(\n () => sql`${c.update(mapping.credential.encodeVerifier(rehash.nextVerifier, version))}\n where ${c.column(mapping.credential.moduleId)} = ${c.value(mapping.credential.moduleId, input.moduleId)}\n and ${c.column(mapping.credential.subjectId)} = ${c.value(mapping.credential.subjectId, current.nativeId)}\n and ${c.column(mapping.credential.credentialId)} = ${c.value(mapping.credential.credentialId, captured.credentialId)}\n and ${c.column(mapping.credential.credentialRevision)} = ${c.value(mapping.credential.credentialRevision, captured.credentialRevision)}\n and ${c.column(mapping.credential.verifierVersion)} = ${c.value(mapping.credential.verifierVersion, rehash.expectedVersion)}\n and ${c.column(mapping.credential.verifier)} = ${c.value(mapping.credential.verifier, Redacted.value(rehash.expectedVerifier))}`,\n ).pipe(Effect.flatten);\n expected = {\n ...expected,\n verifierVersion: version,\n verifier: input.rehash.nextVerifier,\n };\n }\n\n const settled = expected;\n\n const applied = Effect.gen(function* () {\n const final = yield* snapshot(\n current.nativeId,\n input.moduleId,\n captured.identifier,\n false,\n input.attemptId,\n );\n\n return (\n final !== undefined &&\n samePasswordCredentialSnapshot(final.captured, settled) &&\n final.attempt !== undefined &&\n final.attempt[mapping.attempt.state] === \"verified\" &&\n attemptMatches(final.attempt, captured, current.nativeId) &&\n (yield* mapping.decodeInstant(final.attempt[mapping.attempt.deadline])) ===\n deadline &&\n deadline > (yield* nowMillis)\n );\n }).pipe(failure);\n\n if (!(yield* applied)) return yield* unavailable();\n\n // A coordinator that accepts postconditions repeats this check after\n // application work, inside the same transaction.\n const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);\n\n if (\n Option.isSome(postconditions) &&\n !postconditions.value.register(\n Effect.flatMap(applied, (holds) =>\n holds ? Effect.void : Effect.fail(unavailable()),\n ),\n )\n )\n return yield* unavailable();\n }\n\n return receipt;\n }),\n ),\n };\n\n return service;\n});\n"],"mappings":"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"}
@@ -3,12 +3,15 @@ import { NativeDatabase } from "./transaction-kernel.mjs";
3
3
  import { QueryFailure, QueryOperations, SqlColumn, SqlFragment } from "./query-operations.mjs";
4
4
  import { AnyPasswordPersistenceMapping } from "./models/password-model.mjs";
5
5
  import { CurrentProofSql, ProofSqlConfiguration, makeProofKernel } from "./proof-kernel.mjs";
6
+ import { NativeSqlTables } from "./native-sql-table.mjs";
7
+ import { samePasswordCredentialSnapshot } from "./password-attempts.mjs";
6
8
  import { Context, DateTime, Effect, Option, Redacted } from "effect";
7
9
  import { CurrentCommitJournal, HookConfigurationError, LifecycleHooks, PreparedCommit } from "@yielded/auth/Hooks";
8
10
  import { AuthenticationEvidence, AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
9
11
  import { ProofCompletionInput, ProofCompletionPlan } from "@yielded/auth/Proofs";
10
- import { PasswordAttemptDecision, PasswordAttemptPolicy, PasswordCredentialSnapshot, PasswordMutationDecision, PasswordMutationInput, PasswordUnavailable } from "@yielded/auth/Password";
12
+ import { PasswordMutationDecision, PasswordMutationInput, PasswordUnavailable } from "@yielded/auth/Password";
11
13
  import { LoginIdentifier } from "@yielded/auth/Identity";
14
+ import { SqlClient } from "effect/sql/SqlClient";
12
15
  import * as SqlError$1 from "effect/sql/SqlError";
13
16
  //#region src/internal/password-kernel.d.ts
14
17
  type AdapterFailure = QueryFailure | PersistenceMappingError | SqlError$1.SqlError;
@@ -47,34 +50,34 @@ export interface PasswordSqlConfiguration {
47
50
  };
48
51
  }
49
52
  type Database = PasswordSqlDatabase;
50
- export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFragment, Column extends SqlColumn = SqlColumn>(operations: QueryOperations<Fragment, Column>, proofs: Pick<ReturnType<typeof makeProofKernel>, "completeProofPlanIn">) => {
53
+ export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFragment, Column extends SqlColumn = SqlColumn>(operations: QueryOperations<Fragment, Column>, proofs: Pick<ReturnType<typeof makeProofKernel>, "completeProofPlanIn">, nativeTables?: (client: SqlClient, database: object) => NativeSqlTables) => {
51
54
  makeSqlPasswordPersistence: (mapping: AnyPasswordPersistenceMapping<Fragment>, configuration: PasswordSqlConfiguration) => Effect.Effect<{
52
- readonly admitAttempt: <A>(input: {
55
+ readonly prepareAttempt: (input: {
53
56
  readonly moduleId: string;
54
57
  readonly action: "sign-in" | "change";
55
58
  readonly identifier: LoginIdentifier;
56
59
  readonly subjectId?: import("@yielded/auth/Schema").SubjectId;
57
- readonly policy: PasswordAttemptPolicy;
58
- }, prepare: import("@yielded/auth/Password").PreparePasswordCommit<import("@yielded/auth/Password").PasswordAttemptAdmission, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
60
+ readonly attemptLifetimeMillis: number;
61
+ }) => Effect.Effect<import("@yielded/auth/Password").PasswordAttemptPreparation, PasswordUnavailable>;
59
62
  readonly settleAttempt: <A>(input: {
60
63
  readonly moduleId: string;
61
64
  readonly attemptId: import("@yielded/auth/Password").PasswordAttemptId;
62
- readonly captured?: PasswordCredentialSnapshot;
63
- readonly outcome: PasswordAttemptDecision;
65
+ readonly captured?: import("@yielded/auth/Password").PasswordCredentialSnapshot;
66
+ readonly outcome: import("@yielded/auth/Password").PasswordAttemptDecision;
64
67
  readonly rehash?: {
65
68
  readonly expectedVersion: SecurityRevision;
66
69
  readonly expectedVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
67
70
  readonly nextVerifier: Redacted.Redacted<import("@yielded/auth/Password").EncodedPasswordHash>;
68
71
  };
69
- }, prepare: import("@yielded/auth/Password").PreparePasswordCommit<PasswordAttemptDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
72
+ }, prepare: import("@yielded/auth/Password").PreparePasswordCommit<import("@yielded/auth/Password").PasswordAttemptDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
70
73
  readonly readForSubject: (input: {
71
74
  readonly moduleId: string;
72
75
  readonly subjectId: import("@yielded/auth/Schema").SubjectId;
73
- }) => Effect.Effect<Option.Option<PasswordCredentialSnapshot>, PasswordUnavailable>;
76
+ }) => Effect.Effect<Option.Option<import("@yielded/auth/Password").PasswordCredentialSnapshot>, PasswordUnavailable>;
74
77
  readonly recoveryTarget: (input: {
75
78
  readonly moduleId: string;
76
79
  readonly identifier: LoginIdentifier;
77
- }) => Effect.Effect<Option.Option<PasswordCredentialSnapshot>, PasswordUnavailable>;
80
+ }) => Effect.Effect<Option.Option<import("@yielded/auth/Password").PasswordCredentialSnapshot>, PasswordUnavailable>;
78
81
  readonly addIfAbsent: <A>(input: PasswordMutationInput, prepare: import("@yielded/auth/Password").PreparePasswordCommit<PasswordMutationDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
79
82
  readonly replaceIfCurrent: <A>(input: PasswordMutationInput, prepare: import("@yielded/auth/Password").PreparePasswordCommit<PasswordMutationDecision, A>) => Effect.Effect<PreparedCommit<A>, PasswordUnavailable>;
80
83
  readonly checkReset: (input: ProofCompletionInput) => Effect.Effect<boolean, PasswordUnavailable>;
@@ -255,7 +258,7 @@ export declare const makePasswordKernel: <Fragment extends SqlFragment = SqlFrag
255
258
  readonly nextSecurityRevision: SecurityRevision;
256
259
  }, now: number) => Effect.Effect<boolean, AdapterFailure, CurrentPasswordSql>;
257
260
  evidenceSatisfiedAt: (evidence: AuthenticationEvidence, requirement: AuthenticationRequirement, now: number) => boolean;
258
- sameCredentialSnapshot: (left: PasswordCredentialSnapshot, right: PasswordCredentialSnapshot) => boolean;
261
+ sameCredentialSnapshot: typeof samePasswordCredentialSnapshot;
259
262
  subjectColumns: (mapping: AnyPasswordPersistenceMapping<Fragment>) => {
260
263
  id: Column;
261
264
  status: Column;