@shipfox/api-auth 2.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 +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/core/auth.d.ts +108 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +476 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/entities/email-verification.d.ts +9 -0
- package/dist/core/entities/email-verification.d.ts.map +1 -0
- package/dist/core/entities/email-verification.js +3 -0
- package/dist/core/entities/email-verification.js.map +1 -0
- package/dist/core/entities/password-reset.d.ts +9 -0
- package/dist/core/entities/password-reset.d.ts.map +1 -0
- package/dist/core/entities/password-reset.js +3 -0
- package/dist/core/entities/password-reset.js.map +1 -0
- package/dist/core/entities/refresh-token.d.ts +12 -0
- package/dist/core/entities/refresh-token.d.ts.map +1 -0
- package/dist/core/entities/refresh-token.js +3 -0
- package/dist/core/entities/refresh-token.js.map +1 -0
- package/dist/core/entities/user.d.ts +12 -0
- package/dist/core/entities/user.d.ts.map +1 -0
- package/dist/core/entities/user.js +3 -0
- package/dist/core/entities/user.js.map +1 -0
- package/dist/core/errors.d.ts +54 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +106 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/job-lease-token.d.ts +12 -0
- package/dist/core/job-lease-token.d.ts.map +1 -0
- package/dist/core/job-lease-token.js +58 -0
- package/dist/core/job-lease-token.js.map +1 -0
- package/dist/core/jwt.d.ts +37 -0
- package/dist/core/jwt.d.ts.map +1 -0
- package/dist/core/jwt.js +46 -0
- package/dist/core/jwt.js.map +1 -0
- package/dist/core/password.d.ts +10 -0
- package/dist/core/password.d.ts.map +1 -0
- package/dist/core/password.js +13 -0
- package/dist/core/password.js.map +1 -0
- package/dist/core/rate-limit.d.ts +34 -0
- package/dist/core/rate-limit.d.ts.map +1 -0
- package/dist/core/rate-limit.js +76 -0
- package/dist/core/rate-limit.js.map +1 -0
- package/dist/core/runner-session-token.d.ts +6 -0
- package/dist/core/runner-session-token.d.ts.map +1 -0
- package/dist/core/runner-session-token.js +37 -0
- package/dist/core/runner-session-token.js.map +1 -0
- package/dist/db/db.d.ts +1723 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +28 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/email-verifications.d.ts +39 -0
- package/dist/db/email-verifications.d.ts.map +1 -0
- package/dist/db/email-verifications.js +95 -0
- package/dist/db/email-verifications.js.map +1 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +5 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/password-resets.d.ts +23 -0
- package/dist/db/password-resets.d.ts.map +1 -0
- package/dist/db/password-resets.js +37 -0
- package/dist/db/password-resets.js.map +1 -0
- package/dist/db/rate-limits.d.ts +18 -0
- package/dist/db/rate-limits.d.ts.map +1 -0
- package/dist/db/rate-limits.js +44 -0
- package/dist/db/rate-limits.js.map +1 -0
- package/dist/db/refresh-tokens.d.ts +44 -0
- package/dist/db/refresh-tokens.d.ts.map +1 -0
- package/dist/db/refresh-tokens.js +71 -0
- package/dist/db/refresh-tokens.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/email-verifications.d.ts +114 -0
- package/dist/db/schema/email-verifications.d.ts.map +1 -0
- package/dist/db/schema/email-verifications.js +35 -0
- package/dist/db/schema/email-verifications.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/password-resets.d.ts +114 -0
- package/dist/db/schema/password-resets.d.ts.map +1 -0
- package/dist/db/schema/password-resets.js +35 -0
- package/dist/db/schema/password-resets.js.map +1 -0
- package/dist/db/schema/rate-limits.d.ts +145 -0
- package/dist/db/schema/rate-limits.d.ts.map +1 -0
- package/dist/db/schema/rate-limits.js +25 -0
- package/dist/db/schema/rate-limits.js.map +1 -0
- package/dist/db/schema/refresh-tokens.d.ts +165 -0
- package/dist/db/schema/refresh-tokens.d.ts.map +1 -0
- package/dist/db/schema/refresh-tokens.js +47 -0
- package/dist/db/schema/refresh-tokens.js.map +1 -0
- package/dist/db/schema/users.d.ts +149 -0
- package/dist/db/schema/users.d.ts.map +1 -0
- package/dist/db/schema/users.js +40 -0
- package/dist/db/schema/users.js.map +1 -0
- package/dist/db/users.d.ts +28 -0
- package/dist/db/users.d.ts.map +1 -0
- package/dist/db/users.js +83 -0
- package/dist/db/users.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +16 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +52 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/auth/bearer-token-auth.d.ts +15 -0
- package/dist/presentation/auth/bearer-token-auth.d.ts.map +1 -0
- package/dist/presentation/auth/bearer-token-auth.js +31 -0
- package/dist/presentation/auth/bearer-token-auth.js.map +1 -0
- package/dist/presentation/auth/jwt-auth.d.ts +10 -0
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -0
- package/dist/presentation/auth/jwt-auth.js +31 -0
- package/dist/presentation/auth/jwt-auth.js.map +1 -0
- package/dist/presentation/auth/lease-token-auth.d.ts +15 -0
- package/dist/presentation/auth/lease-token-auth.d.ts.map +1 -0
- package/dist/presentation/auth/lease-token-auth.js +27 -0
- package/dist/presentation/auth/lease-token-auth.js.map +1 -0
- package/dist/presentation/auth/refresh-cookie.d.ts +7 -0
- package/dist/presentation/auth/refresh-cookie.d.ts.map +1 -0
- package/dist/presentation/auth/refresh-cookie.js +29 -0
- package/dist/presentation/auth/refresh-cookie.js.map +1 -0
- package/dist/presentation/auth/runner-session-auth.d.ts +3 -0
- package/dist/presentation/auth/runner-session-auth.d.ts.map +1 -0
- package/dist/presentation/auth/runner-session-auth.js +16 -0
- package/dist/presentation/auth/runner-session-auth.js.map +1 -0
- package/dist/presentation/dto/user.d.ts +8 -0
- package/dist/presentation/dto/user.d.ts.map +1 -0
- package/dist/presentation/dto/user.js +19 -0
- package/dist/presentation/dto/user.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-session.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-session.js +49 -0
- package/dist/presentation/e2eRoutes/create-session.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-user.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-user.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-user.js +42 -0
- package/dist/presentation/e2eRoutes/create-user.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +3 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +15 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.js +35 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts +2 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.js +27 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +31 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/password/change-password.d.ts +2 -0
- package/dist/presentation/routes/password/change-password.d.ts.map +1 -0
- package/dist/presentation/routes/password/change-password.js +50 -0
- package/dist/presentation/routes/password/change-password.js.map +1 -0
- package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -0
- package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -0
- package/dist/presentation/routes/password/password-reset-confirm.js +36 -0
- package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -0
- package/dist/presentation/routes/password/password-reset-request.d.ts +2 -0
- package/dist/presentation/routes/password/password-reset-request.d.ts.map +1 -0
- package/dist/presentation/routes/password/password-reset-request.js +26 -0
- package/dist/presentation/routes/password/password-reset-request.js.map +1 -0
- package/dist/presentation/routes/rate-limit.d.ts +10 -0
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
- package/dist/presentation/routes/rate-limit.js +101 -0
- package/dist/presentation/routes/rate-limit.js.map +1 -0
- package/dist/presentation/routes/registration/signup.d.ts +2 -0
- package/dist/presentation/routes/registration/signup.d.ts.map +1 -0
- package/dist/presentation/routes/registration/signup.js +82 -0
- package/dist/presentation/routes/registration/signup.js.map +1 -0
- package/dist/presentation/routes/session/login.d.ts +2 -0
- package/dist/presentation/routes/session/login.d.ts.map +1 -0
- package/dist/presentation/routes/session/login.js +48 -0
- package/dist/presentation/routes/session/login.js.map +1 -0
- package/dist/presentation/routes/session/logout.d.ts +2 -0
- package/dist/presentation/routes/session/logout.d.ts.map +1 -0
- package/dist/presentation/routes/session/logout.js +25 -0
- package/dist/presentation/routes/session/logout.js.map +1 -0
- package/dist/presentation/routes/session/me.d.ts +2 -0
- package/dist/presentation/routes/session/me.d.ts.map +1 -0
- package/dist/presentation/routes/session/me.js +42 -0
- package/dist/presentation/routes/session/me.js.map +1 -0
- package/dist/presentation/routes/session/refresh.d.ts +2 -0
- package/dist/presentation/routes/session/refresh.d.ts.map +1 -0
- package/dist/presentation/routes/session/refresh.js +49 -0
- package/dist/presentation/routes/session/refresh.js.map +1 -0
- package/dist/presentation/subscribers/index.d.ts +3 -0
- package/dist/presentation/subscribers/index.d.ts.map +1 -0
- package/dist/presentation/subscribers/index.js +4 -0
- package/dist/presentation/subscribers/index.js.map +1 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +3 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.js +13 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +1 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.d.ts +3 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.js +14 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +81 -0
- package/drizzle/meta/0000_snapshot.json +643 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +88 -0
- package/src/config.ts +88 -0
- package/src/core/auth.test.ts +675 -0
- package/src/core/auth.ts +560 -0
- package/src/core/entities/email-verification.ts +8 -0
- package/src/core/entities/password-reset.ts +8 -0
- package/src/core/entities/refresh-token.ts +11 -0
- package/src/core/entities/user.ts +12 -0
- package/src/core/errors.ts +123 -0
- package/src/core/job-lease-token.test.ts +201 -0
- package/src/core/job-lease-token.ts +83 -0
- package/src/core/jwt.test.ts +180 -0
- package/src/core/jwt.ts +66 -0
- package/src/core/password.test.ts +28 -0
- package/src/core/password.ts +21 -0
- package/src/core/rate-limit.test.ts +462 -0
- package/src/core/rate-limit.ts +130 -0
- package/src/core/runner-session-token.test.ts +126 -0
- package/src/core/runner-session-token.ts +49 -0
- package/src/db/db.ts +28 -0
- package/src/db/email-verifications.test.ts +61 -0
- package/src/db/email-verifications.ts +160 -0
- package/src/db/migrations.ts +4 -0
- package/src/db/password-resets.test.ts +61 -0
- package/src/db/password-resets.ts +69 -0
- package/src/db/rate-limits.ts +69 -0
- package/src/db/refresh-tokens.test.ts +164 -0
- package/src/db/refresh-tokens.ts +149 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/email-verifications.ts +37 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/password-resets.ts +37 -0
- package/src/db/schema/rate-limits.ts +27 -0
- package/src/db/schema/refresh-tokens.ts +43 -0
- package/src/db/schema/users.ts +37 -0
- package/src/db/users.test.ts +75 -0
- package/src/db/users.ts +121 -0
- package/src/index.test.ts +39 -0
- package/src/index.ts +69 -0
- package/src/metrics/index.ts +13 -0
- package/src/metrics/instance.test.ts +69 -0
- package/src/metrics/instance.ts +77 -0
- package/src/presentation/auth/bearer-token-auth.test.ts +99 -0
- package/src/presentation/auth/bearer-token-auth.ts +51 -0
- package/src/presentation/auth/jwt-auth.test.ts +136 -0
- package/src/presentation/auth/jwt-auth.ts +39 -0
- package/src/presentation/auth/lease-token-auth.test.ts +56 -0
- package/src/presentation/auth/lease-token-auth.ts +25 -0
- package/src/presentation/auth/refresh-cookie.test.ts +39 -0
- package/src/presentation/auth/refresh-cookie.ts +34 -0
- package/src/presentation/auth/runner-session-auth.test.ts +54 -0
- package/src/presentation/auth/runner-session-auth.ts +13 -0
- package/src/presentation/dto/user.ts +21 -0
- package/src/presentation/e2eRoutes/create-session.ts +39 -0
- package/src/presentation/e2eRoutes/create-user.ts +38 -0
- package/src/presentation/e2eRoutes/index.test.ts +102 -0
- package/src/presentation/e2eRoutes/index.ts +10 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +66 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.ts +37 -0
- package/src/presentation/routes/email-verification/verify-email-resend.test.ts +100 -0
- package/src/presentation/routes/email-verification/verify-email-resend.ts +26 -0
- package/src/presentation/routes/index.ts +29 -0
- package/src/presentation/routes/password/change-password.test.ts +106 -0
- package/src/presentation/routes/password/change-password.ts +45 -0
- package/src/presentation/routes/password/password-reset-confirm.test.ts +63 -0
- package/src/presentation/routes/password/password-reset-confirm.ts +35 -0
- package/src/presentation/routes/password/password-reset-request.test.ts +56 -0
- package/src/presentation/routes/password/password-reset-request.ts +25 -0
- package/src/presentation/routes/rate-limit.test.ts +341 -0
- package/src/presentation/routes/rate-limit.ts +119 -0
- package/src/presentation/routes/registration/signup.test.ts +284 -0
- package/src/presentation/routes/registration/signup.ts +81 -0
- package/src/presentation/routes/session/login.test.ts +77 -0
- package/src/presentation/routes/session/login.ts +50 -0
- package/src/presentation/routes/session/logout.test.ts +55 -0
- package/src/presentation/routes/session/logout.ts +23 -0
- package/src/presentation/routes/session/me.test.ts +56 -0
- package/src/presentation/routes/session/me.ts +35 -0
- package/src/presentation/routes/session/refresh.test.ts +89 -0
- package/src/presentation/routes/session/refresh.ts +54 -0
- package/src/presentation/subscribers/email-send-requested.test.ts +77 -0
- package/src/presentation/subscribers/index.ts +2 -0
- package/src/presentation/subscribers/on-email-verification-send-requested.ts +10 -0
- package/src/presentation/subscribers/on-password-reset-send-requested.ts +13 -0
- package/test/env.ts +10 -0
- package/test/factories/index.ts +1 -0
- package/test/factories/user.ts +36 -0
- package/test/globalSetup.ts +18 -0
- package/test/index.ts +1 -0
- package/test/routes.ts +275 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +4 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ shipfox-tsc-emit
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ shipfox-tsc-check
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @shipfox/api-auth
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c31a7e0: Adds public auth session and cookie composition APIs with password-less user and idempotent membership provisioning.
|
|
8
|
+
- 1b0d344: Publishes the complete API runtime closure with packed-consumer-safe internal imports and records its exact package set in application releases.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- Updated dependencies [0cd6dd4]
|
|
13
|
+
- Updated dependencies [c31a7e0]
|
|
14
|
+
- Updated dependencies [1b0d344]
|
|
15
|
+
- Updated dependencies [521e006]
|
|
16
|
+
- @shipfox/node-module@0.2.0
|
|
17
|
+
- @shipfox/api-workspaces@2.0.0
|
|
18
|
+
- @shipfox/api-auth-context@2.0.0
|
|
19
|
+
- @shipfox/api-auth-dto@2.0.0
|
|
20
|
+
- @shipfox/api-workspaces-dto@2.0.0
|
|
21
|
+
- @shipfox/node-jwt@0.2.0
|
|
22
|
+
- @shipfox/node-rate-limit@0.2.0
|
|
23
|
+
- @shipfox/node-tokens@0.2.0
|
|
24
|
+
- @shipfox/config@1.2.1
|
|
25
|
+
- @shipfox/node-drizzle@0.2.1
|
|
26
|
+
- @shipfox/node-email@0.2.1
|
|
27
|
+
- @shipfox/node-fastify@0.2.1
|
|
28
|
+
- @shipfox/node-mailer@0.1.2
|
|
29
|
+
- @shipfox/node-opentelemetry@0.5.0
|
|
30
|
+
- @shipfox/node-outbox@0.2.1
|
|
31
|
+
- @shipfox/node-postgres@0.4.1
|
|
32
|
+
|
|
33
|
+
## 0.1.2
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [705dd43]
|
|
38
|
+
- @shipfox/node-outbox@0.2.0
|
|
39
|
+
- @shipfox/api-workspaces@0.1.2
|
|
40
|
+
- @shipfox/node-module@0.1.2
|
|
41
|
+
|
|
42
|
+
## 0.1.1
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- Updated dependencies [ec75cd5]
|
|
47
|
+
- Updated dependencies [6a1fb54]
|
|
48
|
+
- @shipfox/node-drizzle@0.2.0
|
|
49
|
+
- @shipfox/node-postgres@0.4.0
|
|
50
|
+
- @shipfox/api-workspaces@0.1.1
|
|
51
|
+
- @shipfox/node-module@0.1.1
|
|
52
|
+
- @shipfox/node-outbox@0.1.1
|
|
53
|
+
|
|
54
|
+
## 0.1.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- c0a883c: Moves the job lease capability token codec and its claims schema from the runners packages into api-auth/api-auth-dto, renaming its config to `AUTH_JOB_LEASE_TOKEN_*`, so all signed-token codecs live with authentication. Adds a shared leased-job auth context for request-scoped lease claims, and a shared `createLeaseTokenAuthMethod` (the `leased-job` auth method) registered on the auth module so any feature module can protect routes with a lease token by name.
|
|
59
|
+
- 6181819: Adds runner registration sessions with bounded label contracts, session-token auth, and lease-token heartbeat ownership.
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- d02c5fd: Queues auth and workspace transactional emails through module-owned outbox events so account verification, password reset, and invitation sends retry outside request transactions.
|
|
64
|
+
- e250c4c: Propagates `projectId` end-to-end into the job lease token. Workflows sources the `{workspaceId, projectId, workflowRunId, workflowRunAttemptId, jobId, jobExecutionId}` identity tuple and threads it through the runner pending/running job tables and lease claims. This is lease-shape groundwork for per-project log-ingest authorization; the stream-stamping consumer lands separately.
|
|
65
|
+
- b0a0e1a: Expose the auth `config` object through a new `@shipfox/api-auth/config` subpath export, so a module that already depends on auth can read auth-owned settings (such as `AUTH_JOB_LEASE_TOKEN_EXPIRES_IN`) without pulling in the full module graph from the package root.
|
|
66
|
+
- 1c1fb3e: Adds shared fixed-window rate limiting for provisioner token minting and ephemeral runner registration.
|
|
67
|
+
- 1daf39a: Tolerates concurrent refresh-token reuse within a grace window so parallel browser tabs no longer log each other out, and treats reuse past the window as a session compromise.
|
|
68
|
+
- 362b3eb: Scope runner log append authorization to the dispatched step attempt carried by the job lease token.
|
|
69
|
+
- 27770eb: Tightens signup, workspace, and project display-name validation with shared trimming, control and format-character rejection, length limits, and contextual client form errors.
|
|
70
|
+
- fb64f13: Extracts the HS256 sign/verify mechanics into a shared `@shipfox/node-jwt` package and refactors auth user-token signing onto it, leaving the auth public API unchanged.
|
|
71
|
+
- Updated dependencies [cdd8931]
|
|
72
|
+
- Updated dependencies [34ba284]
|
|
73
|
+
- Updated dependencies [5707d6d]
|
|
74
|
+
- Updated dependencies [b9c3f32]
|
|
75
|
+
- Updated dependencies [d02c5fd]
|
|
76
|
+
- Updated dependencies [a81b68c]
|
|
77
|
+
- Updated dependencies [c0a883c]
|
|
78
|
+
- Updated dependencies [72ce351]
|
|
79
|
+
- Updated dependencies [e47f8da]
|
|
80
|
+
- Updated dependencies [7b175f5]
|
|
81
|
+
- Updated dependencies [ae7a63c]
|
|
82
|
+
- Updated dependencies [5729548]
|
|
83
|
+
- Updated dependencies [f92122b]
|
|
84
|
+
- Updated dependencies [e250c4c]
|
|
85
|
+
- Updated dependencies [b0a0e1a]
|
|
86
|
+
- Updated dependencies [857fd73]
|
|
87
|
+
- Updated dependencies [1c1fb3e]
|
|
88
|
+
- Updated dependencies [3afb7e3]
|
|
89
|
+
- Updated dependencies [75520ff]
|
|
90
|
+
- Updated dependencies [4798517]
|
|
91
|
+
- Updated dependencies [362b3eb]
|
|
92
|
+
- Updated dependencies [d6d4862]
|
|
93
|
+
- Updated dependencies [c0a883c]
|
|
94
|
+
- Updated dependencies [6077301]
|
|
95
|
+
- Updated dependencies [3bea87f]
|
|
96
|
+
- Updated dependencies [82d22e4]
|
|
97
|
+
- Updated dependencies [27770eb]
|
|
98
|
+
- Updated dependencies [6181819]
|
|
99
|
+
- Updated dependencies [9c149d1]
|
|
100
|
+
- Updated dependencies [fb64f13]
|
|
101
|
+
- @shipfox/node-email@0.2.0
|
|
102
|
+
- @shipfox/node-fastify@0.2.0
|
|
103
|
+
- @shipfox/node-drizzle@0.1.0
|
|
104
|
+
- @shipfox/api-auth-dto@0.1.0
|
|
105
|
+
- @shipfox/api-workspaces-dto@0.1.0
|
|
106
|
+
- @shipfox/api-workspaces@0.1.0
|
|
107
|
+
- @shipfox/node-tokens@0.1.0
|
|
108
|
+
- @shipfox/api-auth-context@0.1.0
|
|
109
|
+
- @shipfox/node-opentelemetry@0.4.2
|
|
110
|
+
- @shipfox/node-postgres@0.3.2
|
|
111
|
+
- @shipfox/node-module@0.1.0
|
|
112
|
+
- @shipfox/node-outbox@0.1.0
|
|
113
|
+
- @shipfox/node-jwt@0.1.0
|
|
114
|
+
- @shipfox/node-rate-limit@0.1.0
|
|
115
|
+
- @shipfox/node-mailer@0.1.1
|
|
116
|
+
- @shipfox/config@1.2.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shipfox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Shipfox API Auth
|
|
2
|
+
|
|
3
|
+
Shipfox API Auth provides the server-side auth module for Shipfox APIs. It owns user accounts, email verification, login, refresh sessions, password reset, password change, JWT auth, and its PostgreSQL tables.
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
Register the module with the API module runner:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {authModule} from '@shipfox/api-auth';
|
|
11
|
+
import {createApp, listen} from '@shipfox/node-fastify';
|
|
12
|
+
import {initializeModules} from '@shipfox/node-module';
|
|
13
|
+
|
|
14
|
+
const {auth, routes} = await initializeModules({
|
|
15
|
+
modules: [authModule],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await createApp({auth, routes});
|
|
19
|
+
await listen();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This adds:
|
|
23
|
+
|
|
24
|
+
- auth database migrations from `libs/api/auth/drizzle`
|
|
25
|
+
- the JWT auth method used by protected routes
|
|
26
|
+
- routes under `/auth`
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
Install the package from the registry:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pnpm add @shipfox/api-auth
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Required environment:
|
|
37
|
+
|
|
38
|
+
| Variable | Default | Purpose |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `AUTH_JWT_SECRET` | none | Secret used to sign and verify access tokens. |
|
|
41
|
+
| `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
|
+
| `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
|
+
| `AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN` | `1h` | Runner session token lifetime. |
|
|
46
|
+
| `AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS` | `14` | Refresh token and cookie lifetime. |
|
|
47
|
+
| `AUTH_REFRESH_ROTATION_GRACE_SECONDS` | `30` | Grace window for accepting a just-rotated refresh token during concurrent refreshes. |
|
|
48
|
+
| `AUTH_REFRESH_COOKIE_NAME` | `shipfox_refresh_token` | HTTP cookie name for refresh sessions. |
|
|
49
|
+
| `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`. |
|
|
50
|
+
| `CLIENT_BASE_URL` | `http://localhost:5173` | Base URL used in email verification and password reset links. |
|
|
51
|
+
| `MAILER_TRANSPORT` | `console` | Mail transport. Set to `smtp` to send real mail. |
|
|
52
|
+
| `MAILER_FROM` | `noreply@shipfox.local` | Sender used by auth emails. |
|
|
53
|
+
| `SMTP_HOST` | none | Required when `MAILER_TRANSPORT=smtp`. |
|
|
54
|
+
| `SMTP_PORT` | `587` | SMTP server port. |
|
|
55
|
+
| `SMTP_USER` | none | Optional SMTP user. |
|
|
56
|
+
| `SMTP_PASSWORD` | none | Optional SMTP password. |
|
|
57
|
+
|
|
58
|
+
## Security model
|
|
59
|
+
|
|
60
|
+
The module issues three kinds of bearer token, all presented as
|
|
61
|
+
`Authorization: Bearer <token>`. All are **stateless**: each is signed with
|
|
62
|
+
HMAC-SHA256 and verified by checking its signature and expiry alone, with no
|
|
63
|
+
database read on the request path. They differ in who they authenticate and what
|
|
64
|
+
they grant, and the stateless tradeoff is accepted for a different reason in each
|
|
65
|
+
case. Each token class has a dedicated secret and audience, so one token type
|
|
66
|
+
cannot be used in place of another.
|
|
67
|
+
|
|
68
|
+
### User session token
|
|
69
|
+
|
|
70
|
+
- **Design:** a stateless session token. It carries the user's identity and
|
|
71
|
+
current membership list in its claims so protected routes can authorize without
|
|
72
|
+
a per-request user or membership lookup.
|
|
73
|
+
- **Audience:** signed-in users on first-party clients (web app, CLI).
|
|
74
|
+
- **Grants:** the user's own account routes and, downstream, any route that
|
|
75
|
+
authorizes against the membership claims. Its scope is "this user, with these
|
|
76
|
+
memberships" — never a single resource.
|
|
77
|
+
- **Lifecycle:**
|
|
78
|
+
- *Emit* — issued on login, signup verification, and password reset.
|
|
79
|
+
- *Exchange* — sent on each request; a longer-lived refresh session mints a
|
|
80
|
+
fresh token when it expires.
|
|
81
|
+
- *Store* — held in client memory only, never persisted to disk or local
|
|
82
|
+
storage. Refresh sessions are stored server-side as hashes, never in raw form.
|
|
83
|
+
- *Discard* — expires on its own (minutes, not hours); the refresh session is
|
|
84
|
+
revoked on logout and on password change.
|
|
85
|
+
- **Tradeoff — stale memberships:** because memberships ride in the claims, a
|
|
86
|
+
membership change only takes effect on the next refresh. Accepted because the
|
|
87
|
+
token is short-lived, so the staleness window is bounded and a per-request
|
|
88
|
+
membership read is avoided.
|
|
89
|
+
|
|
90
|
+
### Runner session token
|
|
91
|
+
|
|
92
|
+
A runner session token is the data-plane identity for a running manual runner.
|
|
93
|
+
The runner exchanges its long-lived registration token at startup, then uses the
|
|
94
|
+
short-lived session token to claim jobs. Heartbeat and step/report/log operations
|
|
95
|
+
use the per-job lease token instead.
|
|
96
|
+
|
|
97
|
+
- **Scope — one workspace's runner data plane.** The token names one runner
|
|
98
|
+
session, one workspace, the fixed `workspace` scope, and the session's immutable
|
|
99
|
+
label set. It can claim jobs for that workspace. It is not a user identity and
|
|
100
|
+
does not authorize dashboard management routes.
|
|
101
|
+
- **Labels are self-attested.** A holder of a valid registration token chooses
|
|
102
|
+
the labels it registers with. The labels are immutable after registration and
|
|
103
|
+
signed into the session token, but they are not an authorization boundary in v1.
|
|
104
|
+
Treat the registration token's workspace scope as the trust boundary.
|
|
105
|
+
- **Mechanics.** Signed with HMAC-SHA256 using `AUTH_RUNNER_SESSION_TOKEN_SECRET`
|
|
106
|
+
and the `runner-session` audience. The default lifetime is `1h`, short enough
|
|
107
|
+
to bound the residual claim window when a registration token is revoked.
|
|
108
|
+
- **Tradeoff — no per-session revocation in v1.** Claim stays stateless and does
|
|
109
|
+
not check the runner session row on each poll. Revoking the registration token
|
|
110
|
+
blocks new sessions, but an existing session can keep claiming until
|
|
111
|
+
`AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN` elapses. A job execution claimed before
|
|
112
|
+
session expiry then moves to the narrower job lease token path: heartbeat can
|
|
113
|
+
renew that lease while server state still says the job execution is live, and
|
|
114
|
+
cancellation or terminal job execution state is the revocation boundary for that
|
|
115
|
+
already-claimed work.
|
|
116
|
+
|
|
117
|
+
### Job lease token
|
|
118
|
+
|
|
119
|
+
A single-job **capability** token, not a session. It is the means by which a
|
|
120
|
+
runner proves it is the legitimate holder of one specific job while it reports
|
|
121
|
+
progress and drives that job to completion.
|
|
122
|
+
|
|
123
|
+
- **Scope — exactly one job execution.** The token authorizes action on the
|
|
124
|
+
single job execution it names, for the runner holding it. It is **not** a runner
|
|
125
|
+
identity, it is **not** workspace-wide, and it is **not** a substitute for the
|
|
126
|
+
long-lived runner credential used to claim work in the first place. The
|
|
127
|
+
surrounding identifiers it carries (job, run, workspace, the claiming runner)
|
|
128
|
+
are context for consumers; they do not widen what the bearer may touch. When a
|
|
129
|
+
step is dispatched, the token may also carry the current step id and attempt so
|
|
130
|
+
log append can verify signed membership in that one step attempt without
|
|
131
|
+
querying workflow state.
|
|
132
|
+
- **Trust boundary.** There is exactly one issuer: the scheduling side mints a
|
|
133
|
+
lease when a runner claims a job execution, next-step re-scopes it to the
|
|
134
|
+
dispatched step attempt, and heartbeat re-mints the same narrow capability
|
|
135
|
+
after server state accepts the heartbeat. Everything
|
|
136
|
+
downstream only *verifies* — an in-process signature check, with no callback to
|
|
137
|
+
the issuer. The runner, and the untrusted agent workload it hosts, never mints
|
|
138
|
+
or modifies a token; it only presents the one it was handed.
|
|
139
|
+
- **Mechanics.** Signed with HMAC-SHA256 using a dedicated secret, separate from
|
|
140
|
+
the user-session secret. Its claims name the job, job execution, and surrounding
|
|
141
|
+
context and nothing more, and it carries a fixed audience so a token minted for
|
|
142
|
+
one purpose cannot be replayed against another. It is short-lived (bounded in
|
|
143
|
+
hours, not days), with heartbeat issuing a fresh short lease for the same live
|
|
144
|
+
execution. The signing secret is supplied through configuration, never embedded
|
|
145
|
+
in code or committed. The raw token must **never** be written to logs, traces, or
|
|
146
|
+
error payloads — there is no automatic redaction to fall back on.
|
|
147
|
+
- **Defense in depth — server state is the final authority.** A valid token is
|
|
148
|
+
never sufficient on its own to advance work. On the lease's own request path the
|
|
149
|
+
gate is server-side step and progression state: a report against a step that has
|
|
150
|
+
already reached a terminal state (finished, failed, or cancelled) is ignored, so
|
|
151
|
+
a still-valid token cannot re-drive work that is already done. Job-level
|
|
152
|
+
finalization is enforced outside the lease path. Cancellation flows the other way
|
|
153
|
+
as well — the server can ask the runner to stop at any point, and that request
|
|
154
|
+
rides on the response to each heartbeat rather than depending on the token.
|
|
155
|
+
- **Log append scope.** A step-scoped lease is a signed membership and attempt
|
|
156
|
+
check for append-log authorization. It is not an active-step proof: until the
|
|
157
|
+
runner adopts a later step-scoped token or the lease expires, the bearer can
|
|
158
|
+
append to that step attempt's log stream. This is no broader than the job-scoped
|
|
159
|
+
append authority it replaces, and step completion remains governed by
|
|
160
|
+
server-side report/progression state.
|
|
161
|
+
- **Threat model.** A leaked or replayed token has a deliberately small blast
|
|
162
|
+
radius: it grants action on one already-claimed job execution while that
|
|
163
|
+
execution remains live. It cannot claim new work, impersonate a runner, or reach
|
|
164
|
+
other workspaces. If the *signing secret* leaks, the response is to rotate it;
|
|
165
|
+
rotation invalidates every live lease at once (they fail signature verification)
|
|
166
|
+
and forces fresh claims. Isolating this secret from the user-session secret is
|
|
167
|
+
what lets one be rotated without disrupting the other.
|
|
168
|
+
- **Tradeoff — no per-token revocation.** A single outstanding lease cannot be
|
|
169
|
+
revoked on its own by token ID. The claiming runner's credential is not
|
|
170
|
+
re-checked on each request, so a lease for already-claimed work can continue to
|
|
171
|
+
be renewed by heartbeat until server state cancels, completes, times out, or
|
|
172
|
+
otherwise terminates the job execution. Accepted because access is bounded in
|
|
173
|
+
scope (one execution) and server state is checked on the hot path; a
|
|
174
|
+
per-request credential lookup would sit on the hot progress-reporting path.
|
|
175
|
+
- **Guidelines for future changes.** Keep the authority narrow: do not add claims
|
|
176
|
+
that grant access beyond the single job, keep the lifetime bounded, keep a single
|
|
177
|
+
issuer, and keep every other side verify-only. If the no-revocation window ever
|
|
178
|
+
proves too wide, the right fix is to bind the lease to live runner or job state —
|
|
179
|
+
not to broaden what the token itself can authorize.
|
|
180
|
+
|
|
181
|
+
## Routes
|
|
182
|
+
|
|
183
|
+
All routes are mounted under `/auth`.
|
|
184
|
+
|
|
185
|
+
| Method | Path | Auth | Result |
|
|
186
|
+
| --- | --- | --- | --- |
|
|
187
|
+
| `POST` | `/signup` | none | Creates a user and sends an email verification link. |
|
|
188
|
+
| `POST` | `/verify-email/confirm` | none | Verifies email, returns an access token, and sets the refresh cookie. |
|
|
189
|
+
| `POST` | `/verify-email/resend` | none | Sends a new verification email when the account is eligible. |
|
|
190
|
+
| `POST` | `/login` | none | Returns an access token and sets the refresh cookie. |
|
|
191
|
+
| `POST` | `/refresh` | refresh cookie | Rotates the refresh token when needed and returns a new access token. |
|
|
192
|
+
| `POST` | `/logout` | refresh cookie | Revokes the current refresh token and clears the cookie. |
|
|
193
|
+
| `GET` | `/me` | bearer token | Returns the signed-in user. |
|
|
194
|
+
| `POST` | `/change-password` | bearer token | Changes the password and revokes other refresh sessions. |
|
|
195
|
+
| `POST` | `/password-reset` | none | Sends a password reset email when the account is eligible. |
|
|
196
|
+
| `POST` | `/password-reset/confirm` | none | Sets a new password, returns an access token, and sets the refresh cookie. |
|
|
197
|
+
|
|
198
|
+
Protected routes use the `Authorization: Bearer <token>` header. The refresh flow uses an HTTP-only cookie on the `/auth` path.
|
|
199
|
+
|
|
200
|
+
> [!IMPORTANT]
|
|
201
|
+
> Refresh cookies are set with `secure: true`, `httpOnly: true`, and `sameSite: "lax"`. Local browser tests need HTTPS or a test path that handles secure cookies.
|
|
202
|
+
|
|
203
|
+
### Rate limiting
|
|
204
|
+
|
|
205
|
+
The public auth endpoints include an application-layer abuse baseline for open source installs:
|
|
206
|
+
|
|
207
|
+
| Route | IP bucket | Email bucket |
|
|
208
|
+
| --- | --- | --- |
|
|
209
|
+
| `POST /auth/login` | 60 attempts per 5 minutes | 10 attempts per 15 minutes |
|
|
210
|
+
| `POST /auth/password-reset` | 30 email-send attempts per hour | 3 email-send attempts per hour |
|
|
211
|
+
| `POST /auth/verify-email/resend` | Shared with password reset | Shared with password reset |
|
|
212
|
+
|
|
213
|
+
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. `RATE_LIMIT_IDENTIFIER_SECRET` is optional. Set it when you want a dedicated HMAC secret, or leave it unset to derive the key from `AUTH_JWT_SECRET`.
|
|
214
|
+
|
|
215
|
+
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.
|
|
216
|
+
|
|
217
|
+
This app-layer limiter protects semantic auth work such as Argon2 verification and email sending. It is not a volumetric DDoS control. Public production deployments should still use load balancer, CDN, WAF, or firewall rate limits at the network edge.
|
|
218
|
+
|
|
219
|
+
## API
|
|
220
|
+
|
|
221
|
+
The package exports the module entry point:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import {authModule} from '@shipfox/api-auth';
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
It also exports lower-level pieces for tests and advanced integration:
|
|
228
|
+
|
|
229
|
+
- `routes`: the `/auth` route group.
|
|
230
|
+
- `db` and `migrationsPath`: the Drizzle database handle and migration path.
|
|
231
|
+
- `createJwtAuthMethod()`: the Fastify auth method for user JWTs.
|
|
232
|
+
- `createRunnerSessionAuthMethod()`: the Fastify auth method for runner session tokens.
|
|
233
|
+
- `createLeaseTokenAuthMethod()`: the Fastify auth method for job lease tokens.
|
|
234
|
+
- `issueRunnerSessionToken(claims)` / `verifyRunnerSessionToken(token)`: mint and verify runner session tokens.
|
|
235
|
+
- `issueJobLeaseToken(claims)` / `verifyJobLeaseToken(token)`: mint and verify job lease tokens.
|
|
236
|
+
- `getClientContext(request)`: reads the authenticated user context from a Fastify request.
|
|
237
|
+
- Entity types: `User`, `UserStatus`, `RefreshToken`, `EmailVerification`, and `PasswordReset`.
|
|
238
|
+
|
|
239
|
+
### External identity callbacks
|
|
240
|
+
|
|
241
|
+
An external identity route can use these APIs to provision a user. It can then
|
|
242
|
+
create a normal Shipfox session and set its refresh cookie:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import {
|
|
246
|
+
authCookiePlugin,
|
|
247
|
+
createSessionForUser,
|
|
248
|
+
provisionUser,
|
|
249
|
+
setRefreshTokenCookie,
|
|
250
|
+
} from '@shipfox/api-auth';
|
|
251
|
+
import type {FastifyReply} from 'fastify';
|
|
252
|
+
|
|
253
|
+
export const callbackRoutePlugins = [authCookiePlugin];
|
|
254
|
+
|
|
255
|
+
export async function completeProviderCallback(
|
|
256
|
+
reply: FastifyReply,
|
|
257
|
+
profile: {email: string; name?: string},
|
|
258
|
+
): Promise<string> {
|
|
259
|
+
const user = await provisionUser(profile);
|
|
260
|
+
const session = await createSessionForUser({userId: user.id});
|
|
261
|
+
|
|
262
|
+
setRefreshTokenCookie(reply, session.refreshToken);
|
|
263
|
+
return session.token;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The provider must prove it owns `profile.email` before calling `provisionUser`.
|
|
268
|
+
For OAuth providers, require a verified-email claim such as `email_verified`.
|
|
269
|
+
`provisionUser` matches existing emails, and `createSessionForUser` can mint a
|
|
270
|
+
session for any active, verified account. An unverified provider email could
|
|
271
|
+
otherwise sign in as an existing password account with the same address.
|
|
272
|
+
|
|
273
|
+
Add `authCookiePlugin` to the callback route group before calling a cookie
|
|
274
|
+
helper. `getRefreshTokenCookie`, `setRefreshTokenCookie`, and
|
|
275
|
+
`clearRefreshTokenCookie` use the configured cookie name and the `/auth` path.
|
|
276
|
+
|
|
277
|
+
`provisionUser({email, name?})` uses the same email schema as the auth routes.
|
|
278
|
+
It makes an active, verified user with no password hash. If the email already
|
|
279
|
+
exists, it returns that user unchanged. It does not change the name, email,
|
|
280
|
+
status, verification state, or password. Repeated and concurrent callbacks are
|
|
281
|
+
safe.
|
|
282
|
+
|
|
283
|
+
`createSessionForUser` accepts either a `userId` or an `email`. It only creates
|
|
284
|
+
a session for an active, verified user. It can throw `UserNotFoundError`,
|
|
285
|
+
`EmailNotVerifiedError`, `InvalidCredentialsError`, or
|
|
286
|
+
`AuthDependencyUnavailableError`. `CreateSessionForUserParams`,
|
|
287
|
+
`CreateSessionForUserResult`, and `CreateSessionForUserError` describe that
|
|
288
|
+
public contract.
|
|
289
|
+
|
|
290
|
+
`createJwtAuthMethod`, `createRunnerSessionAuthMethod`, and
|
|
291
|
+
`createLeaseTokenAuthMethod` make request-auth methods. They do not add a
|
|
292
|
+
user-facing login method.
|
|
293
|
+
|
|
294
|
+
## Data Model
|
|
295
|
+
|
|
296
|
+
The module creates tables with the `auth_` prefix:
|
|
297
|
+
|
|
298
|
+
- `auth_users`
|
|
299
|
+
- `auth_refresh_tokens`
|
|
300
|
+
- `auth_email_verifications`
|
|
301
|
+
- `auth_password_resets`
|
|
302
|
+
- `auth_rate_limits`
|
|
303
|
+
|
|
304
|
+
Passwords use Argon2id. Email verification tokens, password reset tokens, and refresh tokens are opaque tokens stored as hashes.
|
|
305
|
+
|
|
306
|
+
## Behavior Notes
|
|
307
|
+
|
|
308
|
+
- Signup sends a verification email and returns the new user.
|
|
309
|
+
- Login only succeeds for active users with verified email addresses and a password hash.
|
|
310
|
+
- Refresh tokens rotate on each refresh.
|
|
311
|
+
- Password reset and email verification consume their tokens once.
|
|
312
|
+
- Password change revokes other refresh sessions. It keeps the current session when the current refresh cookie is valid.
|
|
313
|
+
- Password reset requests and verification resend requests do not reveal whether an account exists.
|
|
314
|
+
- Login, password reset, and verification resend requests are rate-limited by IP address and email address.
|
|
315
|
+
|
|
316
|
+
## Development
|
|
317
|
+
|
|
318
|
+
Run checks for this package:
|
|
319
|
+
|
|
320
|
+
```sh
|
|
321
|
+
turbo check --filter=@shipfox/api-auth
|
|
322
|
+
turbo type --filter=@shipfox/api-auth
|
|
323
|
+
turbo test --filter=@shipfox/api-auth
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Tests use Vitest and a real PostgreSQL database. Start local services before running the test suite:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
docker compose up -d
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The test environment uses the `api_test` database and sets fake auth secrets in `test/env.ts`.
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Mailer } from '@shipfox/node-mailer';
|
|
2
|
+
export declare const config: Readonly<{
|
|
3
|
+
AUTH_JWT_SECRET: string;
|
|
4
|
+
AUTH_JWT_EXPIRES_IN: string;
|
|
5
|
+
AUTH_JOB_LEASE_TOKEN_SECRET: string;
|
|
6
|
+
AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: string;
|
|
7
|
+
AUTH_RUNNER_SESSION_TOKEN_SECRET: string;
|
|
8
|
+
AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: string;
|
|
9
|
+
AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: number;
|
|
10
|
+
AUTH_REFRESH_ROTATION_GRACE_SECONDS: number;
|
|
11
|
+
AUTH_REFRESH_COOKIE_NAME: string;
|
|
12
|
+
RATE_LIMIT_IDENTIFIER_SECRET: string | undefined;
|
|
13
|
+
CLIENT_BASE_URL: string;
|
|
14
|
+
MAILER_TRANSPORT: "console" | "smtp";
|
|
15
|
+
MAILER_FROM: string;
|
|
16
|
+
SMTP_HOST: string | undefined;
|
|
17
|
+
SMTP_PORT: number;
|
|
18
|
+
SMTP_USER: string | undefined;
|
|
19
|
+
SMTP_PASSWORD: string | undefined;
|
|
20
|
+
} & import("@shipfox/config").CleanedEnvAccessors>;
|
|
21
|
+
export declare const mailer: Mailer;
|
|
22
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAwC,KAAK,MAAM,EAAC,MAAM,sBAAsB,CAAC;AAExF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;kDAmEjB,CAAC;AAiBH,eAAO,MAAM,MAAM,QAAiB,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createConfig, num, str } from '@shipfox/config';
|
|
2
|
+
import { createConsoleMailer, createSmtpMailer } from '@shipfox/node-mailer';
|
|
3
|
+
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
|
+
AUTH_JWT_EXPIRES_IN: str({
|
|
8
|
+
desc: 'How long an access token stays valid. Accepts a duration string such as 15m, 1h, or 7d.',
|
|
9
|
+
default: '15m'
|
|
10
|
+
}),
|
|
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
|
+
AUTH_JOB_LEASE_TOKEN_EXPIRES_IN: str({
|
|
15
|
+
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
|
+
default: '90m'
|
|
17
|
+
}),
|
|
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
|
+
AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN: str({
|
|
22
|
+
desc: 'How long a runner session token stays valid. A revoked registration token can leave existing sessions usable until this lifetime ends.',
|
|
23
|
+
default: '1h'
|
|
24
|
+
}),
|
|
25
|
+
AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: num({
|
|
26
|
+
desc: 'How many days a refresh token stays valid before the user must sign in again.',
|
|
27
|
+
default: 14
|
|
28
|
+
}),
|
|
29
|
+
AUTH_REFRESH_ROTATION_GRACE_SECONDS: num({
|
|
30
|
+
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.',
|
|
31
|
+
default: 30
|
|
32
|
+
}),
|
|
33
|
+
AUTH_REFRESH_COOKIE_NAME: str({
|
|
34
|
+
desc: 'Name of the browser cookie that stores the refresh token.',
|
|
35
|
+
default: 'shipfox_refresh_token'
|
|
36
|
+
}),
|
|
37
|
+
RATE_LIMIT_IDENTIFIER_SECRET: str({
|
|
38
|
+
desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',
|
|
39
|
+
default: undefined
|
|
40
|
+
}),
|
|
41
|
+
CLIENT_BASE_URL: str({
|
|
42
|
+
desc: 'Base URL of the client app. Used to build links in emails such as password resets.',
|
|
43
|
+
default: 'http://localhost:5173'
|
|
44
|
+
}),
|
|
45
|
+
MAILER_TRANSPORT: str({
|
|
46
|
+
desc: 'How emails are delivered. Use console to print them to the log, or smtp to send them through an SMTP server.',
|
|
47
|
+
choices: [
|
|
48
|
+
'console',
|
|
49
|
+
'smtp'
|
|
50
|
+
],
|
|
51
|
+
default: 'console'
|
|
52
|
+
}),
|
|
53
|
+
MAILER_FROM: str({
|
|
54
|
+
desc: 'Sender address shown on outgoing emails.',
|
|
55
|
+
default: 'noreply@shipfox.local'
|
|
56
|
+
}),
|
|
57
|
+
SMTP_HOST: str({
|
|
58
|
+
desc: 'Hostname of the SMTP server. Required when MAILER_TRANSPORT is smtp.',
|
|
59
|
+
default: undefined
|
|
60
|
+
}),
|
|
61
|
+
SMTP_PORT: num({
|
|
62
|
+
desc: 'Port of the SMTP server.',
|
|
63
|
+
default: 587
|
|
64
|
+
}),
|
|
65
|
+
SMTP_USER: str({
|
|
66
|
+
desc: 'Username for SMTP authentication. Leave it unset if the server needs no login.',
|
|
67
|
+
default: undefined
|
|
68
|
+
}),
|
|
69
|
+
SMTP_PASSWORD: str({
|
|
70
|
+
desc: 'Password for SMTP authentication. Leave it unset if the server needs no login.',
|
|
71
|
+
default: undefined
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
function createMailer() {
|
|
75
|
+
if (config.MAILER_TRANSPORT === 'smtp') {
|
|
76
|
+
if (!config.SMTP_HOST) throw new Error('SMTP_HOST is required when MAILER_TRANSPORT=smtp');
|
|
77
|
+
return createSmtpMailer({
|
|
78
|
+
host: config.SMTP_HOST,
|
|
79
|
+
port: config.SMTP_PORT,
|
|
80
|
+
...config.SMTP_USER ? {
|
|
81
|
+
user: config.SMTP_USER
|
|
82
|
+
} : {},
|
|
83
|
+
...config.SMTP_PASSWORD ? {
|
|
84
|
+
password: config.SMTP_PASSWORD
|
|
85
|
+
} : {},
|
|
86
|
+
from: config.MAILER_FROM
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return createConsoleMailer({
|
|
90
|
+
from: config.MAILER_FROM
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export const mailer = createMailer();
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig, num, str} from '@shipfox/config';\nimport {createConsoleMailer, createSmtpMailer, type Mailer} from '@shipfox/node-mailer';\n\nexport const config = createConfig({\n AUTH_JWT_SECRET: str({\n desc: 'Secret used to sign and verify user access tokens (JWTs). Required, with no default, so startup fails when it is missing.',\n }),\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_SECRET: str({\n desc: 'Secret used to sign and verify job lease tokens. Required, with no default, so startup fails when it is missing.',\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_SECRET: str({\n desc: 'Secret used to sign and verify runner session tokens. Required, with no default, so startup fails when it is missing.',\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 RATE_LIMIT_IDENTIFIER_SECRET: str({\n desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',\n default: undefined,\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 MAILER_TRANSPORT: str({\n desc: 'How emails are delivered. Use console to print them to the log, or smtp to send them through an SMTP server.',\n choices: ['console', 'smtp'],\n default: 'console',\n }),\n MAILER_FROM: str({\n desc: 'Sender address shown on outgoing emails.',\n default: 'noreply@shipfox.local',\n }),\n SMTP_HOST: str({\n desc: 'Hostname of the SMTP server. Required when MAILER_TRANSPORT is smtp.',\n default: undefined,\n }),\n SMTP_PORT: num({\n desc: 'Port of the SMTP server.',\n default: 587,\n }),\n SMTP_USER: str({\n desc: 'Username for SMTP authentication. Leave it unset if the server needs no login.',\n default: undefined,\n }),\n SMTP_PASSWORD: str({\n desc: 'Password for SMTP authentication. Leave it unset if the server needs no login.',\n default: undefined,\n }),\n});\n\nfunction createMailer(): Mailer {\n if (config.MAILER_TRANSPORT === 'smtp') {\n if (!config.SMTP_HOST) throw new Error('SMTP_HOST is required when MAILER_TRANSPORT=smtp');\n return createSmtpMailer({\n host: config.SMTP_HOST,\n port: config.SMTP_PORT,\n ...(config.SMTP_USER ? {user: config.SMTP_USER} : {}),\n ...(config.SMTP_PASSWORD ? {password: config.SMTP_PASSWORD} : {}),\n from: config.MAILER_FROM,\n });\n }\n\n return createConsoleMailer({from: config.MAILER_FROM});\n}\n\nexport const mailer = createMailer();\n"],"names":["createConfig","num","str","createConsoleMailer","createSmtpMailer","config","AUTH_JWT_SECRET","desc","AUTH_JWT_EXPIRES_IN","default","AUTH_JOB_LEASE_TOKEN_SECRET","AUTH_JOB_LEASE_TOKEN_EXPIRES_IN","AUTH_RUNNER_SESSION_TOKEN_SECRET","AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","AUTH_REFRESH_ROTATION_GRACE_SECONDS","AUTH_REFRESH_COOKIE_NAME","RATE_LIMIT_IDENTIFIER_SECRET","undefined","CLIENT_BASE_URL","MAILER_TRANSPORT","choices","MAILER_FROM","SMTP_HOST","SMTP_PORT","SMTP_USER","SMTP_PASSWORD","createMailer","Error","host","port","user","password","from","mailer"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,GAAG,EAAEC,GAAG,QAAO,kBAAkB;AACvD,SAAQC,mBAAmB,EAAEC,gBAAgB,QAAoB,uBAAuB;AAExF,OAAO,MAAMC,SAASL,aAAa;IACjCM,iBAAiBJ,IAAI;QACnBK,MAAM;IACR;IACAC,qBAAqBN,IAAI;QACvBK,MAAM;QACNE,SAAS;IACX;IACAC,6BAA6BR,IAAI;QAC/BK,MAAM;IACR;IACAI,iCAAiCT,IAAI;QACnCK,MAAM;QACNE,SAAS;IACX;IACAG,kCAAkCV,IAAI;QACpCK,MAAM;IACR;IACAM,sCAAsCX,IAAI;QACxCK,MAAM;QACNE,SAAS;IACX;IACAK,oCAAoCb,IAAI;QACtCM,MAAM;QACNE,SAAS;IACX;IACAM,qCAAqCd,IAAI;QACvCM,MAAM;QACNE,SAAS;IACX;IACAO,0BAA0Bd,IAAI;QAC5BK,MAAM;QACNE,SAAS;IACX;IACAQ,8BAA8Bf,IAAI;QAChCK,MAAM;QACNE,SAASS;IACX;IACAC,iBAAiBjB,IAAI;QACnBK,MAAM;QACNE,SAAS;IACX;IACAW,kBAAkBlB,IAAI;QACpBK,MAAM;QACNc,SAAS;YAAC;YAAW;SAAO;QAC5BZ,SAAS;IACX;IACAa,aAAapB,IAAI;QACfK,MAAM;QACNE,SAAS;IACX;IACAc,WAAWrB,IAAI;QACbK,MAAM;QACNE,SAASS;IACX;IACAM,WAAWvB,IAAI;QACbM,MAAM;QACNE,SAAS;IACX;IACAgB,WAAWvB,IAAI;QACbK,MAAM;QACNE,SAASS;IACX;IACAQ,eAAexB,IAAI;QACjBK,MAAM;QACNE,SAASS;IACX;AACF,GAAG;AAEH,SAASS;IACP,IAAItB,OAAOe,gBAAgB,KAAK,QAAQ;QACtC,IAAI,CAACf,OAAOkB,SAAS,EAAE,MAAM,IAAIK,MAAM;QACvC,OAAOxB,iBAAiB;YACtByB,MAAMxB,OAAOkB,SAAS;YACtBO,MAAMzB,OAAOmB,SAAS;YACtB,GAAInB,OAAOoB,SAAS,GAAG;gBAACM,MAAM1B,OAAOoB,SAAS;YAAA,IAAI,CAAC,CAAC;YACpD,GAAIpB,OAAOqB,aAAa,GAAG;gBAACM,UAAU3B,OAAOqB,aAAa;YAAA,IAAI,CAAC,CAAC;YAChEO,MAAM5B,OAAOiB,WAAW;QAC1B;IACF;IAEA,OAAOnB,oBAAoB;QAAC8B,MAAM5B,OAAOiB,WAAW;IAAA;AACtD;AAEA,OAAO,MAAMY,SAASP,eAAe"}
|