@shipfox/api-auth 5.0.0 → 6.0.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +71 -37
- package/dist/config.d.ts +0 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -62
- package/dist/config.js.map +1 -1
- package/dist/core/auth.d.ts +21 -2
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +109 -77
- package/dist/core/auth.js.map +1 -1
- package/dist/core/email-owner.d.ts +12 -0
- package/dist/core/email-owner.d.ts.map +1 -0
- package/dist/core/email-owner.js +20 -0
- package/dist/core/email-owner.js.map +1 -0
- package/dist/core/job-lease-token.d.ts.map +1 -1
- package/dist/core/job-lease-token.js +3 -2
- package/dist/core/job-lease-token.js.map +1 -1
- package/dist/core/jwt.d.ts +2 -2
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/jwt.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/rate-limit.js +2 -7
- package/dist/core/rate-limit.js.map +1 -1
- package/dist/core/runner-session-token.d.ts.map +1 -1
- package/dist/core/runner-session-token.js +3 -2
- package/dist/core/runner-session-token.js.map +1 -1
- package/dist/db/db.d.ts +0 -218
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +0 -2
- package/dist/db/db.js.map +1 -1
- package/dist/db/users.d.ts +3 -0
- package/dist/db/users.d.ts.map +1 -1
- package/dist/db/users.js +28 -10
- package/dist/db/users.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -34
- package/dist/index.js.map +1 -1
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
- package/dist/presentation/auth/jwt-auth.js +2 -2
- package/dist/presentation/auth/jwt-auth.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-session.d.ts +2 -1
- package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-session.js +42 -39
- package/dist/presentation/e2eRoutes/create-session.js.map +1 -1
- package/dist/presentation/e2eRoutes/index.d.ts +2 -1
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/index.js +12 -10
- package/dist/presentation/e2eRoutes/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +4 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +16 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -1
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-confirm.js +36 -25
- package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-resend.js +20 -4
- package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -2
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +11 -13
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -1
- package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -1
- package/dist/presentation/routes/password/password-reset-confirm.js +29 -26
- package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -1
- package/dist/presentation/routes/registration/signup.d.ts +2 -1
- package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
- package/dist/presentation/routes/registration/signup.js +86 -68
- package/dist/presentation/routes/registration/signup.js.map +1 -1
- package/dist/presentation/routes/session/login.d.ts +2 -1
- package/dist/presentation/routes/session/login.d.ts.map +1 -1
- package/dist/presentation/routes/session/login.js +40 -37
- package/dist/presentation/routes/session/login.js.map +1 -1
- package/dist/presentation/routes/session/refresh.d.ts +2 -1
- package/dist/presentation/routes/session/refresh.d.ts.map +1 -1
- package/dist/presentation/routes/session/refresh.js +42 -39
- package/dist/presentation/routes/session/refresh.js.map +1 -1
- package/dist/presentation/subscribers/index.d.ts +0 -1
- package/dist/presentation/subscribers/index.d.ts.map +1 -1
- package/dist/presentation/subscribers/index.js +0 -1
- package/dist/presentation/subscribers/index.js.map +1 -1
- package/dist/presentation/subscribers/on-password-reset-send-requested.js +1 -1
- package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +1 -13
- package/drizzle/meta/0000_snapshot.json +0 -92
- package/package.json +17 -13
- package/src/config.ts +0 -56
- package/src/core/auth.test.ts +106 -187
- package/src/core/auth.ts +131 -87
- package/src/core/email-owner.test.ts +86 -0
- package/src/core/email-owner.ts +23 -0
- package/src/core/job-lease-token.test.ts +2 -1
- package/src/core/job-lease-token.ts +3 -2
- package/src/core/jwt.ts +2 -2
- package/src/core/rate-limit.test.ts +11 -28
- package/src/core/rate-limit.ts +3 -11
- package/src/core/runner-session-token.test.ts +2 -1
- package/src/core/runner-session-token.ts +3 -2
- package/src/db/db.ts +0 -2
- package/src/db/users.ts +32 -14
- package/src/index.test.ts +15 -11
- package/src/index.ts +23 -20
- package/src/presentation/auth/jwt-auth.test.ts +2 -1
- package/src/presentation/auth/jwt-auth.ts +2 -2
- package/src/presentation/e2eRoutes/create-session.ts +34 -30
- package/src/presentation/e2eRoutes/index.test.ts +9 -6
- package/src/presentation/e2eRoutes/index.ts +8 -5
- package/src/presentation/inter-module.ts +13 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.ts +42 -24
- package/src/presentation/routes/email-verification/verify-email-resend.ts +18 -4
- package/src/presentation/routes/index.test.ts +10 -3
- package/src/presentation/routes/index.ts +22 -12
- package/src/presentation/routes/password/password-reset-confirm.ts +25 -22
- package/src/presentation/routes/password/password-reset-request.test.ts +1 -0
- package/src/presentation/routes/rate-limit.test.ts +2 -2
- package/src/presentation/routes/registration/signup.test.ts +39 -74
- package/src/presentation/routes/registration/signup.ts +89 -70
- package/src/presentation/routes/session/login.ts +38 -35
- package/src/presentation/routes/session/refresh.ts +43 -40
- package/src/presentation/subscribers/email-send-requested.test.ts +2 -30
- package/src/presentation/subscribers/index.ts +0 -1
- package/src/presentation/subscribers/on-password-reset-send-requested.ts +1 -1
- package/test/env.ts +1 -3
- package/test/globalSetup.ts +10 -1
- package/test/routes.ts +51 -84
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/email-verification.d.ts +0 -9
- package/dist/core/entities/email-verification.d.ts.map +0 -1
- package/dist/core/entities/email-verification.js +0 -3
- package/dist/core/entities/email-verification.js.map +0 -1
- package/dist/db/email-verifications.d.ts +0 -39
- package/dist/db/email-verifications.d.ts.map +0 -1
- package/dist/db/email-verifications.js +0 -95
- package/dist/db/email-verifications.js.map +0 -1
- package/dist/db/schema/email-verifications.d.ts +0 -114
- package/dist/db/schema/email-verifications.d.ts.map +0 -1
- package/dist/db/schema/email-verifications.js +0 -35
- package/dist/db/schema/email-verifications.js.map +0 -1
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +0 -3
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +0 -1
- package/dist/presentation/subscribers/on-email-verification-send-requested.js +0 -13
- package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +0 -1
- package/src/core/entities/email-verification.ts +0 -8
- package/src/db/email-verifications.test.ts +0 -61
- package/src/db/email-verifications.ts +0 -160
- package/src/db/schema/email-verifications.ts +0 -37
- package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +0 -66
- package/src/presentation/routes/email-verification/verify-email-resend.test.ts +0 -100
- package/src/presentation/subscribers/on-email-verification-send-requested.ts +0 -10
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 52 files with swc (551.72ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @shipfox/api-auth
|
|
2
2
|
|
|
3
|
+
## 6.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 6a52909: Replaces separate API auth secrets with domain-separated keys derived from one required AUTH_ROOT_KEY.
|
|
8
|
+
- ba2e3dc: Migrates password email verification from magic links to shared eight-digit email challenges.
|
|
9
|
+
|
|
10
|
+
### Minor Changes
|
|
11
|
+
|
|
12
|
+
- 326f4c0: Exposes Workspaces inter-module operations and moves Auth and OAuth providers onto injected clients.
|
|
13
|
+
- 4a91956: Publishes a shared provider-neutral `emailSchema` in `@shipfox/api-common-dto` and adopts it across auth and workspace invitation inputs. Adds a read-only `findUserByEmail`/`EmailOwner` seam to `@shipfox/api-auth` for looking up the current owner of a normalized email without creating a session or mutating that user. Extends the packed external consumer gate to exercise both seams against PostgreSQL through installed tarballs.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 7366f04: Adds a configured shared mailer that owns SMTP delivery settings. `@shipfox/api-auth` and `@shipfox/api-workspaces` drop their own mailer environment variables and factory logic and use the shared `mailer` from `@shipfox/node-mailer` instead.
|
|
18
|
+
- 112c0fa: Adds the Auth inter-module token-minting contract and removes Auth implementation and configuration coupling from its consumers.
|
|
19
|
+
- f73da5d: Enforces bounded API context imports and routes inter-module consumers through producer contracts.
|
|
20
|
+
- Updated dependencies [905b6a3]
|
|
21
|
+
- Updated dependencies [b70f920]
|
|
22
|
+
- Updated dependencies [7366f04]
|
|
23
|
+
- Updated dependencies [6a52909]
|
|
24
|
+
- Updated dependencies [e6eba5b]
|
|
25
|
+
- Updated dependencies [54ce48b]
|
|
26
|
+
- Updated dependencies [ba2e3dc]
|
|
27
|
+
- Updated dependencies [c0162b0]
|
|
28
|
+
- Updated dependencies [7ac43a4]
|
|
29
|
+
- Updated dependencies [a01e917]
|
|
30
|
+
- Updated dependencies [1b79cda]
|
|
31
|
+
- Updated dependencies [c2db8c3]
|
|
32
|
+
- Updated dependencies [112c0fa]
|
|
33
|
+
- Updated dependencies [8bdc149]
|
|
34
|
+
- Updated dependencies [3810996]
|
|
35
|
+
- Updated dependencies [b00ed29]
|
|
36
|
+
- Updated dependencies [8aa7cd3]
|
|
37
|
+
- Updated dependencies [326f4c0]
|
|
38
|
+
- Updated dependencies [4a91956]
|
|
39
|
+
- Updated dependencies [81f9544]
|
|
40
|
+
- @shipfox/api-email-challenges@0.2.0
|
|
41
|
+
- @shipfox/node-tokens@0.3.0
|
|
42
|
+
- @shipfox/node-mailer@0.2.0
|
|
43
|
+
- @shipfox/node-auth-root-key@0.2.0
|
|
44
|
+
- @shipfox/node-jwt@0.3.0
|
|
45
|
+
- @shipfox/node-rate-limit@0.3.0
|
|
46
|
+
- @shipfox/api-auth-dto@6.0.0
|
|
47
|
+
- @shipfox/node-module@0.4.0
|
|
48
|
+
- @shipfox/node-drizzle@0.3.2
|
|
49
|
+
- @shipfox/node-outbox@0.2.4
|
|
50
|
+
- @shipfox/api-workspaces-dto@6.0.0
|
|
51
|
+
- @shipfox/api-auth-context@6.0.0
|
|
52
|
+
- @shipfox/node-fastify@0.2.4
|
|
53
|
+
- @shipfox/inter-module@0.2.0
|
|
54
|
+
|
|
3
55
|
## 5.0.0
|
|
4
56
|
|
|
5
57
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -7,12 +7,23 @@ Shipfox API Auth provides the server-side auth module for Shipfox APIs. It owns
|
|
|
7
7
|
Register the module with the API module runner:
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import {
|
|
10
|
+
import {createAuthModule} from '@shipfox/api-auth';
|
|
11
|
+
import {workspacesModule} from '@shipfox/api-workspaces';
|
|
12
|
+
import {workspacesInterModuleContract} from '@shipfox/api-workspaces-dto/inter-module';
|
|
11
13
|
import {createApp, listen} from '@shipfox/node-fastify';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
+
import {
|
|
15
|
+
initializeModules,
|
|
16
|
+
registerInterModulePresentations,
|
|
17
|
+
} from '@shipfox/node-module';
|
|
18
|
+
import {createInMemoryInterModuleTransport} from '@shipfox/node-module/inter-module';
|
|
19
|
+
|
|
20
|
+
const interModuleTransport = createInMemoryInterModuleTransport();
|
|
21
|
+
const workspaces = interModuleTransport.createClient(workspacesInterModuleContract);
|
|
22
|
+
const modules = [workspacesModule, createAuthModule({workspaces})];
|
|
23
|
+
registerInterModulePresentations({transport: interModuleTransport, modules});
|
|
24
|
+
interModuleTransport.seal();
|
|
14
25
|
const {auth, routes} = await initializeModules({
|
|
15
|
-
modules
|
|
26
|
+
modules,
|
|
16
27
|
});
|
|
17
28
|
|
|
18
29
|
await createApp({auth, routes});
|
|
@@ -37,24 +48,17 @@ Required environment:
|
|
|
37
48
|
|
|
38
49
|
| Variable | Default | Purpose |
|
|
39
50
|
| --- | --- | --- |
|
|
40
|
-
| `
|
|
51
|
+
| `AUTH_ROOT_KEY` | none | Canonical base64 for 32 random bytes used to derive separate authentication and email-challenge keys. Generate it with `openssl rand -base64 32`. |
|
|
41
52
|
| `AUTH_JWT_EXPIRES_IN` | `15m` | Access token lifetime. |
|
|
42
|
-
| `AUTH_JOB_LEASE_TOKEN_SECRET` | none | Secret used to sign and verify job lease tokens. |
|
|
43
53
|
| `AUTH_JOB_LEASE_TOKEN_EXPIRES_IN` | `90m` | Job lease token lifetime. |
|
|
44
|
-
| `AUTH_RUNNER_SESSION_TOKEN_SECRET` | none | Secret used to sign and verify runner session tokens. |
|
|
45
54
|
| `AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN` | `1h` | Runner session token lifetime. |
|
|
46
55
|
| `AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS` | `14` | Refresh token and cookie lifetime. |
|
|
47
56
|
| `AUTH_REFRESH_ROTATION_GRACE_SECONDS` | `30` | Grace window for accepting a just-rotated refresh token during concurrent refreshes. |
|
|
48
57
|
| `AUTH_REFRESH_COOKIE_NAME` | `shipfox_refresh_token` | HTTP cookie name for refresh sessions. |
|
|
49
58
|
| `AUTH_PASSWORD_ENABLED` | `true` | Enables password and email-verification routes. Set to `false` when another module provides login. Server construction fails if no login method is available. |
|
|
50
|
-
| `RATE_LIMIT_IDENTIFIER_SECRET` | none | Optional secret used to HMAC identifiers before storing rate-limit counters. When unset, the module derives a stable key from `AUTH_JWT_SECRET`. |
|
|
51
59
|
| `CLIENT_BASE_URL` | `http://localhost:5173` | Base URL used in email verification and password reset links. |
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| `SMTP_HOST` | none | Required when `MAILER_TRANSPORT=smtp`. |
|
|
55
|
-
| `SMTP_PORT` | `587` | SMTP server port. |
|
|
56
|
-
| `SMTP_USER` | none | Optional SMTP user. |
|
|
57
|
-
| `SMTP_PASSWORD` | none | Optional SMTP password. |
|
|
60
|
+
|
|
61
|
+
Email delivery uses the shared `@shipfox/node-mailer` configuration.
|
|
58
62
|
|
|
59
63
|
When `AUTH_PASSWORD_ENABLED=false`, the module does not register signup, login, password-reset, password-change, or email-verification routes. Refresh, logout, and current-session routes stay available. Another module must contribute a login method before the API server starts.
|
|
60
64
|
|
|
@@ -65,8 +69,13 @@ The module issues three kinds of bearer token, all presented as
|
|
|
65
69
|
HMAC-SHA256 and verified by checking its signature and expiry alone, with no
|
|
66
70
|
database read on the request path. They differ in who they authenticate and what
|
|
67
71
|
they grant, and the stateless tradeoff is accepted for a different reason in each
|
|
68
|
-
case. Each token class
|
|
69
|
-
cannot be used in place of another.
|
|
72
|
+
case. Each token class uses a separate key derived from `AUTH_ROOT_KEY` and a
|
|
73
|
+
fixed audience, so one token type cannot be used in place of another.
|
|
74
|
+
|
|
75
|
+
Changing `AUTH_ROOT_KEY` invalidates every user session token, runner session
|
|
76
|
+
token, job lease token, email challenge, and rate-limit identifier created with
|
|
77
|
+
the previous root. The module does not support rotating one derived key by
|
|
78
|
+
itself.
|
|
70
79
|
|
|
71
80
|
### User session token
|
|
72
81
|
|
|
@@ -105,7 +114,7 @@ use the per-job lease token instead.
|
|
|
105
114
|
the labels it registers with. The labels are immutable after registration and
|
|
106
115
|
signed into the session token, but they are not an authorization boundary in v1.
|
|
107
116
|
Treat the registration token's workspace scope as the trust boundary.
|
|
108
|
-
- **Mechanics.** Signed with HMAC-SHA256 using
|
|
117
|
+
- **Mechanics.** Signed with HMAC-SHA256 using the derived runner-session key
|
|
109
118
|
and the `runner-session` audience. The default lifetime is `1h`, short enough
|
|
110
119
|
to bound the residual claim window when a registration token is revoked.
|
|
111
120
|
- **Tradeoff — no per-session revocation in v1.** Claim stays stateless and does
|
|
@@ -139,14 +148,14 @@ progress and drives that job to completion.
|
|
|
139
148
|
downstream only *verifies* — an in-process signature check, with no callback to
|
|
140
149
|
the issuer. The runner, and the untrusted agent workload it hosts, never mints
|
|
141
150
|
or modifies a token; it only presents the one it was handed.
|
|
142
|
-
- **Mechanics.** Signed with HMAC-SHA256 using
|
|
143
|
-
|
|
151
|
+
- **Mechanics.** Signed with HMAC-SHA256 using the job-lease key derived from
|
|
152
|
+
`AUTH_ROOT_KEY`. Its claims name the job, job execution, and surrounding
|
|
144
153
|
context and nothing more, and it carries a fixed audience so a token minted for
|
|
145
154
|
one purpose cannot be replayed against another. It is short-lived (bounded in
|
|
146
155
|
hours, not days), with heartbeat issuing a fresh short lease for the same live
|
|
147
|
-
execution. The
|
|
148
|
-
|
|
149
|
-
error payloads
|
|
156
|
+
execution. The root key is supplied through configuration, never embedded in
|
|
157
|
+
code or committed. The raw token must **never** be written to logs, traces, or
|
|
158
|
+
error payloads. There is no automatic redaction to fall back on.
|
|
150
159
|
- **Defense in depth — server state is the final authority.** A valid token is
|
|
151
160
|
never sufficient on its own to advance work. On the lease's own request path the
|
|
152
161
|
gate is server-side step and progression state: a report against a step that has
|
|
@@ -164,10 +173,9 @@ progress and drives that job to completion.
|
|
|
164
173
|
- **Threat model.** A leaked or replayed token has a deliberately small blast
|
|
165
174
|
radius: it grants action on one already-claimed job execution while that
|
|
166
175
|
execution remains live. It cannot claim new work, impersonate a runner, or reach
|
|
167
|
-
other workspaces. If
|
|
168
|
-
|
|
169
|
-
and forces fresh claims.
|
|
170
|
-
what lets one be rotated without disrupting the other.
|
|
176
|
+
other workspaces. If `AUTH_ROOT_KEY` leaks, rotate it. This invalidates every
|
|
177
|
+
live token and email challenge derived from the old root, including every live
|
|
178
|
+
lease, and forces fresh claims.
|
|
171
179
|
- **Tradeoff — no per-token revocation.** A single outstanding lease cannot be
|
|
172
180
|
revoked on its own by token ID. The claiming runner's credential is not
|
|
173
181
|
re-checked on each request, so a lease for already-claimed work can continue to
|
|
@@ -187,9 +195,9 @@ All routes are mounted under `/auth`.
|
|
|
187
195
|
|
|
188
196
|
| Method | Path | Auth | Result |
|
|
189
197
|
| --- | --- | --- | --- |
|
|
190
|
-
| `POST` | `/signup` | none | Creates a user and sends an email verification
|
|
191
|
-
| `POST` | `/verify-email/confirm` | none |
|
|
192
|
-
| `POST` | `/verify-email/resend` | none | Sends a new verification
|
|
198
|
+
| `POST` | `/signup` | none | Creates a user and sends an eight-digit email verification code. |
|
|
199
|
+
| `POST` | `/verify-email/confirm` | none | Confirms a verification code, returns an access token, and sets the refresh cookie. |
|
|
200
|
+
| `POST` | `/verify-email/resend` | none | Sends a new verification code when the account is eligible. |
|
|
193
201
|
| `POST` | `/login` | none | Returns an access token and sets the refresh cookie. |
|
|
194
202
|
| `POST` | `/refresh` | refresh cookie | Rotates the refresh token when needed and returns a new access token. |
|
|
195
203
|
| `POST` | `/logout` | refresh cookie | Revokes the current refresh token and clears the cookie. |
|
|
@@ -215,7 +223,7 @@ The public auth endpoints include an application-layer abuse baseline for open s
|
|
|
215
223
|
| `POST /auth/password-reset` | 30 email-send attempts per hour | 3 email-send attempts per hour |
|
|
216
224
|
| `POST /auth/verify-email/resend` | Shared with password reset | Shared with password reset |
|
|
217
225
|
|
|
218
|
-
Counters are stored in PostgreSQL as fixed windows in `auth_rate_limits`. IP addresses and email addresses are HMAC-SHA256 values before storage; raw identifiers are not persisted.
|
|
226
|
+
Counters are stored in PostgreSQL as fixed windows in `auth_rate_limits`. IP addresses and email addresses are HMAC-SHA256 values before storage; raw identifiers are not persisted. The identifier key is derived from `AUTH_ROOT_KEY` separately from every token key.
|
|
219
227
|
|
|
220
228
|
The limiter uses `request.ip`, so production deployments behind a reverse proxy must configure the API app's `API_TRUST_PROXY` setting. Keep the default `false` when clients connect directly. Use a positive hop count such as `1`, or a trusted proxy IP/CIDR such as `10.0.0.0/8`, when proxy headers are controlled by infrastructure you operate.
|
|
221
229
|
|
|
@@ -223,10 +231,13 @@ This app-layer limiter protects semantic auth work such as Argon2 verification a
|
|
|
223
231
|
|
|
224
232
|
## API
|
|
225
233
|
|
|
226
|
-
The package exports the module
|
|
234
|
+
The package exports a module factory. Pass it the Workspaces inter-module client from the
|
|
235
|
+
application composition:
|
|
227
236
|
|
|
228
237
|
```ts
|
|
229
|
-
import {
|
|
238
|
+
import {createAuthModule} from '@shipfox/api-auth';
|
|
239
|
+
|
|
240
|
+
const authModule = createAuthModule({workspaces});
|
|
230
241
|
```
|
|
231
242
|
|
|
232
243
|
It also exports lower-level pieces for tests and advanced integration:
|
|
@@ -239,7 +250,8 @@ It also exports lower-level pieces for tests and advanced integration:
|
|
|
239
250
|
- `issueRunnerSessionToken(claims)` / `verifyRunnerSessionToken(token)`: mint and verify runner session tokens.
|
|
240
251
|
- `issueJobLeaseToken(claims)` / `verifyJobLeaseToken(token)`: mint and verify job lease tokens.
|
|
241
252
|
- `getClientContext(request)`: reads the authenticated user context from a Fastify request.
|
|
242
|
-
-
|
|
253
|
+
- `findUserByEmail({email})`: read-only lookup of the current owner of a normalized email; see below.
|
|
254
|
+
- Entity types: `User`, `UserStatus`, `RefreshToken`, `PasswordReset`, and `EmailOwner`.
|
|
243
255
|
|
|
244
256
|
### External identity callbacks
|
|
245
257
|
|
|
@@ -287,6 +299,29 @@ safe.
|
|
|
287
299
|
|
|
288
300
|
This pre-verified user state lets an external login method create a session when password login and email-verification routes are disabled.
|
|
289
301
|
|
|
302
|
+
### Email ownership lookup
|
|
303
|
+
|
|
304
|
+
`findUserByEmail({email})` answers "who currently owns this email?" without
|
|
305
|
+
creating a session or changing that user:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import {findUserByEmail} from '@shipfox/api-auth';
|
|
309
|
+
|
|
310
|
+
const owner = await findUserByEmail({email: rawEmail});
|
|
311
|
+
if (owner) {
|
|
312
|
+
// owner: {id, email, status}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
It parses `rawEmail` through the same shared `emailSchema` used by every other
|
|
317
|
+
auth entry point, so whitespace and casing differences resolve to the same
|
|
318
|
+
owner. It returns an owner for every account status (`active`, `suspended`, or
|
|
319
|
+
`deleted`), and `undefined` when no user owns the address. The returned
|
|
320
|
+
`EmailOwner` is an explicit projection of `id`, `email`, and `status` only; it
|
|
321
|
+
never carries a password hash, profile fields, or verification timestamps.
|
|
322
|
+
This seam performs no writes, so it is safe to call before deciding whether to
|
|
323
|
+
provision, link, or reject an identity.
|
|
324
|
+
|
|
290
325
|
`createSessionForUser` accepts either a `userId` or an `email`. It only creates
|
|
291
326
|
a session for an active, verified user. It can throw `UserNotFoundError`,
|
|
292
327
|
`EmailNotVerifiedError`, `InvalidCredentialsError`, or
|
|
@@ -304,18 +339,17 @@ The module creates tables with the `auth_` prefix:
|
|
|
304
339
|
|
|
305
340
|
- `auth_users`
|
|
306
341
|
- `auth_refresh_tokens`
|
|
307
|
-
- `auth_email_verifications`
|
|
308
342
|
- `auth_password_resets`
|
|
309
343
|
- `auth_rate_limits`
|
|
310
344
|
|
|
311
|
-
Passwords use Argon2id.
|
|
345
|
+
Passwords use Argon2id. Password reset tokens and refresh tokens are opaque tokens stored as hashes. Email verification uses the shared email-challenges module.
|
|
312
346
|
|
|
313
347
|
## Behavior Notes
|
|
314
348
|
|
|
315
|
-
- Signup sends
|
|
349
|
+
- Signup sends an eight-digit verification code and returns the new user with its challenge ID.
|
|
316
350
|
- Login only succeeds for active users with verified email addresses and a password hash.
|
|
317
351
|
- Refresh tokens rotate on each refresh.
|
|
318
|
-
- Password reset and email
|
|
352
|
+
- Password reset tokens and email challenge proofs are consumed once.
|
|
319
353
|
- Password change revokes other refresh sessions. It keeps the current session when the current refresh cookie is valid.
|
|
320
354
|
- Password reset requests and verification resend requests do not reveal whether an account exists.
|
|
321
355
|
- Login, password reset, and verification resend requests are rate-limited by IP address and email address.
|
package/dist/config.d.ts
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
import { type Mailer } from '@shipfox/node-mailer';
|
|
2
1
|
export declare const config: Readonly<{
|
|
3
|
-
AUTH_JWT_SECRET: string;
|
|
4
2
|
AUTH_JWT_EXPIRES_IN: string;
|
|
5
|
-
AUTH_JOB_LEASE_TOKEN_SECRET: string;
|
|
6
3
|
AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: string;
|
|
7
|
-
AUTH_RUNNER_SESSION_TOKEN_SECRET: string;
|
|
8
4
|
AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: string;
|
|
9
5
|
AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: number;
|
|
10
6
|
AUTH_REFRESH_ROTATION_GRACE_SECONDS: number;
|
|
11
7
|
AUTH_REFRESH_COOKIE_NAME: string;
|
|
12
8
|
AUTH_PASSWORD_ENABLED: boolean;
|
|
13
|
-
RATE_LIMIT_IDENTIFIER_SECRET: string | undefined;
|
|
14
9
|
CLIENT_BASE_URL: string;
|
|
15
|
-
MAILER_TRANSPORT: "console" | "smtp";
|
|
16
|
-
MAILER_FROM: string;
|
|
17
|
-
SMTP_HOST: string | undefined;
|
|
18
|
-
SMTP_PORT: number;
|
|
19
|
-
SMTP_USER: string | undefined;
|
|
20
|
-
SMTP_PASSWORD: string | undefined;
|
|
21
10
|
} & import("@shipfox/config").CleanedEnvAccessors>;
|
|
22
|
-
export declare const mailer: Mailer;
|
|
23
11
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;;;;;;kDAiCjB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import { bool, createConfig, num, str } from '@shipfox/config';
|
|
2
|
-
import { createConsoleMailer, createSmtpMailer } from '@shipfox/node-mailer';
|
|
3
2
|
export const config = createConfig({
|
|
4
|
-
AUTH_JWT_SECRET: str({
|
|
5
|
-
desc: 'Secret used to sign and verify user access tokens (JWTs). Required, with no default, so startup fails when it is missing.'
|
|
6
|
-
}),
|
|
7
3
|
AUTH_JWT_EXPIRES_IN: str({
|
|
8
4
|
desc: 'How long an access token stays valid. Accepts a duration string such as 15m, 1h, or 7d.',
|
|
9
5
|
default: '15m'
|
|
10
6
|
}),
|
|
11
|
-
AUTH_JOB_LEASE_TOKEN_SECRET: str({
|
|
12
|
-
desc: 'Secret used to sign and verify job lease tokens. Required, with no default, so startup fails when it is missing.'
|
|
13
|
-
}),
|
|
14
7
|
AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: str({
|
|
15
8
|
desc: 'How long a job lease token stays valid. Set it longer than the longest job (JOB_MAX_DURATION is 60 minutes) plus a safety margin.',
|
|
16
9
|
default: '90m'
|
|
17
10
|
}),
|
|
18
|
-
AUTH_RUNNER_SESSION_TOKEN_SECRET: str({
|
|
19
|
-
desc: 'Secret used to sign and verify runner session tokens. Required, with no default, so startup fails when it is missing.'
|
|
20
|
-
}),
|
|
21
11
|
AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: str({
|
|
22
12
|
desc: 'How long a runner session token stays valid. A revoked registration token can leave existing sessions usable until this lifetime ends.',
|
|
23
13
|
default: '1h'
|
|
@@ -38,62 +28,10 @@ export const config = createConfig({
|
|
|
38
28
|
desc: 'Whether password login is available. Use true or false. Defaults to true. When false, password and email-verification routes are not registered, and server startup requires another module to contribute a login method.',
|
|
39
29
|
default: true
|
|
40
30
|
}),
|
|
41
|
-
RATE_LIMIT_IDENTIFIER_SECRET: str({
|
|
42
|
-
desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',
|
|
43
|
-
default: undefined
|
|
44
|
-
}),
|
|
45
31
|
CLIENT_BASE_URL: str({
|
|
46
32
|
desc: 'Base URL of the client app. Used to build links in emails such as password resets.',
|
|
47
33
|
default: 'http://localhost:5173'
|
|
48
|
-
}),
|
|
49
|
-
MAILER_TRANSPORT: str({
|
|
50
|
-
desc: 'How emails are delivered. Use console to print them to the log, or smtp to send them through an SMTP server.',
|
|
51
|
-
choices: [
|
|
52
|
-
'console',
|
|
53
|
-
'smtp'
|
|
54
|
-
],
|
|
55
|
-
default: 'console'
|
|
56
|
-
}),
|
|
57
|
-
MAILER_FROM: str({
|
|
58
|
-
desc: 'Sender address shown on outgoing emails.',
|
|
59
|
-
default: 'noreply@shipfox.local'
|
|
60
|
-
}),
|
|
61
|
-
SMTP_HOST: str({
|
|
62
|
-
desc: 'Hostname of the SMTP server. Required when MAILER_TRANSPORT is smtp.',
|
|
63
|
-
default: undefined
|
|
64
|
-
}),
|
|
65
|
-
SMTP_PORT: num({
|
|
66
|
-
desc: 'Port of the SMTP server.',
|
|
67
|
-
default: 587
|
|
68
|
-
}),
|
|
69
|
-
SMTP_USER: str({
|
|
70
|
-
desc: 'Username for SMTP authentication. Leave it unset if the server needs no login.',
|
|
71
|
-
default: undefined
|
|
72
|
-
}),
|
|
73
|
-
SMTP_PASSWORD: str({
|
|
74
|
-
desc: 'Password for SMTP authentication. Leave it unset if the server needs no login.',
|
|
75
|
-
default: undefined
|
|
76
34
|
})
|
|
77
35
|
});
|
|
78
|
-
function createMailer() {
|
|
79
|
-
if (config.MAILER_TRANSPORT === 'smtp') {
|
|
80
|
-
if (!config.SMTP_HOST) throw new Error('SMTP_HOST is required when MAILER_TRANSPORT=smtp');
|
|
81
|
-
return createSmtpMailer({
|
|
82
|
-
host: config.SMTP_HOST,
|
|
83
|
-
port: config.SMTP_PORT,
|
|
84
|
-
...config.SMTP_USER ? {
|
|
85
|
-
user: config.SMTP_USER
|
|
86
|
-
} : {},
|
|
87
|
-
...config.SMTP_PASSWORD ? {
|
|
88
|
-
password: config.SMTP_PASSWORD
|
|
89
|
-
} : {},
|
|
90
|
-
from: config.MAILER_FROM
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return createConsoleMailer({
|
|
94
|
-
from: config.MAILER_FROM
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
export const mailer = createMailer();
|
|
98
36
|
|
|
99
37
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {bool, createConfig, num, str} from '@shipfox/config';\
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {bool, createConfig, num, str} from '@shipfox/config';\n\nexport const config = createConfig({\n AUTH_JWT_EXPIRES_IN: str({\n desc: 'How long an access token stays valid. Accepts a duration string such as 15m, 1h, or 7d.',\n default: '15m',\n }),\n AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: str({\n desc: 'How long a job lease token stays valid. Set it longer than the longest job (JOB_MAX_DURATION is 60 minutes) plus a safety margin.',\n default: '90m',\n }),\n AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: str({\n desc: 'How long a runner session token stays valid. A revoked registration token can leave existing sessions usable until this lifetime ends.',\n default: '1h',\n }),\n AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: num({\n desc: 'How many days a refresh token stays valid before the user must sign in again.',\n default: 14,\n }),\n AUTH_REFRESH_ROTATION_GRACE_SECONDS: num({\n desc: 'Window after a refresh token is rotated during which the now-rotated token is still accepted, so concurrent refreshes from parallel tabs do not log the user out. Reuse past this window is treated as a compromise and revokes the session.',\n default: 30,\n }),\n AUTH_REFRESH_COOKIE_NAME: str({\n desc: 'Name of the browser cookie that stores the refresh token.',\n default: 'shipfox_refresh_token',\n }),\n AUTH_PASSWORD_ENABLED: bool({\n desc: 'Whether password login is available. Use true or false. Defaults to true. When false, password and email-verification routes are not registered, and server startup requires another module to contribute a login method.',\n default: true,\n }),\n CLIENT_BASE_URL: str({\n desc: 'Base URL of the client app. Used to build links in emails such as password resets.',\n default: 'http://localhost:5173',\n }),\n});\n"],"names":["bool","createConfig","num","str","config","AUTH_JWT_EXPIRES_IN","desc","default","AUTH_JOB_LEASE_TOKEN_EXPIRES_IN","AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","AUTH_REFRESH_ROTATION_GRACE_SECONDS","AUTH_REFRESH_COOKIE_NAME","AUTH_PASSWORD_ENABLED","CLIENT_BASE_URL"],"mappings":"AAAA,SAAQA,IAAI,EAAEC,YAAY,EAAEC,GAAG,EAAEC,GAAG,QAAO,kBAAkB;AAE7D,OAAO,MAAMC,SAASH,aAAa;IACjCI,qBAAqBF,IAAI;QACvBG,MAAM;QACNC,SAAS;IACX;IACAC,iCAAiCL,IAAI;QACnCG,MAAM;QACNC,SAAS;IACX;IACAE,sCAAsCN,IAAI;QACxCG,MAAM;QACNC,SAAS;IACX;IACAG,oCAAoCR,IAAI;QACtCI,MAAM;QACNC,SAAS;IACX;IACAI,qCAAqCT,IAAI;QACvCI,MAAM;QACNC,SAAS;IACX;IACAK,0BAA0BT,IAAI;QAC5BG,MAAM;QACNC,SAAS;IACX;IACAM,uBAAuBb,KAAK;QAC1BM,MAAM;QACNC,SAAS;IACX;IACAO,iBAAiBX,IAAI;QACnBG,MAAM;QACNC,SAAS;IACX;AACF,GAAG"}
|
package/dist/core/auth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WorkspacesInterModuleClient } from '@shipfox/api-workspaces-dto/inter-module';
|
|
1
2
|
import type { User } from './entities/user.js';
|
|
2
3
|
import { AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, UserNotFoundError } from './errors.js';
|
|
3
4
|
export interface SignupParams {
|
|
@@ -14,9 +15,18 @@ export interface ProvisionUserParams {
|
|
|
14
15
|
* Existing users are returned unchanged, including their password and profile.
|
|
15
16
|
*/
|
|
16
17
|
export declare function provisionUser(params: ProvisionUserParams): Promise<User>;
|
|
17
|
-
export
|
|
18
|
+
export type SignupResult = User & {
|
|
19
|
+
emailChallenge: {
|
|
20
|
+
id: string;
|
|
21
|
+
nextResendAvailableAt: Date;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare function signup(params: SignupParams & {
|
|
25
|
+
sourceIp?: string;
|
|
26
|
+
}): Promise<SignupResult>;
|
|
18
27
|
export interface SignupWithInvitationParams extends SignupParams {
|
|
19
28
|
invitationToken: string;
|
|
29
|
+
workspaces: WorkspacesInterModuleClient;
|
|
20
30
|
}
|
|
21
31
|
export interface SignupWithInvitationResult extends LoginResult {
|
|
22
32
|
membership: {
|
|
@@ -37,6 +47,7 @@ export declare function createUser(params: CreateUserParams): Promise<User>;
|
|
|
37
47
|
export interface LoginParams {
|
|
38
48
|
email: string;
|
|
39
49
|
password: string;
|
|
50
|
+
workspaces: WorkspacesInterModuleClient;
|
|
40
51
|
}
|
|
41
52
|
export interface LoginResult {
|
|
42
53
|
token: string;
|
|
@@ -47,6 +58,7 @@ export declare function login(params: LoginParams): Promise<LoginResult>;
|
|
|
47
58
|
export interface CreateSessionForUserParams {
|
|
48
59
|
userId?: string | undefined;
|
|
49
60
|
email?: string | undefined;
|
|
61
|
+
workspaces: WorkspacesInterModuleClient;
|
|
50
62
|
}
|
|
51
63
|
export interface CreateSessionForUserResult {
|
|
52
64
|
token: string;
|
|
@@ -63,6 +75,7 @@ export interface RefreshAccessTokenResult {
|
|
|
63
75
|
}
|
|
64
76
|
export declare function refreshAccessToken(params: {
|
|
65
77
|
refreshToken: string;
|
|
78
|
+
workspaces: WorkspacesInterModuleClient;
|
|
66
79
|
}): Promise<RefreshAccessTokenResult>;
|
|
67
80
|
export interface ConfirmEmailVerificationResult {
|
|
68
81
|
token: string;
|
|
@@ -73,10 +86,15 @@ export interface ResendEmailVerificationResult {
|
|
|
73
86
|
nextResendAvailableAt: Date;
|
|
74
87
|
}
|
|
75
88
|
export declare function confirmEmailVerification(params: {
|
|
76
|
-
|
|
89
|
+
email: string;
|
|
90
|
+
challengeId: string;
|
|
91
|
+
code: string;
|
|
92
|
+
workspaces: WorkspacesInterModuleClient;
|
|
77
93
|
}): Promise<ConfirmEmailVerificationResult>;
|
|
78
94
|
export declare function resendEmailVerification(params: {
|
|
79
95
|
email: string;
|
|
96
|
+
challengeId: string;
|
|
97
|
+
sourceIp: string;
|
|
80
98
|
}): Promise<ResendEmailVerificationResult>;
|
|
81
99
|
export declare function requestPasswordReset(params: {
|
|
82
100
|
email: string;
|
|
@@ -89,6 +107,7 @@ export interface ConfirmPasswordResetResult {
|
|
|
89
107
|
export declare function confirmPasswordReset(params: {
|
|
90
108
|
token: string;
|
|
91
109
|
newPassword: string;
|
|
110
|
+
workspaces: WorkspacesInterModuleClient;
|
|
92
111
|
}): Promise<ConfirmPasswordResetResult>;
|
|
93
112
|
export declare function changePassword(params: {
|
|
94
113
|
userId: string;
|
package/dist/core/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
|