refactor(core, editor): Remove legacy nodesAccess (no-changelog) (#9016)

This commit is contained in:
Iván Ovejero 2024-04-05 13:17:34 +02:00 committed by GitHub
parent ba986fb018
commit b8ab049932
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 45 additions and 266 deletions

View file

@ -268,7 +268,6 @@ export class CredentialsHelper extends ICredentialsHelper {
return new Credentials( return new Credentials(
{ id: credential.id, name: credential.name }, { id: credential.id, name: credential.name },
credential.type, credential.type,
credential.nodesAccess,
credential.data, credential.data,
); );
} }
@ -483,9 +482,9 @@ export function createCredentialsFromCredentialsEntity(
credential: CredentialsEntity, credential: CredentialsEntity,
encrypt = false, encrypt = false,
): Credentials { ): Credentials {
const { id, name, type, nodesAccess, data } = credential; const { id, name, type, data } = credential;
if (encrypt) { if (encrypt) {
return new Credentials({ id: null, name }, type, nodesAccess); return new Credentials({ id: null, name }, type);
} }
return new Credentials({ id, name }, type, nodesAccess, data); return new Credentials({ id, name }, type, data);
} }

View file

@ -41,20 +41,6 @@ export async function createCredential(
Object.assign(newCredential, properties); Object.assign(newCredential, properties);
if (!newCredential.nodesAccess || newCredential.nodesAccess.length === 0) {
newCredential.nodesAccess = [
{
nodeType: `n8n-nodes-base.${properties.type?.toLowerCase() ?? 'unknown'}`,
date: new Date(),
},
];
} else {
// Add the added date for node access permissions
newCredential.nodesAccess.forEach((nodeAccess) => {
nodeAccess.date = new Date();
});
}
return newCredential; return newCredential;
} }
@ -91,11 +77,7 @@ export async function removeCredential(credentials: CredentialsEntity): Promise<
export async function encryptCredential(credential: CredentialsEntity): Promise<ICredentialsDb> { export async function encryptCredential(credential: CredentialsEntity): Promise<ICredentialsDb> {
// Encrypt the data // Encrypt the data
const coreCredential = new Credentials( const coreCredential = new Credentials({ id: null, name: credential.name }, credential.type);
{ id: null, name: credential.name },
credential.type,
credential.nodesAccess,
);
// @ts-ignore // @ts-ignore
coreCredential.setData(credential.data); coreCredential.setData(credential.data);
@ -115,7 +97,7 @@ export function sanitizeCredentials(
const credentialsList = argIsArray ? credentials : [credentials]; const credentialsList = argIsArray ? credentials : [credentials];
const sanitizedCredentials = credentialsList.map((credential) => { const sanitizedCredentials = credentialsList.map((credential) => {
const { data, nodesAccess, shared, ...rest } = credential; const { data, shared, ...rest } = credential;
return rest; return rest;
}); });

View file

@ -111,9 +111,9 @@ export class ExportCredentialsCommand extends BaseCommand {
if (flags.decrypted) { if (flags.decrypted) {
for (let i = 0; i < credentials.length; i++) { for (let i = 0; i < credentials.length; i++) {
const { name, type, nodesAccess, data } = credentials[i]; const { name, type, data } = credentials[i];
const id = credentials[i].id; const id = credentials[i].id;
const credential = new Credentials({ id, name }, type, nodesAccess, data); const credential = new Credentials({ id, name }, type, data);
const plainData = credential.getData(); const plainData = credential.getData();
(credentials[i] as ICredentialsDecryptedDb).data = plainData; (credentials[i] as ICredentialsDecryptedDb).data = plainData;
} }

View file

@ -141,9 +141,6 @@ export class ImportCredentialsCommand extends BaseCommand {
} }
private async storeCredential(credential: Partial<CredentialsEntity>, user: User) { private async storeCredential(credential: Partial<CredentialsEntity>, user: User) {
if (!credential.nodesAccess) {
credential.nodesAccess = [];
}
const result = await this.transactionManager.upsert(CredentialsEntity, credential, ['id']); const result = await this.transactionManager.upsert(CredentialsEntity, credential, ['id']);
await this.transactionManager.upsert( await this.transactionManager.upsert(
SharedCredentials, SharedCredentials,

View file

@ -95,7 +95,7 @@ export abstract class AbstractOAuthController {
credential: ICredentialsDb, credential: ICredentialsDb,
decryptedData: ICredentialDataDecryptedObject, decryptedData: ICredentialDataDecryptedObject,
) { ) {
const credentials = new Credentials(credential, credential.type, credential.nodesAccess); const credentials = new Credentials(credential, credential.type);
credentials.setData(decryptedData); credentials.setData(decryptedData);
await this.credentialsRepository.update(credential.id, { await this.credentialsRepository.update(credential.id, {
...credentials.getDataToSave(), ...credentials.getDataToSave(),

View file

@ -109,11 +109,6 @@ export class CredentialsService {
await validateEntity(newCredentials); await validateEntity(newCredentials);
// Add the date for newly added node access permissions
for (const nodeAccess of newCredentials.nodesAccess) {
nodeAccess.date = new Date();
}
return newCredentials; return newCredentials;
} }
@ -132,13 +127,6 @@ export class CredentialsService {
await validateEntity(updateData); await validateEntity(updateData);
// Add the date for newly added node access permissions
for (const nodeAccess of updateData.nodesAccess) {
if (!nodeAccess.date) {
nodeAccess.date = new Date();
}
}
// Do not overwrite the oauth data else data like the access or refresh token would get lost // Do not overwrite the oauth data else data like the access or refresh token would get lost
// everytime anybody changes anything on the credentials even if it is just the name. // everytime anybody changes anything on the credentials even if it is just the name.
if (decryptedData.oauthTokenData) { if (decryptedData.oauthTokenData) {
@ -149,11 +137,7 @@ export class CredentialsService {
} }
createEncryptedData(credentialId: string | null, data: CredentialsEntity): ICredentialsDb { createEncryptedData(credentialId: string | null, data: CredentialsEntity): ICredentialsDb {
const credentials = new Credentials( const credentials = new Credentials({ id: credentialId, name: data.name }, data.type);
{ id: credentialId, name: data.name },
data.type,
data.nodesAccess,
);
credentials.setData(data.data as unknown as ICredentialDataDecryptedObject); credentials.setData(data.data as unknown as ICredentialDataDecryptedObject);

View file

@ -1,8 +1,7 @@
import type { ICredentialNodeAccess } from 'n8n-workflow';
import { Column, Entity, Index, OneToMany } from '@n8n/typeorm'; import { Column, Entity, Index, OneToMany } from '@n8n/typeorm';
import { IsArray, IsObject, IsString, Length } from 'class-validator'; import { IsObject, IsString, Length } from 'class-validator';
import type { SharedCredentials } from './SharedCredentials'; import type { SharedCredentials } from './SharedCredentials';
import { WithTimestampsAndStringId, jsonColumnType } from './AbstractEntity'; import { WithTimestampsAndStringId } from './AbstractEntity';
import type { ICredentialsDb } from '@/Interfaces'; import type { ICredentialsDb } from '@/Interfaces';
@Entity() @Entity()
@ -27,8 +26,4 @@ export class CredentialsEntity extends WithTimestampsAndStringId implements ICre
@OneToMany('SharedCredentials', 'credentials') @OneToMany('SharedCredentials', 'credentials')
shared: SharedCredentials[]; shared: SharedCredentials[];
@Column(jsonColumnType)
@IsArray()
nodesAccess: ICredentialNodeAccess[];
} }

View file

@ -0,0 +1,7 @@
import type { IrreversibleMigration, MigrationContext } from '@db/types';
export class RemoveNodesAccess1712044305787 implements IrreversibleMigration {
async up({ schemaBuilder: { dropColumns } }: MigrationContext) {
await dropColumns('credentials_entity', ['nodesAccess']);
}
}

View file

@ -54,6 +54,7 @@ import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlob
import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping'; import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping';
import { RemoveFailedExecutionStatus1711018413374 } from '../common/1711018413374-RemoveFailedExecutionStatus'; import { RemoveFailedExecutionStatus1711018413374 } from '../common/1711018413374-RemoveFailedExecutionStatus';
import { MoveSshKeysToDatabase1711390882123 } from '../common/1711390882123-MoveSshKeysToDatabase'; import { MoveSshKeysToDatabase1711390882123 } from '../common/1711390882123-MoveSshKeysToDatabase';
import { RemoveNodesAccess1712044305787 } from '../common/1712044305787-RemoveNodesAccess';
export const mysqlMigrations: Migration[] = [ export const mysqlMigrations: Migration[] = [
InitialMigration1588157391238, InitialMigration1588157391238,
@ -111,4 +112,5 @@ export const mysqlMigrations: Migration[] = [
DropRoleMapping1705429061930, DropRoleMapping1705429061930,
RemoveFailedExecutionStatus1711018413374, RemoveFailedExecutionStatus1711018413374,
MoveSshKeysToDatabase1711390882123, MoveSshKeysToDatabase1711390882123,
RemoveNodesAccess1712044305787,
]; ];

View file

@ -53,6 +53,7 @@ import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlob
import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping'; import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping';
import { RemoveFailedExecutionStatus1711018413374 } from '../common/1711018413374-RemoveFailedExecutionStatus'; import { RemoveFailedExecutionStatus1711018413374 } from '../common/1711018413374-RemoveFailedExecutionStatus';
import { MoveSshKeysToDatabase1711390882123 } from '../common/1711390882123-MoveSshKeysToDatabase'; import { MoveSshKeysToDatabase1711390882123 } from '../common/1711390882123-MoveSshKeysToDatabase';
import { RemoveNodesAccess1712044305787 } from '../common/1712044305787-RemoveNodesAccess';
export const postgresMigrations: Migration[] = [ export const postgresMigrations: Migration[] = [
InitialMigration1587669153312, InitialMigration1587669153312,
@ -109,4 +110,5 @@ export const postgresMigrations: Migration[] = [
DropRoleMapping1705429061930, DropRoleMapping1705429061930,
RemoveFailedExecutionStatus1711018413374, RemoveFailedExecutionStatus1711018413374,
MoveSshKeysToDatabase1711390882123, MoveSshKeysToDatabase1711390882123,
RemoveNodesAccess1712044305787,
]; ];

View file

@ -51,6 +51,7 @@ import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlob
import { DropRoleMapping1705429061930 } from './1705429061930-DropRoleMapping'; import { DropRoleMapping1705429061930 } from './1705429061930-DropRoleMapping';
import { RemoveFailedExecutionStatus1711018413374 } from '../common/1711018413374-RemoveFailedExecutionStatus'; import { RemoveFailedExecutionStatus1711018413374 } from '../common/1711018413374-RemoveFailedExecutionStatus';
import { MoveSshKeysToDatabase1711390882123 } from '../common/1711390882123-MoveSshKeysToDatabase'; import { MoveSshKeysToDatabase1711390882123 } from '../common/1711390882123-MoveSshKeysToDatabase';
import { RemoveNodesAccess1712044305787 } from '../common/1712044305787-RemoveNodesAccess';
const sqliteMigrations: Migration[] = [ const sqliteMigrations: Migration[] = [
InitialMigration1588102412422, InitialMigration1588102412422,
@ -105,6 +106,7 @@ const sqliteMigrations: Migration[] = [
DropRoleMapping1705429061930, DropRoleMapping1705429061930,
RemoveFailedExecutionStatus1711018413374, RemoveFailedExecutionStatus1711018413374,
MoveSshKeysToDatabase1711390882123, MoveSshKeysToDatabase1711390882123,
RemoveNodesAccess1712044305787,
]; ];
export { sqliteMigrations }; export { sqliteMigrations };

View file

@ -46,7 +46,7 @@ export class CredentialsRepository extends Repository<CredentialsEntity> {
type Select = Array<keyof CredentialsEntity>; type Select = Array<keyof CredentialsEntity>;
const defaultRelations = ['shared', 'shared.user']; const defaultRelations = ['shared', 'shared.user'];
const defaultSelect: Select = ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt']; const defaultSelect: Select = ['id', 'name', 'type', 'createdAt', 'updatedAt'];
if (!listQueryOptions) return { select: defaultSelect, relations: defaultRelations }; if (!listQueryOptions) return { select: defaultSelect, relations: defaultRelations };

View file

@ -25,7 +25,6 @@ import { SourceControlPreferencesService } from './sourceControlPreferences.serv
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import { SourceControlImportService } from './sourceControlImport.service.ee'; import { SourceControlImportService } from './sourceControlImport.service.ee';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import isEqual from 'lodash/isEqual';
import type { SourceControlGetStatus } from './types/sourceControlGetStatus'; import type { SourceControlGetStatus } from './types/sourceControlGetStatus';
import type { TagEntity } from '@db/entities/TagEntity'; import type { TagEntity } from '@db/entities/TagEntity';
import type { Variables } from '@db/entities/Variables'; import type { Variables } from '@db/entities/Variables';
@ -384,7 +383,7 @@ export class SourceControlService {
* Does a comparison between the local and remote workfolder based on NOT the git status, * Does a comparison between the local and remote workfolder based on NOT the git status,
* but certain parameters within the items being synced. * but certain parameters within the items being synced.
* For workflows, it compares the versionIds * For workflows, it compares the versionIds
* For credentials, it compares the name, type and nodeAccess * For credentials, it compares the name and type
* For variables, it compares the name * For variables, it compares the name
* For tags, it compares the name and mapping * For tags, it compares the name and mapping
* @returns either SourceControlledFile[] if verbose is false, * @returns either SourceControlledFile[] if verbose is false,
@ -565,12 +564,7 @@ export class SourceControlService {
> = []; > = [];
credLocalIds.forEach((local) => { credLocalIds.forEach((local) => {
const mismatchingCreds = credRemoteIds.find((remote) => { const mismatchingCreds = credRemoteIds.find((remote) => {
return ( return remote.id === local.id && (remote.name !== local.name || remote.type !== local.type);
remote.id === local.id &&
(remote.name !== local.name ||
remote.type !== local.type ||
!isEqual(remote.nodesAccess, local.nodesAccess))
);
}); });
if (mismatchingCreds) { if (mismatchingCreds) {
credModifiedInEither.push({ credModifiedInEither.push({

View file

@ -240,15 +240,14 @@ export class SourceControlExportService {
} }
await Promise.all( await Promise.all(
credentialsToBeExported.map(async (sharing) => { credentialsToBeExported.map(async (sharing) => {
const { name, type, nodesAccess, data, id } = sharing.credentials; const { name, type, data, id } = sharing.credentials;
const credentials = new Credentials({ id, name }, type, nodesAccess, data); const credentials = new Credentials({ id, name }, type, data);
const stub: ExportableCredential = { const stub: ExportableCredential = {
id, id,
name, name,
type, type,
data: this.replaceCredentialData(credentials.getData()), data: this.replaceCredentialData(credentials.getData()),
nodesAccess,
ownedBy: sharing.user.email, ownedBy: sharing.user.email,
}; };

View file

@ -142,13 +142,12 @@ export class SourceControlImportService {
Array<ExportableCredential & { filename: string }> Array<ExportableCredential & { filename: string }>
> { > {
const localCredentials = await Container.get(CredentialsRepository).find({ const localCredentials = await Container.get(CredentialsRepository).find({
select: ['id', 'name', 'type', 'nodesAccess'], select: ['id', 'name', 'type'],
}); });
return localCredentials.map((local) => ({ return localCredentials.map((local) => ({
id: local.id, id: local.id,
name: local.name, name: local.name,
type: local.type, type: local.type,
nodesAccess: local.nodesAccess,
filename: getCredentialExportPath(local.id, this.credentialExportFolder), filename: getCredentialExportPath(local.id, this.credentialExportFolder),
})) as Array<ExportableCredential & { filename: string }>; })) as Array<ExportableCredential & { filename: string }>;
} }
@ -339,14 +338,13 @@ export class SourceControlImportService {
(e) => e.id === credential.id && e.type === credential.type, (e) => e.id === credential.id && e.type === credential.type,
); );
const { name, type, data, id, nodesAccess } = credential; const { name, type, data, id } = credential;
const newCredentialObject = new Credentials({ id, name }, type, []); const newCredentialObject = new Credentials({ id, name }, type);
if (existingCredential?.data) { if (existingCredential?.data) {
newCredentialObject.data = existingCredential.data; newCredentialObject.data = existingCredential.data;
} else { } else {
newCredentialObject.setData(data); newCredentialObject.setData(data);
} }
newCredentialObject.nodesAccess = nodesAccess || existingCredential?.nodesAccess || [];
this.logger.debug(`Updating credential id ${newCredentialObject.id as string}`); this.logger.debug(`Updating credential id ${newCredentialObject.id as string}`);
await Container.get(CredentialsRepository).upsert(newCredentialObject, ['id']); await Container.get(CredentialsRepository).upsert(newCredentialObject, ['id']);

View file

@ -1,11 +1,10 @@
import type { ICredentialDataDecryptedObject, ICredentialNodeAccess } from 'n8n-workflow'; import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
export interface ExportableCredential { export interface ExportableCredential {
id: string; id: string;
name: string; name: string;
type: string; type: string;
data: ICredentialDataDecryptedObject; data: ICredentialDataDecryptedObject;
nodesAccess: ICredentialNodeAccess[];
/** /**
* Email of the user who owns this credential at the source instance. * Email of the user who owns this credential at the source instance.

View file

@ -2,7 +2,6 @@ import type express from 'express';
import type { import type {
BannerName, BannerName,
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialNodeAccess,
IDataObject, IDataObject,
INodeCredentialTestRequest, INodeCredentialTestRequest,
INodeCredentials, INodeCredentials,
@ -158,7 +157,6 @@ export declare namespace CredentialRequest {
id: string; // delete if sent id: string; // delete if sent
name: string; name: string;
type: string; type: string;
nodesAccess: ICredentialNodeAccess[];
data: ICredentialDataDecryptedObject; data: ICredentialDataDecryptedObject;
}>; }>;

View file

@ -45,6 +45,5 @@ test('import:credentials should import a credential', async () => {
expect(after.length).toBe(1); expect(after.length).toBe(1);
expect(after[0].name).toBe('cred-aws-test'); expect(after[0].name).toBe('cred-aws-test');
expect(after[0].id).toBe('123'); expect(after[0].id).toBe('123');
expect(after[0].nodesAccess).toStrictEqual([]);
mockExit.mockRestore(); mockExit.mockRestore();
}); });

View file

@ -9,7 +9,6 @@
"accessKeyId": "999999999999", "accessKeyId": "999999999999",
"secretAccessKey": "aaaaaaaaaaaaa" "secretAccessKey": "aaaaaaaaaaaaa"
}, },
"type": "aws", "type": "aws"
"nodesAccess": ""
} }
] ]

View file

@ -264,11 +264,10 @@ describe('GET /credentials', () => {
}); });
function validateCredential(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) { function validateCredential(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) {
const { name, type, nodesAccess, sharedWith, ownedBy } = credential; const { name, type, sharedWith, ownedBy } = credential;
expect(typeof name).toBe('string'); expect(typeof name).toBe('string');
expect(typeof type).toBe('string'); expect(typeof type).toBe('string');
expect(typeof nodesAccess[0].nodeType).toBe('string');
expect('data' in credential).toBe(false); expect('data' in credential).toBe(false);
if (sharedWith) expect(Array.isArray(sharedWith)).toBe(true); if (sharedWith) expect(Array.isArray(sharedWith)).toBe(true);

View file

@ -539,7 +539,6 @@ describe('PUT /credentials/:id/share', () => {
function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) { function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) {
expect(typeof credential.name).toBe('string'); expect(typeof credential.name).toBe('string');
expect(typeof credential.type).toBe('string'); expect(typeof credential.type).toBe('string');
expect(typeof credential.nodesAccess[0].nodeType).toBe('string');
expect(credential.ownedBy).toBeDefined(); expect(credential.ownedBy).toBeDefined();
expect(Array.isArray(credential.sharedWith)).toBe(true); expect(Array.isArray(credential.sharedWith)).toBe(true);
} }

View file

@ -96,21 +96,16 @@ describe('POST /credentials', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data; const { id, name, type, data: encryptedData } = response.body.data;
expect(name).toBe(payload.name); expect(name).toBe(payload.name);
expect(type).toBe(payload.type); expect(type).toBe(payload.type);
if (!payload.nodesAccess) {
fail('Payload did not contain a nodesAccess array');
}
expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
expect(encryptedData).not.toBe(payload.data); expect(encryptedData).not.toBe(payload.data);
const credential = await Container.get(CredentialsRepository).findOneByOrFail({ id }); const credential = await Container.get(CredentialsRepository).findOneByOrFail({ id });
expect(credential.name).toBe(payload.name); expect(credential.name).toBe(payload.name);
expect(credential.type).toBe(payload.type); expect(credential.type).toBe(payload.type);
expect(credential.nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
expect(credential.data).not.toBe(payload.data); expect(credential.data).not.toBe(payload.data);
const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({ const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({
@ -258,14 +253,10 @@ describe('PATCH /credentials/:id', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data; const { id, name, type, data: encryptedData } = response.body.data;
expect(name).toBe(patchPayload.name); expect(name).toBe(patchPayload.name);
expect(type).toBe(patchPayload.type); expect(type).toBe(patchPayload.type);
if (!patchPayload.nodesAccess) {
fail('Payload did not contain a nodesAccess array');
}
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
expect(encryptedData).not.toBe(patchPayload.data); expect(encryptedData).not.toBe(patchPayload.data);
@ -273,7 +264,6 @@ describe('PATCH /credentials/:id', () => {
expect(credential.name).toBe(patchPayload.name); expect(credential.name).toBe(patchPayload.name);
expect(credential.type).toBe(patchPayload.type); expect(credential.type).toBe(patchPayload.type);
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
expect(credential.data).not.toBe(patchPayload.data); expect(credential.data).not.toBe(patchPayload.data);
const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({ const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({
@ -304,7 +294,6 @@ describe('PATCH /credentials/:id', () => {
const credentialObject = new Credentials( const credentialObject = new Credentials(
{ id: credential.id, name: credential.name }, { id: credential.id, name: credential.name },
credential.type, credential.type,
credential.nodesAccess,
credential.data, credential.data,
); );
expect(credentialObject.getData()).toStrictEqual(patchPayload.data); expect(credentialObject.getData()).toStrictEqual(patchPayload.data);
@ -327,23 +316,17 @@ describe('PATCH /credentials/:id', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
const { id, name, type, nodesAccess, data: encryptedData } = response.body.data; const { id, name, type, data: encryptedData } = response.body.data;
expect(name).toBe(patchPayload.name); expect(name).toBe(patchPayload.name);
expect(type).toBe(patchPayload.type); expect(type).toBe(patchPayload.type);
if (!patchPayload.nodesAccess) {
fail('Payload did not contain a nodesAccess array');
}
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
expect(encryptedData).not.toBe(patchPayload.data); expect(encryptedData).not.toBe(patchPayload.data);
const credential = await Container.get(CredentialsRepository).findOneByOrFail({ id }); const credential = await Container.get(CredentialsRepository).findOneByOrFail({ id });
expect(credential.name).toBe(patchPayload.name); expect(credential.name).toBe(patchPayload.name);
expect(credential.type).toBe(patchPayload.type); expect(credential.type).toBe(patchPayload.type);
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
expect(credential.data).not.toBe(patchPayload.data); expect(credential.data).not.toBe(patchPayload.data);
const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({ const sharedCredential = await Container.get(SharedCredentialsRepository).findOneOrFail({
@ -545,11 +528,10 @@ describe('GET /credentials/:id', () => {
}); });
function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) { function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedByAndSharedWith) {
const { name, type, nodesAccess, sharedWith, ownedBy } = credential; const { name, type, sharedWith, ownedBy } = credential;
expect(typeof name).toBe('string'); expect(typeof name).toBe('string');
expect(typeof type).toBe('string'); expect(typeof type).toBe('string');
expect(typeof nodesAccess?.[0].nodeType).toBe('string');
if (sharedWith) { if (sharedWith) {
expect(Array.isArray(sharedWith)).toBe(true); expect(Array.isArray(sharedWith)).toBe(true);
@ -568,23 +550,15 @@ function validateMainCredentialData(credential: ListQuery.Credentials.WithOwnedB
const INVALID_PAYLOADS = [ const INVALID_PAYLOADS = [
{ {
type: randomName(), type: randomName(),
nodesAccess: [{ nodeType: randomName() }],
data: { accessToken: randomString(6, 16) }, data: { accessToken: randomString(6, 16) },
}, },
{ {
name: randomName(), name: randomName(),
nodesAccess: [{ nodeType: randomName() }],
data: { accessToken: randomString(6, 16) }, data: { accessToken: randomString(6, 16) },
}, },
{ {
name: randomName(), name: randomName(),
type: randomName(), type: randomName(),
data: { accessToken: randomString(6, 16) },
},
{
name: randomName(),
type: randomName(),
nodesAccess: [{ nodeType: randomName() }],
}, },
{}, {},
undefined, undefined,

View file

@ -54,7 +54,6 @@ describe('SourceControlImportService', () => {
name: 'My Credential', name: 'My Credential',
type: 'someCredentialType', type: 'someCredentialType',
data: {}, data: {},
nodesAccess: [],
ownedBy: member.email, // user at source instance owns credential ownedBy: member.email, // user at source instance owns credential
}; };
@ -90,7 +89,6 @@ describe('SourceControlImportService', () => {
name: 'My Credential', name: 'My Credential',
type: 'someCredentialType', type: 'someCredentialType',
data: {}, data: {},
nodesAccess: [],
ownedBy: null, ownedBy: null,
}; };
@ -126,7 +124,6 @@ describe('SourceControlImportService', () => {
name: 'My Credential', name: 'My Credential',
type: 'someCredentialType', type: 'someCredentialType',
data: {}, data: {},
nodesAccess: [],
ownedBy: 'user@test.com', // user at source instance owns credential ownedBy: 'user@test.com', // user at source instance owns credential
}; };

View file

@ -258,7 +258,6 @@ const credentialPayload = (): CredentialPayload => ({
const dbCredential = () => { const dbCredential = () => {
const credential = credentialPayload(); const credential = credentialPayload();
credential.nodesAccess = [{ nodeType: credential.type }];
return credential; return credential;
}; };
@ -276,13 +275,6 @@ const INVALID_PAYLOADS = [
name: randomName(), name: randomName(),
type: randomName(), type: randomName(),
}, },
{
name: randomName(),
type: 'ftp',
data: {
username: randomName(),
},
},
{}, {},
[], [],
undefined, undefined,

View file

@ -33,7 +33,6 @@ test('should report credentials not in any use', async () => {
name: 'My Slack Credential', name: 'My Slack Credential',
data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=', data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=',
type: 'slackApi', type: 'slackApi',
nodesAccess: [{ nodeType: 'n8n-nodes-base.slack', date: '2022-12-21T11:23:00.561Z' }],
}; };
const workflowDetails = { const workflowDetails = {
@ -79,7 +78,6 @@ test('should report credentials not in active use', async () => {
name: 'My Slack Credential', name: 'My Slack Credential',
data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=', data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=',
type: 'slackApi', type: 'slackApi',
nodesAccess: [{ nodeType: 'n8n-nodes-base.slack', date: '2022-12-21T11:23:00.561Z' }],
}; };
const credential = await Container.get(CredentialsRepository).save(credentialDetails); const credential = await Container.get(CredentialsRepository).save(credentialDetails);
@ -124,7 +122,6 @@ test('should report credential in not recently executed workflow', async () => {
name: 'My Slack Credential', name: 'My Slack Credential',
data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=', data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=',
type: 'slackApi', type: 'slackApi',
nodesAccess: [{ nodeType: 'n8n-nodes-base.slack', date: '2022-12-21T11:23:00.561Z' }],
}; };
const credential = await Container.get(CredentialsRepository).save(credentialDetails); const credential = await Container.get(CredentialsRepository).save(credentialDetails);
@ -192,7 +189,6 @@ test('should not report credentials in recently executed workflow', async () =>
name: 'My Slack Credential', name: 'My Slack Credential',
data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=', data: 'U2FsdGVkX18WjITBG4IDqrGB1xE/uzVNjtwDAG3lP7E=',
type: 'slackApi', type: 'slackApi',
nodesAccess: [{ nodeType: 'n8n-nodes-base.slack', date: '2022-12-21T11:23:00.561Z' }],
}; };
const credential = await Container.get(CredentialsRepository).save(credentialDetails); const credential = await Container.get(CredentialsRepository).save(credentialDetails);

View file

@ -21,7 +21,6 @@ const emptyAttributes = {
name: 'test', name: 'test',
type: 'test', type: 'test',
data: '', data: '',
nodesAccess: [],
}; };
export async function createManyCredentials( export async function createManyCredentials(

View file

@ -59,7 +59,6 @@ export const randomName = () => randomString(4, 8);
export const randomCredentialPayload = (): CredentialPayload => ({ export const randomCredentialPayload = (): CredentialPayload => ({
name: randomName(), name: randomName(),
type: randomName(), type: randomName(),
nodesAccess: [{ nodeType: randomName() }],
data: { accessToken: randomString(6, 16) }, data: { accessToken: randomString(6, 16) },
}); });

View file

@ -1,5 +1,5 @@
import type { Application } from 'express'; import type { Application } from 'express';
import type { ICredentialDataDecryptedObject, ICredentialNodeAccess } from 'n8n-workflow'; import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
import type { SuperAgentTest } from 'supertest'; import type { SuperAgentTest } from 'supertest';
import type { Server } from 'http'; import type { Server } from 'http';
@ -52,7 +52,6 @@ export interface TestServer {
export type CredentialPayload = { export type CredentialPayload = {
name: string; name: string;
type: string; type: string;
nodesAccess?: ICredentialNodeAccess[];
data: ICredentialDataDecryptedObject; data: ICredentialDataDecryptedObject;
}; };

View file

@ -244,7 +244,7 @@ describe('DELETE /users/:id', () => {
const savedWorkflow = await createWorkflow({ name: randomName() }, member); const savedWorkflow = await createWorkflow({ name: randomName() }, member);
const savedCredential = await saveCredential( const savedCredential = await saveCredential(
{ name: randomName(), type: '', data: {}, nodesAccess: [] }, { name: randomName(), type: '', data: {} },
{ user: member, role: 'credential:owner' }, { user: member, role: 'credential:owner' },
); );
@ -286,7 +286,7 @@ describe('DELETE /users/:id', () => {
const [savedWorkflow, savedCredential] = await Promise.all([ const [savedWorkflow, savedCredential] = await Promise.all([
await createWorkflow({ name: randomName() }, member), await createWorkflow({ name: randomName() }, member),
await saveCredential( await saveCredential(
{ name: randomName(), type: '', data: {}, nodesAccess: [] }, { name: randomName(), type: '', data: {} },
{ {
user: member, user: member,
role: 'credential:owner', role: 'credential:owner',

View file

@ -39,7 +39,6 @@ describe('OAuth1CredentialController', () => {
const credential = mock<CredentialsEntity>({ const credential = mock<CredentialsEntity>({
id: '1', id: '1',
name: 'Test Credential', name: 'Test Credential',
nodesAccess: [],
type: 'oAuth1Api', type: 'oAuth1Api',
}); });

View file

@ -43,7 +43,6 @@ describe('OAuth2CredentialController', () => {
const credential = mock<CredentialsEntity>({ const credential = mock<CredentialsEntity>({
id: '1', id: '1',
name: 'Test Credential', name: 'Test Credential',
nodesAccess: [],
type: 'oAuth2Api', type: 'oAuth2Api',
}); });

View file

@ -6,19 +6,6 @@ import { Cipher } from './Cipher';
export class Credentials extends ICredentials { export class Credentials extends ICredentials {
private readonly cipher = Container.get(Cipher); private readonly cipher = Container.get(Cipher);
/**
* Returns if the given nodeType has access to data
*/
hasNodeAccess(nodeType: string): boolean {
for (const accessData of this.nodesAccess) {
if (accessData.nodeType === nodeType) {
return true;
}
}
return false;
}
/** /**
* Sets new credential object * Sets new credential object
*/ */
@ -29,14 +16,7 @@ export class Credentials extends ICredentials {
/** /**
* Returns the decrypted credential object * Returns the decrypted credential object
*/ */
getData(nodeType?: string): ICredentialDataDecryptedObject { getData(): ICredentialDataDecryptedObject {
if (nodeType && !this.hasNodeAccess(nodeType)) {
throw new ApplicationError('Node does not have access to credential', {
tags: { nodeType, credentialType: this.type },
extra: { credentialName: this.name },
});
}
if (this.data === undefined) { if (this.data === undefined) {
throw new ApplicationError('No data is set so nothing can be returned.'); throw new ApplicationError('No data is set so nothing can be returned.');
} }
@ -65,7 +45,6 @@ export class Credentials extends ICredentials {
name: this.name, name: this.name,
type: this.type, type: this.type,
data: this.data, data: this.data,
nodesAccess: this.nodesAccess,
}; };
} }
} }

View file

@ -22,7 +22,7 @@ describe('Credentials', () => {
describe('without nodeType set', () => { describe('without nodeType set', () => {
test('should be able to set and read key data without initial data set', () => { test('should be able to set and read key data without initial data set', () => {
const credentials = new Credentials({ id: null, name: 'testName' }, 'testType', []); const credentials = new Credentials({ id: null, name: 'testName' }, 'testType');
const key = 'key1'; const key = 'key1';
const newData = 1234; const newData = 1234;
@ -42,7 +42,6 @@ describe('Credentials', () => {
const credentials = new Credentials( const credentials = new Credentials(
{ id: null, name: 'testName' }, { id: null, name: 'testName' },
'testType', 'testType',
[],
initialDataEncoded, initialDataEncoded,
); );
@ -56,46 +55,4 @@ describe('Credentials', () => {
expect(credentials.getData().key1).toEqual(initialData); expect(credentials.getData().key1).toEqual(initialData);
}); });
}); });
describe('with nodeType set', () => {
test('should be able to set and read key data without initial data set', () => {
const nodeAccess = [
{
nodeType: 'base.noOp',
user: 'userName',
date: new Date(),
},
];
const credentials = new Credentials({ id: null, name: 'testName' }, 'testType', nodeAccess);
const key = 'key1';
const nodeType = 'base.noOp';
const newData = 1234;
setDataKey(credentials, key, newData);
// Should be able to read with nodeType which has access
expect(credentials.getData(nodeType)[key]).toEqual(newData);
// Should not be able to read with nodeType which does NOT have access
// expect(credentials.getData('base.otherNode')[key]).toThrowError(Error);
try {
credentials.getData('base.otherNode');
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Node does not have access to credential');
}
// Get the data which will be saved in database
const dbData = credentials.getDataToSave();
expect(dbData.name).toEqual('testName');
expect(dbData.type).toEqual('testType');
expect(dbData.nodesAccess).toEqual(nodeAccess);
// Compare only the first 6 characters as the rest seems to change with each execution
expect(dbData.data!.slice(0, 6)).toEqual(
'U2FsdGVkX1+wpQWkj+YTzaPSNTFATjnlmFKIsUTZdhk='.slice(0, 6),
);
});
});
}); });

View file

@ -12,9 +12,6 @@ export const credentialFactory = Factory.extend<ICredentialsResponse>({
name() { name() {
return faker.company.name(); return faker.company.name();
}, },
nodesAccess() {
return [];
},
type() { type() {
return 'notionApi'; return 'notionApi';
}, },

View file

@ -68,7 +68,6 @@ export default defineComponent({
updatedAt: '', updatedAt: '',
type: '', type: '',
name: '', name: '',
nodesAccess: [],
sharedWith: [], sharedWith: [],
ownedBy: {} as IUser, ownedBy: {} as IUser,
}), }),

View file

@ -119,7 +119,6 @@ import type { ICredentialsResponse, IUser } from '@/Interface';
import type { import type {
CredentialInformation, CredentialInformation,
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialNodeAccess,
ICredentialsDecrypted, ICredentialsDecrypted,
ICredentialType, ICredentialType,
INode, INode,
@ -165,10 +164,6 @@ import { isValidCredentialResponse, isCredentialModalState } from '@/utils/typeG
import { isExpression, isTestableExpression } from '@/utils/expressions'; import { isExpression, isTestableExpression } from '@/utils/expressions';
import { useExternalHooks } from '@/composables/useExternalHooks'; import { useExternalHooks } from '@/composables/useExternalHooks';
interface NodeAccessMap {
[nodeType: string]: ICredentialNodeAccess | null;
}
export default defineComponent({ export default defineComponent({
name: 'CredentialEdit', name: 'CredentialEdit',
components: { components: {
@ -212,7 +207,6 @@ export default defineComponent({
credentialName: '', credentialName: '',
credentialData: {} as ICredentialDataDecryptedObject, credentialData: {} as ICredentialDataDecryptedObject,
modalBus: createEventBus(), modalBus: createEventBus(),
nodeAccess: {} as NodeAccessMap,
isDeleting: false, isDeleting: false,
isSaving: false, isSaving: false,
isTesting: false, isTesting: false,
@ -233,8 +227,6 @@ export default defineComponent({
isCredentialModalState(this.uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY]) && isCredentialModalState(this.uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY]) &&
this.uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true; this.uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true;
this.setupNodeAccess();
if (this.mode === 'new' && this.credentialTypeName) { if (this.mode === 'new' && this.credentialTypeName) {
this.credentialName = await this.credentialsStore.getNewCredentialName({ this.credentialName = await this.credentialsStore.getNewCredentialName({
credentialTypeName: this.defaultCredentialTypeName, credentialTypeName: this.defaultCredentialTypeName,
@ -765,7 +757,6 @@ export default defineComponent({
name: this.credentialName, name: this.credentialName,
type: this.credentialTypeName!, type: this.credentialTypeName!,
data: credentialData, data: credentialData,
nodesAccess: [],
}; };
this.isRetesting = true; this.isRetesting = true;
@ -817,7 +808,6 @@ export default defineComponent({
name: this.credentialName, name: this.credentialName,
type: this.credentialTypeName!, type: this.credentialTypeName!,
data: data as unknown as ICredentialDataDecryptedObject, data: data as unknown as ICredentialDataDecryptedObject,
nodesAccess: [],
sharedWith, sharedWith,
ownedBy, ownedBy,
}; };
@ -1091,7 +1081,6 @@ export default defineComponent({
if (credentialsForType) { if (credentialsForType) {
this.selectedCredential = credentialsForType.name; this.selectedCredential = credentialsForType.name;
this.resetCredentialData(); this.resetCredentialData();
this.setupNodeAccess();
// Update current node auth type so credentials dropdown can be displayed properly // Update current node auth type so credentials dropdown can be displayed properly
updateNodeAuthType(this.ndvStore.activeNode, type); updateNodeAuthType(this.ndvStore.activeNode, type);
// Also update credential name but only if the default name is still used // Also update credential name but only if the default name is still used
@ -1103,9 +1092,6 @@ export default defineComponent({
} }
} }
}, },
setupNodeAccess(): void {
this.nodeAccess = {};
},
resetCredentialData(): void { resetCredentialData(): void {
if (!this.credentialType) { if (!this.credentialType) {
return; return;

View file

@ -51,7 +51,6 @@ export const newCredential = (
updatedAt: faker.date.past().toISOString(), updatedAt: faker.date.past().toISOString(),
id: faker.string.alphanumeric({ length: 16 }), id: faker.string.alphanumeric({ length: 16 }),
name: faker.commerce.productName(), name: faker.commerce.productName(),
nodesAccess: [],
...opts, ...opts,
}); });
@ -61,16 +60,6 @@ export const credentialsTelegram1: ICredentialsResponse = {
id: 'YaSKdvEcT1TSFrrr1', id: 'YaSKdvEcT1TSFrrr1',
name: 'Telegram account', name: 'Telegram account',
type: 'telegramApi', type: 'telegramApi',
nodesAccess: [
{
nodeType: 'n8n-nodes-base.telegram',
date: new Date('2023-11-23T14:26:07.962Z'),
},
{
nodeType: 'n8n-nodes-base.telegramTrigger',
date: new Date('2023-11-23T14:26:07.962Z'),
},
],
ownedBy: { ownedBy: {
id: '713ef3e7-9e65-4b0a-893c-8a653cbb2c4f', id: '713ef3e7-9e65-4b0a-893c-8a653cbb2c4f',
email: 'user@n8n.io', email: 'user@n8n.io',
@ -86,16 +75,6 @@ export const credentialsTelegram2: ICredentialsResponse = {
id: 'YaSKdvEcT1TSFrrr2', id: 'YaSKdvEcT1TSFrrr2',
name: 'Telegram account', name: 'Telegram account',
type: 'telegramApi', type: 'telegramApi',
nodesAccess: [
{
nodeType: 'n8n-nodes-base.telegram',
date: new Date('2023-11-23T14:26:07.962Z'),
},
{
nodeType: 'n8n-nodes-base.telegramTrigger',
date: new Date('2023-11-23T14:26:07.962Z'),
},
],
ownedBy: { ownedBy: {
id: '713ef3e7-9e65-4b0a-893c-8a653cbb2c4f', id: '713ef3e7-9e65-4b0a-893c-8a653cbb2c4f',
email: 'user@n8n.io', email: 'user@n8n.io',

View file

@ -93,18 +93,10 @@ export abstract class ICredentials {
data: string | undefined; data: string | undefined;
nodesAccess: ICredentialNodeAccess[]; constructor(nodeCredentials: INodeCredentialsDetails, type: string, data?: string) {
constructor(
nodeCredentials: INodeCredentialsDetails,
type: string,
nodesAccess: ICredentialNodeAccess[],
data?: string,
) {
this.id = nodeCredentials.id ?? undefined; this.id = nodeCredentials.id ?? undefined;
this.name = nodeCredentials.name; this.name = nodeCredentials.name;
this.type = type; this.type = type;
this.nodesAccess = nodesAccess;
this.data = data; this.data = data;
} }
@ -112,8 +104,6 @@ export abstract class ICredentials {
abstract getDataToSave(): ICredentialsEncrypted; abstract getDataToSave(): ICredentialsEncrypted;
abstract hasNodeAccess(nodeType: string): boolean;
abstract setData(data: ICredentialDataDecryptedObject): void; abstract setData(data: ICredentialDataDecryptedObject): void;
} }
@ -124,19 +114,10 @@ export interface IUser {
lastName: string; lastName: string;
} }
// Defines which nodes are allowed to access the credentials and
// when that access got granted from which user
export interface ICredentialNodeAccess {
nodeType: string;
user?: string;
date?: Date;
}
export interface ICredentialsDecrypted { export interface ICredentialsDecrypted {
id: string; id: string;
name: string; name: string;
type: string; type: string;
nodesAccess: ICredentialNodeAccess[];
data?: ICredentialDataDecryptedObject; data?: ICredentialDataDecryptedObject;
ownedBy?: IUser; ownedBy?: IUser;
sharedWith?: IUser[]; sharedWith?: IUser[];
@ -146,7 +127,6 @@ export interface ICredentialsEncrypted {
id?: string; id?: string;
name: string; name: string;
type: string; type: string;
nodesAccess: ICredentialNodeAccess[];
data?: string; data?: string;
} }
@ -345,7 +325,6 @@ export interface ICredentialData {
id?: string; id?: string;
name: string; name: string;
data: string; // Contains the access data as encrypted JSON string data: string; // Contains the access data as encrypted JSON string
nodesAccess: ICredentialNodeAccess[];
} }
// The encrypted credentials which the nodes can access // The encrypted credentials which the nodes can access

View file

@ -46,10 +46,6 @@ export interface INodeTypesObject {
} }
export class Credentials extends ICredentials { export class Credentials extends ICredentials {
hasNodeAccess() {
return true;
}
setData(data: ICredentialDataDecryptedObject) { setData(data: ICredentialDataDecryptedObject) {
this.data = JSON.stringify(data); this.data = JSON.stringify(data);
} }
@ -71,7 +67,6 @@ export class Credentials extends ICredentials {
name: this.name, name: this.name,
type: this.type, type: this.type,
data: this.data, data: this.data,
nodesAccess: this.nodesAccess,
}; };
} }
} }