From 69e6498d24248d046a1ab2e2cf2edea1f72d0f48 Mon Sep 17 00:00:00 2001 From: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:42:38 +0100 Subject: [PATCH] fix: Put static types files behind authentication (#18660) --- .../auth-service-browser-id-whitelist.test.ts | 52 +++++++++++++++++++ packages/cli/src/auth/auth.service.ts | 4 ++ packages/cli/src/server.ts | 18 +++++++ 3 files changed, 74 insertions(+) create mode 100644 packages/cli/src/auth/__tests__/auth-service-browser-id-whitelist.test.ts diff --git a/packages/cli/src/auth/__tests__/auth-service-browser-id-whitelist.test.ts b/packages/cli/src/auth/__tests__/auth-service-browser-id-whitelist.test.ts new file mode 100644 index 00000000000..6eea947db08 --- /dev/null +++ b/packages/cli/src/auth/__tests__/auth-service-browser-id-whitelist.test.ts @@ -0,0 +1,52 @@ +import type { GlobalConfig } from '@n8n/config'; +import type { InvalidAuthTokenRepository, UserRepository } from '@n8n/db'; +import { mock } from 'jest-mock-extended'; + +import { AuthService } from '@/auth/auth.service'; +import type { MfaService } from '@/mfa/mfa.service'; +import type { JwtService } from '@/services/jwt.service'; +import type { UrlService } from '@/services/url.service'; + +describe('AuthService Browser ID Whitelist', () => { + let authService: AuthService; + + beforeEach(() => { + const globalConfig = mock({ + endpoints: { rest: 'rest' }, + }); + const jwtService = mock(); + const urlService = mock(); + const userRepository = mock(); + const invalidAuthTokenRepository = mock(); + const mfaService = mock(); + + authService = new AuthService( + globalConfig, + mock(), + mock(), + jwtService, + urlService, + userRepository, + invalidAuthTokenRepository, + mfaService, + ); + }); + + describe('skipBrowserIdCheckEndpoints', () => { + it('should include type files in the skip browser ID check endpoints', () => { + // Access the private property for testing + const skipEndpoints = (authService as any).skipBrowserIdCheckEndpoints; + + expect(skipEndpoints).toContain('/types/nodes.json'); + expect(skipEndpoints).toContain('/types/credentials.json'); + }); + + it('should include oauth callback urls in the skip browser ID check endpoints', () => { + // Access the private property for testing + const skipEndpoints = (authService as any).skipBrowserIdCheckEndpoints; + + expect(skipEndpoints).toContain('/rest/oauth1-credential/callback'); + expect(skipEndpoints).toContain('/rest/oauth2-credential/callback'); + }); + }); +}); diff --git a/packages/cli/src/auth/auth.service.ts b/packages/cli/src/auth/auth.service.ts index 3a38f1dd1f0..48ec2e4235e 100644 --- a/packages/cli/src/auth/auth.service.ts +++ b/packages/cli/src/auth/auth.service.ts @@ -65,6 +65,10 @@ export class AuthService { // oAuth callback urls aren't called by the frontend. therefore we can't send custom header on these requests `/${restEndpoint}/oauth1-credential/callback`, `/${restEndpoint}/oauth2-credential/callback`, + + // Skip browser ID check for type files + '/types/nodes.json', + '/types/credentials.json', ]; } diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index f09cad7018d..626a3d8428c 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -13,6 +13,7 @@ import { jsonParse } from 'n8n-workflow'; import { resolve } from 'path'; import { AbstractServer } from '@/abstract-server'; +import { AuthService } from '@/auth/auth.service'; import config from '@/config'; import { CLI_DIR, EDITOR_UI_DIST_DIR, inE2ETests } from '@/constants'; import { ControllerRegistry } from '@/controller.registry'; @@ -318,6 +319,23 @@ export class Server extends AbstractServer { const maxAge = Time.days.toMilliseconds; const cacheOptions = inE2ETests || inDevelopment ? {} : { maxAge }; const { staticCacheDir } = Container.get(InstanceSettings); + + // Protect type files with authentication regardless of UI availability + const authService = Container.get(AuthService); + const protectedTypeFiles = ['/types/nodes.json', '/types/credentials.json']; + protectedTypeFiles.forEach((path) => { + this.app.get( + path, + authService.createAuthMiddleware(true), + async (_, res: express.Response) => { + res.setHeader('Cache-Control', 'no-cache, must-revalidate'); + res.sendFile(path.substring(1), { + root: staticCacheDir, + }); + }, + ); + }); + if (frontendService) { this.app.use( [