pipework 0.61.0 → 0.63.0

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 (87) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/REFERENCE.md +53 -0
  3. package/dist/REFERENCE.md +67 -3
  4. package/dist/auth/auth/index.d.ts +4 -0
  5. package/dist/auth/auth/index.d.ts.map +1 -1
  6. package/dist/auth/auth/index.js +2 -0
  7. package/dist/auth/auth/index.js.map +1 -1
  8. package/dist/auth/auth/namespace.d.ts +6 -0
  9. package/dist/auth/auth/namespace.d.ts.map +1 -1
  10. package/dist/auth/auth/namespace.js +6 -0
  11. package/dist/auth/auth/namespace.js.map +1 -1
  12. package/dist/auth/auth/passwords.d.ts +39 -0
  13. package/dist/auth/auth/passwords.d.ts.map +1 -0
  14. package/dist/auth/auth/passwords.js +76 -0
  15. package/dist/auth/auth/passwords.js.map +1 -0
  16. package/dist/auth/auth/schema.d.ts +27 -0
  17. package/dist/auth/auth/schema.d.ts.map +1 -1
  18. package/dist/auth/auth/schema.js +10 -1
  19. package/dist/auth/auth/schema.js.map +1 -1
  20. package/dist/auth/auth/sign-in.d.ts +64 -0
  21. package/dist/auth/auth/sign-in.d.ts.map +1 -0
  22. package/dist/auth/auth/sign-in.js +135 -0
  23. package/dist/auth/auth/sign-in.js.map +1 -0
  24. package/dist/core/config/namespace.d.ts +2 -0
  25. package/dist/core/config/namespace.d.ts.map +1 -1
  26. package/dist/core/config/schema.d.ts +2 -0
  27. package/dist/core/config/schema.d.ts.map +1 -1
  28. package/dist/core/config/schema.js +4 -0
  29. package/dist/core/config/schema.js.map +1 -1
  30. package/dist/core/errors/http.d.ts +6 -0
  31. package/dist/core/errors/http.d.ts.map +1 -1
  32. package/dist/core/errors/http.js +9 -0
  33. package/dist/core/errors/http.js.map +1 -1
  34. package/dist/core/errors/index.d.ts +1 -1
  35. package/dist/core/errors/index.d.ts.map +1 -1
  36. package/dist/core/errors/index.js +1 -1
  37. package/dist/core/errors/index.js.map +1 -1
  38. package/dist/core/errors/namespace.d.ts +3 -1
  39. package/dist/core/errors/namespace.d.ts.map +1 -1
  40. package/dist/core/errors/namespace.js +3 -1
  41. package/dist/core/errors/namespace.js.map +1 -1
  42. package/dist/data/migrate/generate.d.ts.map +1 -1
  43. package/dist/data/migrate/generate.js +2 -1
  44. package/dist/data/migrate/generate.js.map +1 -1
  45. package/dist/data/migrate/internal-definitions.d.ts.map +1 -1
  46. package/dist/data/migrate/internal-definitions.js +9 -0
  47. package/dist/data/migrate/internal-definitions.js.map +1 -1
  48. package/dist/index.d.ts +5 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +2 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/infra/email/audit.d.ts +3 -0
  53. package/dist/infra/email/audit.d.ts.map +1 -0
  54. package/dist/infra/email/audit.js +25 -0
  55. package/dist/infra/email/audit.js.map +1 -0
  56. package/dist/infra/email/index.d.ts +5 -0
  57. package/dist/infra/email/index.d.ts.map +1 -0
  58. package/dist/infra/email/index.js +5 -0
  59. package/dist/infra/email/index.js.map +1 -0
  60. package/dist/infra/email/mail.d.ts +69 -0
  61. package/dist/infra/email/mail.d.ts.map +1 -0
  62. package/dist/infra/email/mail.js +144 -0
  63. package/dist/infra/email/mail.js.map +1 -0
  64. package/dist/infra/email/namespace.d.ts +15 -0
  65. package/dist/infra/email/namespace.d.ts.map +1 -0
  66. package/dist/infra/email/namespace.js +15 -0
  67. package/dist/infra/email/namespace.js.map +1 -0
  68. package/dist/infra/email/providers.d.ts +42 -0
  69. package/dist/infra/email/providers.d.ts.map +1 -0
  70. package/dist/infra/email/providers.js +71 -0
  71. package/dist/infra/email/providers.js.map +1 -0
  72. package/dist/infra/email/schema.d.ts +74 -0
  73. package/dist/infra/email/schema.d.ts.map +1 -0
  74. package/dist/infra/email/schema.js +39 -0
  75. package/dist/infra/email/schema.js.map +1 -0
  76. package/dist/infra/email/sigv4.d.ts +24 -0
  77. package/dist/infra/email/sigv4.d.ts.map +1 -0
  78. package/dist/infra/email/sigv4.js +55 -0
  79. package/dist/infra/email/sigv4.js.map +1 -0
  80. package/dist/infra/email/sns.d.ts +32 -0
  81. package/dist/infra/email/sns.d.ts.map +1 -0
  82. package/dist/infra/email/sns.js +173 -0
  83. package/dist/infra/email/sns.js.map +1 -0
  84. package/dist/request/http/server.d.ts.map +1 -1
  85. package/dist/request/http/server.js +3 -0
  86. package/dist/request/http/server.js.map +1 -1
  87. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.63.0
