@shipfox/api-auth 2.0.0 → 4.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-auth",
3
3
  "license": "MIT",
4
- "version": "2.0.0",
4
+ "version": "4.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -46,35 +46,35 @@
46
46
  "@node-rs/argon2": "^2.0.2",
47
47
  "drizzle-orm": "^0.45.2",
48
48
  "zod": "^4.4.3",
49
- "@shipfox/api-auth-context": "2.0.0",
49
+ "@shipfox/api-auth-context": "3.0.0",
50
50
  "@shipfox/api-auth-dto": "2.0.0",
51
- "@shipfox/config": "1.2.1",
51
+ "@shipfox/api-workspaces": "4.0.0",
52
52
  "@shipfox/api-workspaces-dto": "2.0.0",
53
- "@shipfox/node-drizzle": "0.2.1",
54
- "@shipfox/node-fastify": "0.2.1",
55
- "@shipfox/node-module": "0.2.0",
56
- "@shipfox/node-opentelemetry": "0.5.0",
57
- "@shipfox/node-outbox": "0.2.1",
53
+ "@shipfox/config": "1.2.1",
54
+ "@shipfox/node-drizzle": "0.3.0",
55
+ "@shipfox/node-fastify": "0.2.2",
58
56
  "@shipfox/node-jwt": "0.2.0",
57
+ "@shipfox/node-email": "0.2.1",
58
+ "@shipfox/node-mailer": "0.1.3",
59
+ "@shipfox/node-module": "0.3.1",
60
+ "@shipfox/node-opentelemetry": "0.5.1",
61
+ "@shipfox/node-outbox": "0.2.2",
59
62
  "@shipfox/node-postgres": "0.4.1",
60
63
  "@shipfox/node-rate-limit": "0.2.0",
61
- "@shipfox/node-tokens": "0.2.0",
62
- "@shipfox/node-mailer": "0.1.2",
63
- "@shipfox/api-workspaces": "2.0.0",
64
- "@shipfox/node-email": "0.2.1"
64
+ "@shipfox/node-tokens": "0.2.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/pg": "^8.15.5",
68
68
  "drizzle-kit": "^0.31.10",
69
69
  "fastify": "^5.3.3",
70
70
  "fastify-type-provider-zod": "^6.0.0",
71
- "fishery": "^2.2.2",
71
+ "fishery": "^2.4.0",
72
72
  "jose": "^6.0.0",
73
- "@shipfox/swc": "1.2.5",
74
73
  "@shipfox/biome": "1.8.1",
74
+ "@shipfox/swc": "1.2.5",
75
+ "@shipfox/ts-config": "1.3.8",
75
76
  "@shipfox/typescript": "1.1.6",
76
- "@shipfox/vitest": "1.2.2",
77
- "@shipfox/ts-config": "1.3.8"
77
+ "@shipfox/vitest": "1.2.2"
78
78
  },
