mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix: Credentials overrides no longer blocks UI (#25830)
This commit is contained in:
parent
f71bfff61c
commit
20175243da
|
|
@ -251,27 +251,43 @@ export class Server extends AbstractServer {
|
|||
this.app.post(
|
||||
`/${this.endpointPresetCredentials}`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// If authentication is enforced we can allow multiple overwrites
|
||||
if (!this.presetCredentialsLoaded || authenticationEnforced) {
|
||||
const body = req.body as ICredentialsOverwrite;
|
||||
try {
|
||||
// If authentication is enforced we can allow multiple overwrites
|
||||
if (!this.presetCredentialsLoaded || authenticationEnforced) {
|
||||
const body = req.body as ICredentialsOverwrite;
|
||||
|
||||
if (req.contentType !== 'application/json') {
|
||||
if (req.contentType !== 'application/json') {
|
||||
ResponseHelper.sendErrorResponse(
|
||||
res,
|
||||
new Error(
|
||||
'Body must be a valid JSON, make sure the content-type is application/json',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await Container.get(CredentialsOverwrites).setData(body, true, true);
|
||||
|
||||
this.presetCredentialsLoaded = true;
|
||||
|
||||
// Send push event to notify frontend to refetch types
|
||||
Container.get(Push).broadcast({ type: 'nodeDescriptionUpdated', data: {} });
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, { success: true }, true, 200);
|
||||
} else {
|
||||
ResponseHelper.sendErrorResponse(
|
||||
res,
|
||||
new Error(
|
||||
'Body must be a valid JSON, make sure the content-type is application/json',
|
||||
),
|
||||
new Error('Preset credentials can be set once'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await Container.get(CredentialsOverwrites).setData(body, true, true);
|
||||
|
||||
this.presetCredentialsLoaded = true;
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, { success: true }, true, 200);
|
||||
} else {
|
||||
ResponseHelper.sendErrorResponse(res, new Error('Preset credentials can be set once'));
|
||||
} catch (error) {
|
||||
this.logger.error('Error handling credentials overwrite', { error });
|
||||
ResponseHelper.sendErrorResponse(
|
||||
res,
|
||||
new Error(
|
||||
'An error occurred while handling credentials overwrite, please check the logs for more details',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -376,16 +376,28 @@ export class FrontendService {
|
|||
}
|
||||
|
||||
async generateTypes() {
|
||||
/*
|
||||
* If there are no types and credentials, they have been released from memory
|
||||
* we trigger a re-initialization of the types to ensure they are loaded in memory, post processing triggers type regeneration
|
||||
*/
|
||||
const { types } = this.loadNodesAndCredentials;
|
||||
if (types.nodes.length === 0 && types.credentials.length === 0) {
|
||||
return await this.loadNodesAndCredentials.postProcessLoaders();
|
||||
}
|
||||
|
||||
this.overwriteCredentialsProperties();
|
||||
|
||||
const { staticCacheDir } = this.instanceSettings;
|
||||
// pre-render all the node and credential types as static json files
|
||||
await mkdir(path.join(staticCacheDir, 'types'), { recursive: true });
|
||||
const { credentials, nodes } = this.loadNodesAndCredentials.types;
|
||||
this.writeStaticJSON('nodes', nodes);
|
||||
await this.writeStaticJSON('nodes', nodes);
|
||||
const nodeVersionIdentifiers = this.getNodeVersionIdentifiers(nodes);
|
||||
this.writeStaticJSON('node-versions', nodeVersionIdentifiers);
|
||||
this.writeStaticJSON('credentials', credentials);
|
||||
await this.writeStaticJSON('node-versions', nodeVersionIdentifiers);
|
||||
await this.writeStaticJSON('credentials', credentials);
|
||||
|
||||
// release types to free memory
|
||||
this.loadNodesAndCredentials.releaseTypes();
|
||||
}
|
||||
|
||||
async getSettings(): Promise<FrontendSettings> {
|
||||
|
|
@ -604,27 +616,36 @@ export class FrontendService {
|
|||
return Array.from(identifiers);
|
||||
}
|
||||
|
||||
private writeStaticJSON(
|
||||
private async writeStaticJSON(
|
||||
name: string,
|
||||
data: Array<INodeTypeBaseDescription | ICredentialType | string>,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const { staticCacheDir } = this.instanceSettings;
|
||||
const filePath = path.join(staticCacheDir, `types/${name}.json`);
|
||||
const stream = createWriteStream(filePath, 'utf-8');
|
||||
stream.write('[\n');
|
||||
data.forEach((entry, index) => {
|
||||
stream.write(JSON.stringify(entry));
|
||||
if (index !== data.length - 1) stream.write(',');
|
||||
stream.write('\n');
|
||||
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
stream.on('error', reject);
|
||||
stream.on('finish', resolve);
|
||||
|
||||
stream.write('[\n');
|
||||
data.forEach((entry, index) => {
|
||||
stream.write(JSON.stringify(entry));
|
||||
if (index !== data.length - 1) stream.write(',');
|
||||
stream.write('\n');
|
||||
});
|
||||
stream.write(']\n');
|
||||
stream.end();
|
||||
});
|
||||
stream.write(']\n');
|
||||
stream.end();
|
||||
}
|
||||
|
||||
private overwriteCredentialsProperties() {
|
||||
const { credentials } = this.loadNodesAndCredentials.types;
|
||||
const credentialsOverwrites = this.credentialsOverwrites.getAll();
|
||||
for (const credential of credentials) {
|
||||
// Clear any existing overwritten properties to prevent stale data
|
||||
delete credential.__overwrittenProperties;
|
||||
|
||||
const overwrittenProperties = [];
|
||||
this.credentialTypes
|
||||
.getParentTypes(credential.name)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { testDb } from '@n8n/backend-test-utils';
|
||||
import { mockInstance, testDb } from '@n8n/backend-test-utils';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { SettingsRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
|
@ -7,6 +7,7 @@ import { Cipher } from 'n8n-core';
|
|||
import { CredentialsOverwrites } from '@/credentials-overwrites';
|
||||
import { CredentialTypes } from '@/credential-types';
|
||||
import type { ICredentialsOverwrite } from '@/interfaces';
|
||||
import { FrontendService } from '@/services/frontend.service';
|
||||
|
||||
describe('CredentialsOverwrites - Integration Tests', () => {
|
||||
let credentialsOverwrites: CredentialsOverwrites;
|
||||
|
|
@ -16,6 +17,9 @@ describe('CredentialsOverwrites - Integration Tests', () => {
|
|||
let credentialTypes: CredentialTypes;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Mock FrontendService to prevent initialization issues during tests
|
||||
mockInstance(FrontendService);
|
||||
|
||||
// Initialize real database
|
||||
await testDb.init();
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,29 @@ import type {
|
|||
INodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
import axios from 'axios';
|
||||
import { sleep } from 'n8n-workflow';
|
||||
import type { CreateCredentialDto } from '@n8n/api-types';
|
||||
|
||||
async function fetchCredentialTypesJsonWithRetry(url: string, retries = 5, delay = 500) {
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
const response = await axios.get(url, { withCredentials: true });
|
||||
|
||||
if (
|
||||
typeof response.data === 'object' &&
|
||||
response.data !== null &&
|
||||
Array.isArray(response.data)
|
||||
) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
await sleep(delay * attempt);
|
||||
}
|
||||
|
||||
throw new Error('Could not fetch credential types');
|
||||
}
|
||||
|
||||
export async function getCredentialTypes(baseUrl: string): Promise<ICredentialType[]> {
|
||||
const { data } = await axios.get(baseUrl + 'types/credentials.json', { withCredentials: true });
|
||||
return data;
|
||||
return await fetchCredentialTypesJsonWithRetry(baseUrl + 'types/credentials.json');
|
||||
}
|
||||
|
||||
export async function getCredentialsNewName(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user