4
+
5
+ **Schema note: blueprint `email: {}` adds `pipework_email_messages` (`key` unique, `to_address`, `template`, `data`, `status`, `provider_message_id`, `error`, `trace_id`, `created_at`, `sent_at`, `sending_at`) and `pipework_email_suppressions` (`address` primary key, `reason`, `detail`, `created_at`) to generated migrations; not tenant-scoped. Run `pipework generate` and `pipework migrate` to add them.**
6
+
7
+ - **New: `mail.create` sends transactional email through your job queue.** `send(db, { to, template, data, key })` records the message and enqueues its job in the caller's transaction (a rollback sends nothing); the same `key` sends once. Templates stay in the app as functions returning `{ subject, text, html? }`. Spread `mail.jobs` into your worker surface; the send runs in the trace of the flow that called `send`.
8
+ - **New: providers.** `mail.ses({ region, credentials, configurationSet? })` calls the SES v2 API with SigV4 (no AWS SDK); `mail.devStandIn({ dir })` writes each message to a file and `mail.create` refuses it when the environment is production.
9
+ - **New: bounces and complaints suppress the address.** `mail.snsWebhook({ path, topicArns, region? })` receives SES events through SNS, verifies the SNS signature, certificate host and topic, confirms the subscription, and puts the recipient of a permanent bounce or a complaint on a do-not-send list the job checks. Each one is audited (`Email`: `bounce`, `complaint`) with the original send's trace id. EventBridge delivery is not supported.
10
+
11
+ ## 0.62.0
12
+
13
+ **Schema note: blueprint `passwords: {}` adds `pipework_signin_attempts` to generated migrations (`key` text primary key, `failures`, `first_failure_at`, `last_failure_at`, `locked_until`; not tenant-scoped). Run `pipework generate` and `pipework migrate` to add it.**
14
+
15
+ - **New: `auth.passwords` hashes and verifies passwords.** scrypt with a PHC-string hash that records its parameters, constant-time verify, and `needsRehash` when the configured parameters were raised. No composition rules; passwords up to 4096 bytes (long passphrases, pasted passwords) work. `verifyNone` costs the same as a wrong password, so an unknown account is not distinguishable by timing.
16
+ - **New: `auth.signIn` slows, then refuses, repeated failed sign-ins.** `attempt({ account, client?, password, lookup })` runs in the current Flow but outside the request transaction, so a route can throw `UnauthorizedError` and the count stays. Attempts are counted per account and per client address in Postgres (shared across processes, surviving restarts) before the password is checked, so parallel guesses are locked out; past the threshold each failure locks for a doubling, capped time and attempts are refused with `retryAfterSeconds`. A success resets the account's count. Failures, refusals and successes are logged and audited (`SignIn`: `fail`, `locked`, `success`).
17
+ - **New: `errors.TooManyRequests` (`TooManyRequestsError`, 429).** The HTTP server sends `Retry-After` when an error carries `retryAfterSeconds`.
18
+
3
19
  ## 0.61.0
4
20
 
5
21
  **Schema note: apps using `auth.createSessions` / `auth.createMultiOrg` can now get their tables from `pipework generate`. Existing tables from the hand-copied SQL keep working (the statements are `IF NOT EXISTS`); to type their ids uuid, run `ALTER TABLE pipework_refresh_tokens ALTER COLUMN tenant_id TYPE uuid USING tenant_id::uuid` and `ALTER TABLE pipework_user_orgs ALTER COLUMN org_id TYPE uuid USING org_id::uuid` once.**