79
79
  "scripts": {
80
80
  "build": "shipfox-swc",
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {createConfig, num, str} from '@shipfox/config';
1
+ import {bool, createConfig, num, str} from '@shipfox/config';
2
2
  import {createConsoleMailer, createSmtpMailer, type Mailer} from '@shipfox/node-mailer';
3
3
 
4
4
  export const config = createConfig({
@@ -35,6 +35,10 @@ export const config = createConfig({
35
35
  desc: 'Name of the browser cookie that stores the refresh token.',
36
36
  default: 'shipfox_refresh_token',
37
37
  }),
38
+ AUTH_PASSWORD_ENABLED: bool({
39
+ 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.',
40
+ default: true,
41
+ }),
38
42
  RATE_LIMIT_IDENTIFIER_SECRET: str({
39
43
  desc: 'Optional secret used to HMAC identifiers before storing rate-limit counters. Leave it unset to derive a stable key from AUTH_JWT_SECRET.',
40
44
  default: undefined,
package/src/index.test.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  authEventSchemas,
5
5
  } from '@shipfox/api-auth-dto';
6
6
  import {authModule} from './index.js';
7
+ import {passwordLoginMethods} from './login-methods.js';
7
8
 
8
9
  vi.mock('#config.js', () => ({
9
10
  config: {
@@ -14,12 +15,19 @@ vi.mock('#config.js', () => ({
14
15
  AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: 14,
15
16
  AUTH_REFRESH_ROTATION_GRACE_SECONDS: 30,
16
17
  AUTH_REFRESH_COOKIE_NAME: 'shipfox_refresh_token',
18
+ AUTH_PASSWORD_ENABLED: true,
17
19
  CLIENT_BASE_URL: 'https://app.example.test',
18
20
  },
19
21
  mailer: {send: vi.fn()},
20
22
  }));
21
23
 
22
24
  describe('authModule', () => {
25
+ test('declares password login only when password login is enabled', () => {
26
+ expect(passwordLoginMethods(true)).toEqual([{id: 'password'}]);
27
+ expect(passwordLoginMethods(false)).toEqual([]);
28
+ expect(authModule.loginMethods).toEqual([{id: 'password'}]);
29
+ });
30
+
23
31
  test('registers auth email outbox publisher and subscribers', () => {
24
32
  const publisher = authModule.publishers?.find((pub) => pub.name === 'auth');
25
33
  const events = authModule.subscribers?.map((subscriber) => subscriber.event);
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  } from '@shipfox/api-auth-dto';
7
7
  import type {ShipfoxModule} from '@shipfox/node-module';
8
8
  import {subscriberFactory} from '@shipfox/node-module';
9
+ import {config} from '#config.js';
9
10
  import {db} from '#db/db.js';
10
11
  import {migrationsPath} from '#db/migrations.js';
11
12
  import {authOutbox} from '#db/schema/outbox.js';
@@ -18,6 +19,7 @@ import {
18
19
  onEmailVerificationSendRequested,
19
20
  onPasswordResetSendRequested,
20
21
  } from '#presentation/subscribers/index.js';
22
+ import {passwordLoginMethods} from './login-methods.js';
21
23
 
22
24
  export type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';
23
25
  export type {
@@ -59,6 +61,7 @@ export const authModule: ShipfoxModule = {
59
61
  name: 'auth',
60
62
  database: {db, migrationsPath},
61
63
  auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],
64
+ loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),
62
65
  routes: [authRoutes],
63
66
  e2eRoutes: [authE2eRoutes],
64
67
  publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],
@@ -0,0 +1,5 @@
1
+ import type {LoginMethod} from '@shipfox/node-module';
2
+
3
+ export function passwordLoginMethods(passwordEnabled: boolean): LoginMethod[] {
4
+ return passwordEnabled ? [{id: 'password'}] : [];
5
+ }
@@ -5,6 +5,7 @@ vi.mock('#config.js', () => ({
5
5
  config: {
6
6
  AUTH_REFRESH_COOKIE_NAME: 'shipfox_refresh_token',
7
7
  AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: 14,
8
+ AUTH_PASSWORD_ENABLED: true,
8
9
  },
9
10
  }));
10
11
 
@@ -0,0 +1,67 @@
1
+ import {createApp} from '@shipfox/node-fastify';
2
+ import {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';
3
+ import {buildAuthRoutes} from './index.js';
4
+
5
+ const disabledPasswordPaths = [
6
+ '/auth/signup',
7
+ '/auth/verify-email/confirm',
8
+ '/auth/verify-email/resend',
9
+ '/auth/login',
10
+ '/auth/change-password',
11
+ '/auth/password-reset',
12
+ '/auth/password-reset/confirm',
13
+ ];
14
+
15
+ describe('buildAuthRoutes', () => {
16
+ test('preserves the default route table when password login is enabled', () => {
17
+ const routes = buildAuthRoutes(true);
18
+
19
+ expect(routes.routes.map((route) => ('path' in route ? route.path : route.prefix))).toEqual([
20
+ '/signup',
21
+ '/verify-email/confirm',
22
+ '/verify-email/resend',
23
+ '/login',
24
+ '/refresh',
25
+ '/logout',
26
+ '/me',
27
+ '/change-password',
28
+ '/password-reset',
29
+ '/password-reset/confirm',
30
+ ]);
31
+ });
32
+
33
+ test('keeps only session lifecycle routes when password login is disabled', () => {
34
+ const routes = buildAuthRoutes(false);
35
+
36
+ expect(routes.routes.map((route) => ('path' in route ? route.path : route.prefix))).toEqual([
37
+ '/refresh',
38
+ '/logout',
39
+ '/me',
40
+ ]);
41
+ });
42
+
43
+ test('does not register password or email-verification routes when password login is disabled', async () => {
44
+ const app = await createApp({
45
+ auth: [createJwtAuthMethod()],
46
+ routes: [buildAuthRoutes(false)],
47
+ swagger: false,
48
+ });
49
+
50
+ try {
51
+ for (const url of disabledPasswordPaths) {
52
+ const response = await app.inject({method: 'POST', url});
53
+ expect(response.statusCode).toBe(404);
54
+ }
55
+
56
+ const refresh = await app.inject({method: 'POST', url: '/auth/refresh'});
57
+ const logout = await app.inject({method: 'POST', url: '/auth/logout'});
58
+ const me = await app.inject({method: 'GET', url: '/auth/me'});
59
+
60
+ expect(refresh.statusCode).not.toBe(404);
61
+ expect(logout.statusCode).not.toBe(404);
62
+ expect(me.statusCode).not.toBe(404);
63
+ } finally {
64
+ await app.close();
65
+ }
66
+ });
67
+ });
@@ -1,4 +1,5 @@
1
1
  import type {RouteGroup} from '@shipfox/node-fastify';
2
+ import {config} from '#config.js';
2
3
  import {authCookiePlugin} from '#presentation/auth/refresh-cookie.js';
3
4
  import {verifyEmailConfirmRoute} from './email-verification/verify-email-confirm.js';
4
5
  import {verifyEmailResendRoute} from './email-verification/verify-email-resend.js';
@@ -11,19 +12,24 @@ import {logoutRoute} from './session/logout.js';
11
12
  import {meRoute} from './session/me.js';
12
13
  import {refreshRoute} from './session/refresh.js';
13
14
 
14
- export const authRoutes: RouteGroup = {
15
- prefix: '/auth',
16
- plugins: [authCookiePlugin],
17
- routes: [
18
- signupRoute,
19
- verifyEmailConfirmRoute,
20
- verifyEmailResendRoute,
21
- loginRoute,
22
- refreshRoute,
23
- logoutRoute,
24
- meRoute,
25
- changePasswordRoute,
26
- passwordResetRequestRoute,
27
- passwordResetConfirmRoute,
28
- ],
29
- };
15
+ export function buildAuthRoutes(passwordEnabled: boolean): RouteGroup {
16
+ const passwordRoutes = passwordEnabled
17
+ ? [signupRoute, verifyEmailConfirmRoute, verifyEmailResendRoute, loginRoute]
18
+ : [];
19
+
20
+ return {
21
+ prefix: '/auth',
22
+ plugins: [authCookiePlugin],
23
+ routes: [
24
+ ...passwordRoutes,
25
+ refreshRoute,
26
+ logoutRoute,
27
+ meRoute,
28
+ ...(passwordEnabled
29
+ ? [changePasswordRoute, passwordResetRequestRoute, passwordResetConfirmRoute]
30
+ : []),
31
+ ],
32
+ };
33
+ }
34
+
35
+ export const authRoutes = buildAuthRoutes(config.AUTH_PASSWORD_ENABLED);
package/test/routes.ts CHANGED
@@ -81,6 +81,7 @@ vi.mock('#config.js', () => ({
81
81
  AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: 14,
82
82
  AUTH_REFRESH_ROTATION_GRACE_SECONDS: 30,
83
83
  AUTH_REFRESH_COOKIE_NAME: 'shipfox_refresh_token',
84
+ AUTH_PASSWORD_ENABLED: true,
84
85
  CLIENT_BASE_URL: testConfig.clientBaseUrl,
85
86
  },
86
87
  mailer: testConfig.mailer,