fix(core): Make sure middleware works with legacy API Keys (#13390)

This commit is contained in:
Ricardo Espinoza
2025-02-20 09:10:54 -05:00
committed by GitHub
parent c9c0716a69
commit ca76ef4bc2
3 changed files with 107 additions and 14 deletions
@@ -1,5 +1,8 @@
import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended';
import { DateTime } from 'luxon';
import type { InstanceSettings } from 'n8n-core';
import { randomString } from 'n8n-workflow';
import type { OpenAPIV3 } from 'openapi-types';
import { ApiKeyRepository } from '@/databases/repositories/api-key.repository';
@@ -143,6 +146,85 @@ describe('PublicApiKeyService', () => {
}),
);
});
it('should return false if expired JWT is used', async () => {
//Arrange
const path = '/test';
const method = 'GET';
const apiVersion = 'v1';
const publicApiKeyService = new PublicApiKeyService(
apiKeyRepository,
userRepository,
jwtService,
eventService,
);
const dateInThePast = DateTime.now().minus({ days: 1 }).toUnixInteger();
const owner = await createOwnerWithApiKey({
expiresAt: dateInThePast,
});
const [{ apiKey }] = owner.apiKeys;
const middleware = publicApiKeyService.getAuthMiddleware(apiVersion);
//Act
const response = await middleware(mockReqWith(apiKey, path, method), {}, securitySchema);
//Assert
expect(response).toBe(false);
});
it('should work with non JWT (legacy) api keys', async () => {
//Arrange
const path = '/test';
const method = 'GET';
const apiVersion = 'v1';
const legacyApiKey = `n8n_api_${randomString(10)}`;
const publicApiKeyService = new PublicApiKeyService(
apiKeyRepository,
userRepository,
jwtService,
eventService,
);
const owner = await createOwnerWithApiKey();
const [{ apiKey }] = owner.apiKeys;
await Container.get(ApiKeyRepository).update({ apiKey }, { apiKey: legacyApiKey });
const middleware = publicApiKeyService.getAuthMiddleware(apiVersion);
//Act
const response = await middleware(
mockReqWith(legacyApiKey, path, method),
{},
securitySchema,
);
//Assert
expect(response).toBe(true);
expect(eventService.emit).toHaveBeenCalledTimes(1);
expect(eventService.emit).toHaveBeenCalledWith(
'public-api-invoked',
expect.objectContaining({
userId: owner.id,
path,
method,
apiVersion: 'v1',
}),
);
});
});
describe('redactApiKey', () => {
@@ -17,6 +17,7 @@ const API_KEY_AUDIENCE = 'public-api';
const API_KEY_ISSUER = 'n8n';
const REDACT_API_KEY_REVEAL_COUNT = 4;
const REDACT_API_KEY_MAX_LENGTH = 10;
const PREFIX_LEGACY_API_KEY = 'n8n_api_';
@Service()
export class PublicApiKeyService {
@@ -107,14 +108,17 @@ export class PublicApiKeyService {
if (!user) return false;
try {
this.jwtService.verify(providedApiKey, {
issuer: API_KEY_ISSUER,
audience: API_KEY_AUDIENCE,
});
} catch (e) {
if (e instanceof TokenExpiredError) return false;
throw e;
// Legacy API keys are not JWTs and do not need to be verified.
if (!providedApiKey.startsWith(PREFIX_LEGACY_API_KEY)) {
try {
this.jwtService.verify(providedApiKey, {
issuer: API_KEY_ISSUER,
audience: API_KEY_AUDIENCE,
});
} catch (e) {
if (e instanceof TokenExpiredError) return false;
throw e;
}
}
this.eventService.emit('public-api-invoked', {