mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-03 02:07:06 +02:00
137 lines
4.5 KiB
TypeScript
137 lines
4.5 KiB
TypeScript
import { testDb, randomValidPassword, mockInstance } from '@n8n/backend-test-utils';
|
|
import { LICENSE_FEATURES } from '@n8n/constants';
|
|
import { GlobalConfig } from '@n8n/config';
|
|
import type { User } from '@n8n/db';
|
|
import { ApiKeyRepository, GLOBAL_MEMBER_ROLE } from '@n8n/db';
|
|
import { Container } from '@n8n/di';
|
|
import { getApiKeyScopesForRole } from '@n8n/permissions';
|
|
|
|
import { License } from '@/license';
|
|
|
|
import { createUser } from './shared/db/users';
|
|
import * as utils from './shared/utils/';
|
|
import { LicenseMocker } from './shared/license';
|
|
|
|
const testServer = utils.setupTestServer({ endpointGroups: ['apiKeys'] });
|
|
let licenseMocker: LicenseMocker;
|
|
|
|
beforeAll(() => {
|
|
// Setup License mocker
|
|
licenseMocker = new LicenseMocker();
|
|
licenseMocker.mock(Container.get(License));
|
|
licenseMocker.enable(LICENSE_FEATURES.API_KEY_SCOPES);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await testDb.truncate(['User']);
|
|
mockInstance(GlobalConfig, { publicApi: { disabled: false } });
|
|
// Reset to licensed by default
|
|
licenseMocker.reset();
|
|
licenseMocker.enable(LICENSE_FEATURES.API_KEY_SCOPES);
|
|
});
|
|
|
|
describe('API Key Scopes License Enforcement', () => {
|
|
let member: User;
|
|
|
|
beforeEach(async () => {
|
|
member = await createUser({ role: GLOBAL_MEMBER_ROLE, password: randomValidPassword() });
|
|
});
|
|
|
|
describe('When feat:apiKeyScopes is NOT licensed', () => {
|
|
test('POST /api-keys should ignore custom scopes and assign all available scopes', async () => {
|
|
licenseMocker.disable(LICENSE_FEATURES.API_KEY_SCOPES);
|
|
|
|
const expiresAt = Date.now() + 1000;
|
|
|
|
const response = await testServer
|
|
.authAgentFor(member)
|
|
.post('/api-keys')
|
|
.send({ label: 'Test API Key', expiresAt, scopes: ['workflow:create'] });
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
const storedApiKey = await Container.get(ApiKeyRepository).findOneByOrFail({
|
|
userId: member.id,
|
|
});
|
|
|
|
// Should have all available scopes for member role, not just the requested one
|
|
const expectedScopes = getApiKeyScopesForRole(member);
|
|
expect(storedApiKey.scopes.sort()).toEqual(expectedScopes.sort());
|
|
expect(storedApiKey.scopes).not.toEqual(['workflow:create']);
|
|
expect(storedApiKey.scopes.length).toBeGreaterThan(1);
|
|
});
|
|
|
|
test('PATCH /api-keys/:id should ignore custom scopes and assign all available scopes', async () => {
|
|
licenseMocker.disable(LICENSE_FEATURES.API_KEY_SCOPES);
|
|
|
|
const createResponse = await testServer
|
|
.authAgentFor(member)
|
|
.post('/api-keys')
|
|
.send({ label: 'Original Label', expiresAt: null, scopes: ['workflow:create'] });
|
|
|
|
const apiKeyId = createResponse.body.data.id;
|
|
|
|
const updateResponse = await testServer
|
|
.authAgentFor(member)
|
|
.patch(`/api-keys/${apiKeyId}`)
|
|
.send({ label: 'Updated Label', scopes: ['workflow:create'] });
|
|
|
|
expect(updateResponse.statusCode).toBe(200);
|
|
|
|
const updatedApiKey = await Container.get(ApiKeyRepository).findOneByOrFail({
|
|
id: apiKeyId,
|
|
});
|
|
|
|
// Should still have all available scopes, not the custom one
|
|
const expectedScopes = getApiKeyScopesForRole(member);
|
|
expect(updatedApiKey.scopes.sort()).toEqual(expectedScopes.sort());
|
|
expect(updatedApiKey.scopes).not.toEqual(['workflow:create']);
|
|
expect(updatedApiKey.label).toBe('Updated Label');
|
|
});
|
|
});
|
|
|
|
describe('When feat:apiKeyScopes IS licensed', () => {
|
|
test('POST /api-keys should honor custom scopes', async () => {
|
|
const expiresAt = Date.now() + 1000;
|
|
|
|
const response = await testServer
|
|
.authAgentFor(member)
|
|
.post('/api-keys')
|
|
.send({ label: 'Test API Key', expiresAt, scopes: ['workflow:create'] });
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
const storedApiKey = await Container.get(ApiKeyRepository).findOneByOrFail({
|
|
userId: member.id,
|
|
});
|
|
|
|
// Should have only the requested scope
|
|
expect(storedApiKey.scopes).toEqual(['workflow:create']);
|
|
});
|
|
|
|
test('PATCH /api-keys/:id should honor custom scopes', async () => {
|
|
const createResponse = await testServer
|
|
.authAgentFor(member)
|
|
.post('/api-keys')
|
|
.send({ label: 'Original Label', expiresAt: null, scopes: ['workflow:create'] });
|
|
|
|
const apiKeyId = createResponse.body.data.id;
|
|
|
|
const updateResponse = await testServer
|
|
.authAgentFor(member)
|
|
.patch(`/api-keys/${apiKeyId}`)
|
|
.send({ label: 'Updated Label', scopes: ['workflow:list'] });
|
|
|
|
expect(updateResponse.statusCode).toBe(200);
|
|
|
|
const updatedApiKey = await Container.get(ApiKeyRepository).findOneByOrFail({
|
|
id: apiKeyId,
|
|
});
|
|
|
|
// Should have the new custom scope
|
|
expect(updatedApiKey.scopes).toEqual(['workflow:list']);
|
|
expect(updatedApiKey.label).toBe('Updated Label');
|
|
});
|
|
});
|
|
});
|