package/REFERENCE.md CHANGED
@@ -27,6 +27,7 @@
27
27
  - [audit — Audit Logging](#audit-audit-logging)
28
28
  - [cache — Caching](#cache-caching)
29
29
  - [webhook — Inbound & Outbound Webhooks](#webhook-inbound-outbound-webhooks)
30
+ - [mail — Transactional Email](#mail-transactional-email)
30
31
  - [accessLinks — Access Links](#accesslinks-access-links)
31
32
  - [behavior — Resource Decorators](#behavior-resource-decorators)
32
33
  - [fitting — Dependency Injection](#fitting-dependency-injection)
@@ -292,6 +293,24 @@ Creates multi-org session management — org selection, switching, membership CR
292
293
 
293
294
  Opt in with blueprint `orgs: {}` (plus `sessions: {}`). The generated migration then includes `pipework_user_orgs` (`user_id` text, `org_id` uuid, `role`, `joined_at`, `added_by`, unique on `(user_id, org_id)`). It is deliberately not tenant-scoped: a user's orgs are read across orgs at sign-in. An `orgId` that is not a uuid throws a `ValidationError`. The statements are `IF NOT EXISTS`; if your existing table has `org_id text` holding uuids, run once: `ALTER TABLE pipework_user_orgs ALTER COLUMN org_id TYPE uuid USING org_id::uuid`. The definition is for the default table name only: with a custom `table`, the app owns that table's DDL.
294
295
 
296
+ ### `auth.passwords`
297
+
298
+ Hashes and verifies passwords with scrypt; reports when a stored hash needs rehashing.
299
+
300
+ `auth.passwords({ scrypt?: { ln, r, p } })` returns `{ hash, verify, verifyNone }`. Defaults are `ln=15, r=8, p=3`. `hash(password)` returns a PHC string `$scrypt$ln=15,r=8,p=3$<salt>$<hash>` (16-byte salt, 32-byte key). Passwords are NFC-normalized; there are no composition rules, and up to 4096 bytes of UTF-8 are accepted (more throws a `ValidationError`), so long passphrases and pasted passwords work. `verify(stored, password)` compares in constant time and returns `{ ok, needsRehash }`; `needsRehash` is true when the password matched but the stored parameters differ from the configured ones. A malformed stored string returns `ok: false` and logs an error. `verifyNone(password)` spends the cost of a verify against a fixed dummy hash, so an unknown account takes as long as a wrong password.
301
+
302
+ ### `auth.signIn`
303
+
304
+ Guarded password sign-in that slows, then refuses, repeated failures per account and per client.
305
+
306
+ Opt in with blueprint `passwords: {}`; the generated migration adds `pipework_signin_attempts` (`key` text primary key, `failures`, `first_failure_at`, `last_failure_at`, `locked_until`), `IF NOT EXISTS`, not tenant-scoped (a sign-in happens before any tenant is known). Counts live in Postgres, so they survive restarts and are shared across processes. `attempt` needs an active Flow and runs every statement on `pipe.system()`, outside the request transaction, so the count is kept even when the route answers a bad password by throwing `UnauthorizedError`.
307
+
308
+ `auth.signIn({ passwords, accountThreshold = 5, clientThreshold = 20, windowSeconds = 900, maxLockSeconds = 900 })` returns `{ attempt({ account, client?, password, lookup }) }`, where `lookup(db, account)` (given `pipe.system()`, a pre-tenant read) returns `{ userId, passwordHash }` or `null`. The result is `{ ok: true, userId, needsRehash }` or `{ ok: false, reason: 'invalid' | 'locked', retryAfterSeconds? }`. An unknown account runs a dummy verify and is reported as `'invalid'`, exactly like a wrong password. Keys are `account:<sha256 of the NFC-lowercased trimmed account>` and `client:<client>` (omitted when no client is given). At or above its threshold, each further failure locks the key for `2^(failures - threshold)` seconds, capped at `maxLockSeconds`; a failure more than `windowSeconds` after the first restarts the count. Each attempt is counted, atomically per key, before the password is checked, so a burst of parallel guesses is locked out rather than all verified. While any key is locked an attempt returns `reason: 'locked'` with `retryAfterSeconds` at once, without checking the password. A success deletes the account key and gives the client key its count back.
309
+
310
+ Tradeoff: locking is per account, so anyone who knows an account name can lock it out by failing on purpose. Each lock is capped (15 minutes by default), and the client key limits how fast one address can do it.
311
+
312
+ Every success, failure and refusal is logged, and audited (entity `SignIn`, verbs `success`, `fail`, `locked`; `entity_id` is the user id when known; `after` holds `accountKey`, `client`, `failures`, `retryAfterSeconds`, never the account string or password) when the Flow has an open trace step. For a route, turn a locked result into a 429 with `throw new errors.TooManyRequests(result.retryAfterSeconds)`.
313
+
295
314
  ### `auth.runChain`
296
315
 
297
316
  Executes an auth chain (strategy pipeline) to resolve auth + tenant from a request.
@@ -586,6 +605,10 @@ Base error class with code, statusCode, and suggestion fields.
586
605
 
587
606
  409 — resource state conflict (duplicate, version mismatch).
588
607
 
608
+ ### `errors.TooManyRequests`
609
+
610
+ 429 — too many attempts; carries `retryAfterSeconds`, sent as a `Retry-After` header.
611
+
589
612
  ## lifecycle — Startup & Shutdown
590
613
 
591
614
  Application lifecycle namespace — startup orchestration with hooks and timeouts.
@@ -1097,6 +1120,36 @@ Creates an outbound webhook system — endpoint registration, payload signing, d
1097
1120
 
1098
1121
  Signs a payload with HMAC for outbound delivery — returns body, signature, timestamp, messageId.
1099
1122
 
1123
+ ## mail — Transactional Email
1124
+
1125
+ Mail namespace — transactional email through a job queue, SES v2 or a dev stand-in, bounce and complaint suppression.
1126
+
1127
+ Opt in with blueprint `email: {}`; the generated migration adds `pipework_email_messages` (`key` unique, `to_address`, `template`, `data`, `status` `queued | sending | sent | suppressed | failed`, `provider_message_id`, `error`, `trace_id`, `created_at`, `sent_at`, `sending_at`) and `pipework_email_suppressions` (`address` primary key, lowercased; `reason` `bounce | complaint`; `detail`; `created_at`), both `IF NOT EXISTS` and not tenant-scoped.
1128
+
1129
+ ### `mail.create`
1130
+
1131
+ Creates the transactional email interface: `send(db, { to, template, data, key })` in the caller's transaction, and the `jobs` that deliver.
1132
+
1133
+ `mail.create({ provider, templates, from, queue, configurationSet?, environment?, sendingStaleSeconds? })` returns `{ send, jobs }`. `templates` is `{ [name]: (data) => { subject, text, html? } }`; `send` checks the template name and the data type. `send(db, ...)` inserts the message row (`ON CONFLICT (key) DO NOTHING`) and enqueues a `pipework.email.send` job on `queue` in the same `db`, so the caller's rollback drops both; it returns `{ messageId, queued }`, with `queued: false` and the earlier id when the key was used. Spread `mail.jobs` into the worker surface's `jobs` (on the same queue). The job skips a message already `sent`, `suppressed` or `failed`, marks an address on the suppression list `suppressed` without calling the provider, and otherwise renders, sends and marks `sent`. A provider error returns the row to `queued` with `error` set and rethrows, so the queue retries per its `maxAttempts`; if it gives up, the row keeps the error. A template that is missing or throws marks the row `failed` without retrying. The job claims the message with a conditional update (`queued`, or `sending` for longer than `sendingStaleSeconds`, default 600), so a reaper re-run of a job still in flight does not call the provider; a run that cannot claim returns and logs at info. Residual window: a crash mid-send leaves the message `sending` for the stale window, after which a retry may send it once more; likewise if the provider accepted the message and the process died before the row was marked `sent`. SES v2 has no idempotency token. The job runs in the trace of the flow that called `send` (`continueFlow`); the delivery is the step `pipework/email:deliver`, with `Email:sent`, `Email:suppressed` or `Email:failed` audit records under it. Message bodies and template data are never logged. `create` throws when `environment` is `production` and the provider is the stand-in; the job checks the instance's environment too.
1134
+
1135
+ ### `mail.ses`
1136
+
1137
+ Amazon SES v2 provider, SigV4-signed without the AWS SDK.
1138
+
1139
+ `mail.ses({ region, credentials: async () => ({ accessKeyId, secretAccessKey, sessionToken? }), configurationSet?, endpoint? })` posts JSON to `https://email.<region>.amazonaws.com/v2/email/outbound-emails` (`endpoint` replaces the base URL, for tests). Credentials come from the app, never from the environment, and are asked for on every send. A non-2xx answer throws with the status and AWS's message.
1140
+
1141
+ ### `mail.devStandIn`
1142
+
1143
+ Development provider that writes each message to a file instead of sending; refused in production.
1144
+
1145
+ `mail.devStandIn({ dir })` writes `<dir>/<messageId>.json` (`from`, `to`, `subject`, `text`, `html`) and logs one line with the address and file path.
1146
+
1147
+ ### `mail.snsWebhook`
1148
+
1149
+ Inbound handler for SES bounce and complaint events delivered through SNS; suppresses the address.
1150
+
1151
+ `mail.snsWebhook({ path, topicArns, region?, fetchCert?, confirm? })` returns a public POST handler for an SNS HTTP/S subscription to the SES event topic. `topicArns` is required: after the signature verifies, a message whose `TopicArn` is not listed is refused with 401, before a subscription is confirmed or an event processed (SNS signs every topic, so anyone could otherwise subscribe the endpoint to their own topic and forge bounces). Every message's signature (SignatureVersion 1 or 2) is verified against the certificate at `SigningCertURL`, which must be `https://sns.<region>.amazonaws.com/...pem` (any AWS region when `region` is omitted); otherwise the answer is 401. A `SubscriptionConfirmation` is confirmed by fetching its `SubscribeURL` (same host rule). A `Notification` with a permanent bounce or a complaint puts each recipient on `pipework_email_suppressions`; a transient or undetermined bounce is logged only. Each is the step `pipework/email:notification` with an `Email:bounce` or `Email:complaint` audit record (`entity_id` the message id when found by `provider_message_id`; `after` has `address`, `bounceType`, `suppressed`, `messageTraceId`, the trace of the original send). A suppression logs a warning `email suppressed`; the alert condition (#458) is not built yet. SES events delivered through EventBridge are not supported. `fetchCert` and `confirm` replace the default HTTP fetches (for tests).
1152
+
1100
1153
  ## accessLinks — Access Links
1101
1154
 
1102
1155
  Short, revocable bearer links to a tenant's resource, for people with no account ("a public link that never expires", "a link for John that works once"). A tenant creates a link; anyone holding the code can resolve it on a public route with no tenant and no auth in the Flow; a use counts only when the app confirms it completed.
package/dist/REFERENCE.md CHANGED
@@ -277,6 +277,14 @@ Creates a Sessions manager with JWT access/refresh tokens, rotation, and reuse d
277
277
 
278
278
  Creates multi-org session management — org selection, switching, membership CRUD.
279
279
 
280
+ ### `auth.passwords`
281
+
282
+ Hashes and verifies passwords with scrypt; reports when a stored hash needs rehashing.
283
+
284
+ ### `auth.signIn`
285
+
286
+ Guarded password sign-in that slows, then refuses, repeated failures per account and per client.
287
+
280
288
  ### `auth.runChain`
281
289
 
282
290
  Executes an auth chain (strategy pipeline) to resolve auth + tenant from a request.
@@ -377,6 +385,54 @@ interface Sessions
377
385
  interface CookieConfig
378
386
  ```
379
387
 
388
+ **`ScryptParams`**
389
+
390
+ ```typescript
391
+ interface ScryptParams
392
+ ```
393
+
394
+ **`PasswordsConfig`**
395
+
396
+ ```typescript
397
+ interface PasswordsConfig
398
+ ```
399
+
400
+ **`VerifyResult`**
401
+
402
+ ```typescript
403
+ interface VerifyResult
404
+ ```
405
+
406
+ **`Passwords`**
407
+
408
+ ```typescript
409
+ interface Passwords
410
+ ```
411
+
412
+ **`SignInConfig`**
413
+
414
+ ```typescript
415
+ interface SignInConfig
416
+ ```
417
+
418
+ **`SignInAttempt`**
419
+
420
+ ```typescript
421
+ interface SignInAttempt
422
+ ```
423
+
424
+ **`SignInResult`**
425
+
426
+ ```typescript
427
+ type SignInResult =
428
+ ```
429
+
430
+ **`SignIn`**
431
+
432
+ ```typescript
433
+ interface SignIn
434
+ ```
435
+
380
436
  **`MultiOrgConfig`** — Configuration for multi-org sessions — base sessions, signing keys, org-select token TTL.
381
437
 
382
438
  ```typescript
@@ -572,6 +628,10 @@ Base error class with code, statusCode, and suggestion fields.
572
628
 
573
629
  409 — resource state conflict (duplicate, version mismatch).
574
630
 
631
+ ### `errors.TooManyRequests`
632
+
633
+ 429 — too many attempts; carries `retryAfterSeconds`, sent as a `Retry-After` header.
634
+
575
635
  ## lifecycle — Startup & Shutdown
576
636
 
577
637
  Application lifecycle namespace — startup orchestration with hooks and timeouts.
@@ -703,7 +763,7 @@ Field builders for domain definitions — uuid(), text(), integer(), etc.
703
763
 
704
764
  ### `pipe.define`
705
765
 
706
- Registers a domain definition with table, validators, and factory projections.
766
+ Registers a domain definition with table, validators, and factory projections. The `auditEntity` option names the `@touches` entity writes to this table are audited under (see `__flowStep`).
707
767
 
708
768
  ### `pipe.definitions`
709
769
 
@@ -1037,7 +1097,11 @@ Creates an Audit instance that writes audit records to a Postgres table.
1037
1097
 
1038
1098
  ### `audit.query`
1039
1099
 
1040
- Queries the framework-managed `audit_record` table. Tenant-scoped via the active Flow.
1100
+ Queries the framework-managed `audit_record` table. Tenant-scoped via the active Flow. `{ entity, entityId }` returns one row's history from an index; build a composite key's `entityId` with `audit.entityId`.
1101
+
1102
+ ### `audit.entityId`
1103
+
1104
+ The `entityId` filter text for a written table's row: the primary key's value as text, or for a composite key a compact JSON array of the key values as strings in key order (`["a","7"]`). Pass the table and a row (or just its key fields). Dates become ISO strings.
1041
1105
 
1042
1106
  ### `audit.chainHead`
1043
1107
 
@@ -1209,7 +1273,7 @@ interface ErrorResponse
1209
1273
  interface PipeworkServer
1210
1274
  ```
1211
1275
 
1212
- **`HttpRequest`** — Fastify request with pipework extensions — req.id (UUID), typed headers.
1276
+ **`HttpRequest`** — Fastify request with pipework extensions — req.id (UUID), typed headers, client address (`ip`, `ips`).
1213
1277
 
1214
1278
  ```typescript
1215
1279
  interface HttpRequest
@@ -7,4 +7,8 @@ export type { CookieConfig, ResolvedCookieConfig } from './cookie.js';
7
7
  export { resolveCookieConfig, buildSetCookieHeader, buildClearCookieHeader, parseCookieHeader } from './cookie.js';
8
8
  export { createMultiOrgSessions } from './multi-org.js';
9
9
  export type { MultiOrgConfig, MultiOrgSessions, ResolveLoginResult, AuthenticatedResult, OrgSelectResult, OrgMembership, OrgInfo, OrgMembershipDetail } from './multi-org.js';
10
+ export { createPasswords } from './passwords.js';
11
+ export type { Passwords, PasswordsConfig, ScryptParams, VerifyResult } from './passwords.js';
12
+ export { createSignIn } from './sign-in.js';
13
+ export type { SignIn, SignInConfig, SignInAttempt, SignInResult } from './sign-in.js';
10
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjI,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjI,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAC7K,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC5F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
@@ -2,4 +2,6 @@ export { runAuthChain } from './chain.js';
2
2
  export { createSessions } from './sessions.js';
3
3
  export { resolveCookieConfig, buildSetCookieHeader, buildClearCookieHeader, parseCookieHeader } from './cookie.js';
4
4
  export { createMultiOrgSessions } from './multi-org.js';
5
+ export { createPasswords } from './passwords.js';
6
+ export { createSignIn } from './sign-in.js';
5
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAG9C,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAG9C,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
@@ -1,5 +1,7 @@
1
1
  import { createSessions } from './sessions.js';
2
2
  import { createMultiOrgSessions } from './multi-org.js';
3
+ import { createPasswords } from './passwords.js';
4
+ import { createSignIn } from './sign-in.js';
3
5
  import { runAuthChain } from './chain.js';
4
6
  import { resolveCookieConfig, parseCookieHeader, buildSetCookieHeader, buildClearCookieHeader } from './cookie.js';
5
7
  /** Authentication namespace — session management, multi-org support, auth chain resolution. */
@@ -8,6 +10,10 @@ export declare const auth: {
8
10
  createSessions: typeof createSessions;
9
11
  /** Creates multi-org session management — org selection, switching, membership CRUD. */
10
12
  createMultiOrg: typeof createMultiOrgSessions;
13
+ /** Hashes and verifies passwords with scrypt; reports when a stored hash needs rehashing. */
14
+ passwords: typeof createPasswords;
15
+ /** Guarded password sign-in that slows, then refuses, repeated failures per account and per client. */
16
+ signIn: typeof createSignIn;
11
17
  /** Executes an auth chain (strategy pipeline) to resolve auth + tenant from a request. */
12
18
  runChain: typeof runAuthChain;
13
19
  /** Resolves CookieConfig with defaults for secure cookie-based token delivery. */
@@ -1 +1 @@
1
- {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAElH,+FAA+F;AAC/F,eAAO,MAAM,IAAI;IACf,gGAAgG;;IAEhG,wFAAwF;;IAExF,0FAA0F;;IAE1F,kFAAkF;;IAElF,0DAA0D;;IAE1D,4EAA4E;;IAE5E,iEAAiE;;CAElE,CAAA"}
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAElH,+FAA+F;AAC/F,eAAO,MAAM,IAAI;IACf,gGAAgG;;IAEhG,wFAAwF;;IAExF,6FAA6F;;IAE7F,uGAAuG;;IAEvG,0FAA0F;;IAE1F,kFAAkF;;IAElF,0DAA0D;;IAE1D,4EAA4E;;IAE5E,iEAAiE;;CAElE,CAAA"}
@@ -1,5 +1,7 @@
1
1
  import { createSessions } from './sessions.js';
2
2
  import { createMultiOrgSessions } from './multi-org.js';
3
+ import { createPasswords } from './passwords.js';
4
+ import { createSignIn } from './sign-in.js';
3
5
  import { runAuthChain } from './chain.js';
4
6
  import { resolveCookieConfig, parseCookieHeader, buildSetCookieHeader, buildClearCookieHeader } from './cookie.js';
5
7
  /** Authentication namespace — session management, multi-org support, auth chain resolution. */
@@ -8,6 +10,10 @@ export const auth = {
8
10
  createSessions,
9
11
  /** Creates multi-org session management — org selection, switching, membership CRUD. */
10
12
  createMultiOrg: createMultiOrgSessions,
13
+ /** Hashes and verifies passwords with scrypt; reports when a stored hash needs rehashing. */
14
+ passwords: createPasswords,
15
+ /** Guarded password sign-in that slows, then refuses, repeated failures per account and per client. */
16
+ signIn: createSignIn,
11
17
  /** Executes an auth chain (strategy pipeline) to resolve auth + tenant from a request. */
12
18
  runChain: runAuthChain,
13
19
  /** Resolves CookieConfig with defaults for secure cookie-based token delivery. */
@@ -1 +1 @@
1
- {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/auth/auth/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAElH,+FAA+F;AAC/F,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,gGAAgG;IAChG,cAAc;IACd,wFAAwF;IACxF,cAAc,EAAE,sBAAsB;IACtC,0FAA0F;IAC1F,QAAQ,EAAE,YAAY;IACtB,kFAAkF;IAClF,aAAa,EAAE,mBAAmB;IAClC,0DAA0D;IAC1D,WAAW,EAAE,iBAAiB;IAC9B,4EAA4E;IAC5E,cAAc,EAAE,oBAAoB;IACpC,iEAAiE;IACjE,gBAAgB,EAAE,sBAAsB;CACzC,CAAA"}
1
+ {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/auth/auth/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAElH,+FAA+F;AAC/F,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,gGAAgG;IAChG,cAAc;IACd,wFAAwF;IACxF,cAAc,EAAE,sBAAsB;IACtC,6FAA6F;IAC7F,SAAS,EAAE,eAAe;IAC1B,uGAAuG;IACvG,MAAM,EAAE,YAAY;IACpB,0FAA0F;IAC1F,QAAQ,EAAE,YAAY;IACtB,kFAAkF;IAClF,aAAa,EAAE,mBAAmB;IAClC,0DAA0D;IAC1D,WAAW,EAAE,iBAAiB;IAC9B,4EAA4E;IAC5E,cAAc,EAAE,oBAAoB;IACpC,iEAAiE;IACjE,gBAAgB,EAAE,sBAAsB;CACzC,CAAA"}
@@ -0,0 +1,39 @@
1
+ export interface ScryptParams {
2
+ /** log2 of the CPU/memory cost N. Default 15. */
3
+ readonly ln: number;
4
+ /** Block size. Default 8. */
5
+ readonly r: number;
6
+ /** Parallelism. Default 3. */
7
+ readonly p: number;
8
+ }
9
+ export interface PasswordsConfig {
10
+ readonly scrypt?: Partial<ScryptParams>;
11
+ }
12
+ export interface VerifyResult {
13
+ readonly ok: boolean;
14
+ /** True when the password matched but the stored hash used other parameters than the configured ones. */
15
+ readonly needsRehash: boolean;
16
+ }
17
+ export interface Passwords {
18
+ /** Returns a PHC string `$scrypt$ln=15,r=8,p=3$<salt>$<hash>` for the password. */
19
+ hash(password: string): Promise<string>;
20
+ /** Checks a password against a stored hash in constant time. A malformed stored hash is `ok: false`. */
21
+ verify(stored: string, password: string): Promise<VerifyResult>;
22
+ /** Spends the cost of a verify against a fixed dummy hash, for an account that does not exist. */
23
+ verifyNone(password: string): Promise<{
24
+ ok: false;
25
+ }>;
26
+ }
27
+ /**
28
+ * Creates the password hasher. Passwords are NFC-normalized and hashed with
29
+ * Node's scrypt (no dependency); there are no composition rules, and anything up
30
+ * to 4096 bytes of UTF-8 is accepted, so long passphrases and pasted passwords
31
+ * work. The hash records its own parameters, so raising `scrypt` later leaves old
32
+ * hashes verifiable and `verify` reports `needsRehash` for them.
33
+ *
34
+ * `verify` never throws on a bad stored string (it returns `ok: false` and logs),
35
+ * and `verifyNone` costs the same as a wrong password, so an unknown account is
36
+ * not distinguishable by timing.
37
+ */
38
+ export declare function createPasswords(config?: PasswordsConfig): Passwords;
39
+ //# sourceMappingURL=passwords.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passwords.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/passwords.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,6BAA6B;IAC7B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,8BAA8B;IAC9B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,yGAAyG;IACzG,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,mFAAmF;IACnF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,wGAAwG;IACxG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC/D,kGAAkG;IAClG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;CACrD;AAgCD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,GAAE,eAAoB,GAAG,SAAS,CAoCvE"}
@@ -0,0 +1,76 @@
1
+ import { scrypt, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { ValidationError } from '../../core/errors/index.js';
3
+ import { getBaseLogger } from '../../core/logging/index.js';
4
+ const DEFAULTS = { ln: 15, r: 8, p: 3 };
5
+ const KEY_LENGTH = 32;
6
+ const SALT_LENGTH = 16;
7
+ const MAX_PASSWORD_BYTES = 4096;
8
+ const STORED_RE = /^\$scrypt\$ln=(\d+),r=(\d+),p=(\d+)\$([A-Za-z0-9+/]+)\$([A-Za-z0-9+/]+)$/;
9
+ function derive(password, salt, params, length) {
10
+ const N = 2 ** params.ln;
11
+ return new Promise((resolve, reject) => {
12
+ scrypt(password, salt, length, { N, r: params.r, p: params.p, maxmem: 256 * N * params.r + (1 << 20) }, (err, key) => {
13
+ if (err !== null)
14
+ reject(err);
15
+ else
16
+ resolve(key);
17
+ });
18
+ });
19
+ }
20
+ function normalize(password) {
21
+ const normalized = password.normalize('NFC');
22
+ if (Buffer.byteLength(normalized, 'utf8') > MAX_PASSWORD_BYTES) {
23
+ throw new ValidationError({ password: [`must be at most ${MAX_PASSWORD_BYTES} bytes`] }, 'Password is too long.');
24
+ }
25
+ return normalized;
26
+ }
27
+ function validParams(p) {
28
+ return Number.isInteger(p.ln) && p.ln >= 1 && p.ln <= 22
29
+ && Number.isInteger(p.r) && p.r >= 1 && p.r <= 32
30
+ && Number.isInteger(p.p) && p.p >= 1 && p.p <= 16;
31
+ }
32
+ /**
33
+ * Creates the password hasher. Passwords are NFC-normalized and hashed with
34
+ * Node's scrypt (no dependency); there are no composition rules, and anything up
35
+ * to 4096 bytes of UTF-8 is accepted, so long passphrases and pasted passwords
36
+ * work. The hash records its own parameters, so raising `scrypt` later leaves old
37
+ * hashes verifiable and `verify` reports `needsRehash` for them.
38
+ *
39
+ * `verify` never throws on a bad stored string (it returns `ok: false` and logs),
40
+ * and `verifyNone` costs the same as a wrong password, so an unknown account is
41
+ * not distinguishable by timing.
42
+ */
43
+ export function createPasswords(config = {}) {
44
+ const params = { ...DEFAULTS, ...config.scrypt };
45
+ if (!validParams(params)) {
46
+ throw new ValidationError('auth.passwords scrypt params must satisfy 1<=ln<=22, 1<=r<=32, 1<=p<=16.');
47
+ }
48
+ const encode = (b) => b.toString('base64').replace(/=+$/, '');
49
+ let dummy;
50
+ async function hash(password) {
51
+ const salt = randomBytes(SALT_LENGTH);
52
+ const key = await derive(normalize(password), salt, params, KEY_LENGTH);
53
+ return `$scrypt$ln=${params.ln},r=${params.r},p=${params.p}$${encode(salt)}$${encode(key)}`;
54
+ }
55
+ async function verify(stored, password) {
56
+ const m = STORED_RE.exec(stored);
57
+ const storedParams = m === null ? null : { ln: Number(m[1]), r: Number(m[2]), p: Number(m[3]) };
58
+ if (m === null || storedParams === null || !validParams(storedParams)) {
59
+ getBaseLogger().error({ event: 'password hash malformed' }, 'stored password hash is malformed');
60
+ return { ok: false, needsRehash: false };
61
+ }
62
+ const salt = Buffer.from(m[4], 'base64');
63
+ const expected = Buffer.from(m[5], 'base64');
64
+ const key = await derive(normalize(password), salt, storedParams, expected.length);
65
+ const ok = key.length === expected.length && timingSafeEqual(key, expected);
66
+ const needsRehash = ok && (storedParams.ln !== params.ln || storedParams.r !== params.r || storedParams.p !== params.p);
67
+ return { ok, needsRehash };
68
+ }
69
+ async function verifyNone(password) {
70
+ dummy ??= hash(randomBytes(16).toString('hex'));
71
+ await verify(await dummy, password);
72
+ return { ok: false };
73
+ }
74
+ return { hash, verify, verifyNone };
75
+ }
76
+ //# sourceMappingURL=passwords.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passwords.js","sourceRoot":"","sources":["../../../src/auth/auth/passwords.ts"],"names":[],"mappings":"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"}
@@ -74,4 +74,31 @@ export declare const userOrgs: import("../../data/domain/types.js").DefinedTable
74
74
  nullable: true;
75
75
  }>;
76
76
  }, "pipework_user_orgs">;
77
+ export declare const signinAttempts: import("../../data/domain/types.js").DefinedTable<{
78
+ key: import("../../data/domain/types.js").FieldDescriptor<string, {
79
+ primaryKey: true;
80
+ }> & import("../../data/domain/field.js").FieldModifiers<string, {
81
+ primaryKey: true;
82
+ }>;
83
+ failures: import("../../data/domain/types.js").FieldDescriptor<number, {
84
+ hasDefault: true;
85
+ }> & import("../../data/domain/field.js").FieldModifiers<number, {
86
+ hasDefault: true;
87
+ }>;
88
+ firstFailureAt: import("../../data/domain/types.js").FieldDescriptor<Date | null, {
89
+ nullable: true;
90
+ }> & import("../../data/domain/field.js").FieldModifiers<Date | null, {
91
+ nullable: true;
92
+ }>;
93
+ lastFailureAt: import("../../data/domain/types.js").FieldDescriptor<Date | null, {
94
+ nullable: true;
95
+ }> & import("../../data/domain/field.js").FieldModifiers<Date | null, {
96
+ nullable: true;
97
+ }>;
98
+ lockedUntil: import("../../data/domain/types.js").FieldDescriptor<Date | null, {
99
+ nullable: true;
100
+ }> & import("../../data/domain/field.js").FieldModifiers<Date | null, {
101
+ nullable: true;
102
+ }>;
103
+ }, "pipework_signin_attempts">;
77
104
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/schema.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAE/C,CAAA;AAEF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAexB,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;4BAIvB,CAAA;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAanB,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/schema.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAE/C,CAAA;AAEF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAexB,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;4BAIvB,CAAA;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAanB,CAAA;AAIF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;8BAMzB,CAAA"}
@@ -17,7 +17,7 @@ import { field } from '../../data/domain/field.js';
17
17
  import { sql } from '../../data/db/sql.js';
18
18
  import { index, unique } from '../../data/db/idx/index.js';
19
19
  export const AUTH_TABLE_NAMES = new Set([
20
- 'pipework_refresh_tokens', 'pipework_auth_versions', 'pipework_user_orgs',
20
+ 'pipework_refresh_tokens', 'pipework_auth_versions', 'pipework_user_orgs', 'pipework_signin_attempts',
21
21
  ]);
22
22
  export const refreshTokens = define('pipework_refresh_tokens', {
23
23
  id: field.uuid().defaultRandom().primaryKey(),
@@ -54,4 +54,13 @@ export const userOrgs = define('pipework_user_orgs', {
54
54
  byOrg: index('idx_pipework_user_orgs_org').on(t['orgId']),
55
55
  }),
56
56
  });
57
+ // Behind `auth.signIn`. Not tenant-scoped: a sign-in happens before any tenant
58
+ // is known. `key` is 'account:<sha256 of the account>' or 'client:<address>'.
59
+ export const signinAttempts = define('pipework_signin_attempts', {
60
+ key: field.text().primaryKey(),
61
+ failures: field.integer().default(0),
62
+ firstFailureAt: field.timestamp().nullable(),
63
+ lastFailureAt: field.timestamp().nullable(),
64
+ lockedUntil: field.timestamp().nullable(),
65
+ });
57
66
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/auth/auth/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/auth/auth/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import type { DB } from '../../data/db/index.js';
2
+ import type { Passwords } from './passwords.js';
3
+ export interface SignInConfig {
4
+ readonly passwords: Passwords;
5
+ /** Failures per account within the window before it is locked. Default 5. */
6
+ readonly accountThreshold?: number;
7
+ /** Failures per client address within the window before it is locked. Default 20. */
8
+ readonly clientThreshold?: number;
9
+ /** Window since the first failure after which the count restarts. Default 900 seconds. */
10
+ readonly windowSeconds?: number;
11
+ /** Longest single lock. Default 900 seconds. */
12
+ readonly maxLockSeconds?: number;
13
+ }
14
+ export interface SignInAttempt {
15
+ readonly account: string;
16
+ /** The client address; without it only the account is guarded. */
17
+ readonly client?: string;
18
+ readonly password: string;
19
+ /** Finds the account's user id and stored password hash, or null if there is no such account. */
20
+ readonly lookup: (db: DB, account: string) => Promise<{
21
+ userId: string;
22
+ passwordHash: string;
23
+ } | null>;
24
+ }
25
+ export type SignInResult = {
26
+ readonly ok: true;
27
+ readonly userId: string;
28
+ readonly needsRehash: boolean;
29
+ } | {
30
+ readonly ok: false;
31
+ readonly reason: 'invalid' | 'locked';
32
+ readonly retryAfterSeconds?: number;
33
+ };
34
+ export interface SignIn {
35
+ attempt(input: SignInAttempt): Promise<SignInResult>;
36
+ }
37
+ /**
38
+ * Creates a guarded password sign-in. `attempt` needs an active Flow (a request,
39
+ * job or test context) and runs every statement, `lookup` included, on
40
+ * `pipe.system()`: outside the request transaction, committed on its own, so a
41
+ * route that answers a bad password by throwing `UnauthorizedError` (rolling the
42
+ * request back) still keeps the count. Failed attempts are counted per account and
43
+ * per client address in `pipework_signin_attempts` (blueprint `passwords: {}`), so
44
+ * counts survive restarts and are shared across processes. At the threshold each
45
+ * further failure locks the key for 2^(failures - threshold) seconds, capped at
46
+ * `maxLockSeconds`. Every attempt is counted, atomically per key, before the
47
+ * password is checked, so a burst of parallel guesses is locked out instead of
48
+ * all being verified. While any key is locked an attempt is refused with
49
+ * `reason: 'locked'` and `retryAfterSeconds`, without checking the password (a
50
+ * locked key is not counted further). A success deletes the account's key and
51
+ * gives the client key its count back. An unknown account runs a dummy verify
52
+ * and counts as a failure, reported as `'invalid'` like a wrong password.
53
+ *
54
+ * Tradeoff: locking is per account, so anyone who knows an account name can lock
55
+ * it out by failing on purpose. Each lock is capped (default 15 minutes) and the
56
+ * client key bounds how fast one address can do it.
57
+ *
58
+ * Every success, failure and refusal is logged, and audited (entity `SignIn`,
59
+ * verbs `success`, `fail`, `locked`; never the account string or password) when
60
+ * the Flow has an open trace step. Route a locked result to a 429 with
61
+ * `throw new TooManyRequestsError(result.retryAfterSeconds)`.
62
+ */
63
+ export declare function createSignIn(config: SignInConfig): SignIn;
64
+ //# sourceMappingURL=sign-in.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-in.d.ts","sourceRoot":"","sources":["../../../src/auth/auth/sign-in.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAIhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,qFAAqF;IACrF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,gDAAgD;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,iGAAiG;IACjG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;CACvG;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAAE,GAC7E;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtG,MAAM,WAAW,MAAM;IACrB,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACrD;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CA0GzD"}