mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
filter by credential ids in a better way
This commit is contained in:
parent
ee1b50de3e
commit
fd9435c3f1
|
@ -91,6 +91,9 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl
|
|||
display() {
|
||||
return `"${this.name}" (ID: ${this.id})`;
|
||||
}
|
||||
|
||||
@Column({ type: 'simple-array', default: '' })
|
||||
credentialIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import type { MigrationContext, ReversibleMigration } from '@/databases/types';
|
||||
|
||||
export class AddWorkflowFilterColumns1730286483664 implements ReversibleMigration {
|
||||
async up({ schemaBuilder: { addColumns, column } }: MigrationContext) {
|
||||
console.log('foo');
|
||||
await addColumns('workflow_entity', [column('credentialIds').text]);
|
||||
}
|
||||
|
||||
async down({ schemaBuilder: { dropColumns } }: MigrationContext) {
|
||||
await dropColumns('workflow_entity', ['credentialIds']);
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ import { CreateAnnotationTables1724753530828 } from '../common/1724753530828-Cre
|
|||
import { CreateProcessedDataTable1726606152711 } from '../common/1726606152711-CreateProcessedDataTable';
|
||||
import { SeparateExecutionCreationFromStart1727427440136 } from '../common/1727427440136-SeparateExecutionCreationFromStart';
|
||||
import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText';
|
||||
import { AddWorkflowFilterColumns1730286483664 } from '../common/1730286483664-AddWorkflowFilterColumns';
|
||||
|
||||
const sqliteMigrations: Migration[] = [
|
||||
InitialMigration1588102412422,
|
||||
|
@ -132,6 +133,7 @@ const sqliteMigrations: Migration[] = [
|
|||
CreateProcessedDataTable1726606152711,
|
||||
AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644,
|
||||
UpdateProcessedDataValueColumnToText1729607673464,
|
||||
AddWorkflowFilterColumns1730286483664,
|
||||
];
|
||||
|
||||
export { sqliteMigrations };
|
||||
|
|
|
@ -13,11 +13,10 @@ import {
|
|||
} from '@n8n/typeorm';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
import * as a from 'assert/strict';
|
||||
|
||||
import config from '@/config';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { isStringArray } from '@/utils';
|
||||
import { toBase64 } from '@/workflows/utils';
|
||||
|
||||
import { WebhookEntity } from '../entities/webhook-entity';
|
||||
import { WorkflowEntity } from '../entities/workflow-entity';
|
||||
|
@ -155,11 +154,11 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||
}
|
||||
|
||||
if (credentialIds.length) {
|
||||
where.nodes = Or(...credentialIds.map((id) => Like(`%{"id":"${id}"%`)));
|
||||
where.credentialIds = Or(...credentialIds.map((id) => Like(`%${toBase64(id)}%`)));
|
||||
}
|
||||
|
||||
const findManyOptions: FindManyOptions<WorkflowEntity> = {
|
||||
select: { ...select, id: true, nodes: true },
|
||||
select: { ...select, id: true },
|
||||
where,
|
||||
};
|
||||
|
||||
|
@ -176,43 +175,11 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||
findManyOptions.take = options.take;
|
||||
}
|
||||
|
||||
let [workflows, count] = (await this.findAndCount(findManyOptions)) as [
|
||||
const [workflows, count] = (await this.findAndCount(findManyOptions)) as [
|
||||
ListQuery.Workflow.Plain[] | ListQuery.Workflow.WithSharing[],
|
||||
number,
|
||||
];
|
||||
|
||||
function workflowUsesCredential(
|
||||
workflow: ListQuery.Workflow.Plain,
|
||||
credentialIds: string[],
|
||||
): boolean {
|
||||
a.ok(workflow.nodes);
|
||||
|
||||
return (
|
||||
workflow.nodes.findIndex((node) => {
|
||||
if (node.credentials) {
|
||||
return (
|
||||
Object.values(node.credentials).findIndex((credential) => {
|
||||
a.ok(credential.id);
|
||||
return credentialIds.includes(credential.id);
|
||||
}) !== -1
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
if (credentialIds.length) {
|
||||
workflows = workflows.filter((wf) => workflowUsesCredential(wf, credentialIds));
|
||||
|
||||
count = workflows.length;
|
||||
}
|
||||
|
||||
for (const wf of workflows) {
|
||||
delete wf.nodes;
|
||||
}
|
||||
|
||||
return { workflows, count };
|
||||
}
|
||||
|
||||
|
|
|
@ -81,14 +81,7 @@ export namespace ListQuery {
|
|||
* Slim workflow returned from a list query operation.
|
||||
*/
|
||||
export namespace Workflow {
|
||||
type OptionalBaseFields =
|
||||
| 'name'
|
||||
| 'active'
|
||||
| 'versionId'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'tags'
|
||||
| 'nodes';
|
||||
type OptionalBaseFields = 'name' | 'active' | 'versionId' | 'createdAt' | 'updatedAt' | 'tags';
|
||||
|
||||
type BaseFields = Pick<WorkflowEntity, 'id'> &
|
||||
Partial<Pick<WorkflowEntity, OptionalBaseFields>>;
|
||||
|
|
23
packages/cli/src/workflows/utils.ts
Normal file
23
packages/cli/src/workflows/utils.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
||||
|
||||
export function toBase64(id: string): string {
|
||||
return Buffer.from(id).toString('base64');
|
||||
}
|
||||
|
||||
export function fromBase64(encoded: string): string {
|
||||
return Buffer.from(encoded, 'base64').toString();
|
||||
}
|
||||
|
||||
export function getEncodedCredentialIds(workflow: WorkflowEntity): string[] {
|
||||
const credentialIds: string[] = [];
|
||||
|
||||
for (const nodes of workflow.nodes) {
|
||||
for (const credential of Object.values(nodes.credentials ?? {})) {
|
||||
if (credential.id) {
|
||||
credentialIds.push(toBase64(credential.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return credentialIds;
|
||||
}
|
|
@ -33,6 +33,7 @@ import { RoleService } from '@/services/role.service';
|
|||
import { TagService } from '@/services/tag.service';
|
||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||
|
||||
import { getEncodedCredentialIds } from './utils';
|
||||
import { WorkflowHistoryService } from './workflow-history/workflow-history.service.ee';
|
||||
import { WorkflowSharingService } from './workflow-sharing.service';
|
||||
|
||||
|
@ -84,9 +85,10 @@ export class WorkflowService {
|
|||
}
|
||||
|
||||
workflows.forEach((w) => {
|
||||
// This is to emulate the old behavior of removing the shared field as
|
||||
// part of `addOwnedByAndSharedWith`. We need this field in `addScopes`
|
||||
// though. So to avoid leaking the information we just delete it.
|
||||
// @ts-expect-error: This is to emulate the old behavior of removing the
|
||||
// shared field as part of `addOwnedByAndSharedWith`. We need this field
|
||||
// in `addScopes` though. So to avoid leaking the information we just
|
||||
// delete it.
|
||||
delete w.shared;
|
||||
});
|
||||
|
||||
|
@ -183,9 +185,8 @@ export class WorkflowService {
|
|||
await validateEntity(workflowUpdateData);
|
||||
}
|
||||
|
||||
await this.workflowRepository.update(
|
||||
workflowId,
|
||||
pick(workflowUpdateData, [
|
||||
await this.workflowRepository.update(workflowId, {
|
||||
...pick(workflowUpdateData, [
|
||||
'name',
|
||||
'active',
|
||||
'nodes',
|
||||
|
@ -196,7 +197,8 @@ export class WorkflowService {
|
|||
'pinData',
|
||||
'versionId',
|
||||
]),
|
||||
);
|
||||
credentialIds: getEncodedCredentialIds(workflow),
|
||||
});
|
||||
|
||||
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
||||
await this.workflowTagMappingRepository.overwriteTaggings(workflowId, tagIds);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GlobalConfig } from '@n8n/config';
|
|||
import { In, type FindOptionsRelations } from '@n8n/typeorm';
|
||||
import axios from 'axios';
|
||||
import express from 'express';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
import { ApplicationError, createEnvProvider } from 'n8n-workflow';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
@ -44,6 +44,7 @@ import { WorkflowRequest } from './workflow.request';
|
|||
import { WorkflowService } from './workflow.service';
|
||||
import { EnterpriseWorkflowService } from './workflow.service.ee';
|
||||
import { CredentialsService } from '../credentials/credentials.service';
|
||||
import { getEncodedCredentialIds } from './utils';
|
||||
|
||||
@RestController('/workflows')
|
||||
export class WorkflowsController {
|
||||
|
@ -115,6 +116,8 @@ export class WorkflowsController {
|
|||
}
|
||||
}
|
||||
|
||||
newWorkflow.credentialIds = getEncodedCredentialIds(newWorkflow);
|
||||
|
||||
let project: Project | null;
|
||||
const savedWorkflow = await Db.transaction(async (transactionManager) => {
|
||||
const workflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||
|
|
|
@ -13,6 +13,7 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository
|
|||
import { License } from '@/license';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { ProjectService } from '@/services/project.service';
|
||||
import { toBase64 } from '@/workflows/utils';
|
||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
|
@ -488,7 +489,10 @@ describe('GET /workflows', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const workflow1 = await createWorkflow({ name: 'First', nodes: [node1] }, owner);
|
||||
const workflow1 = await createWorkflow(
|
||||
{ name: 'First', nodes: [node1], credentialIds: [toBase64(credential1.id)] },
|
||||
owner,
|
||||
);
|
||||
|
||||
const credential2 = await saveCredential(randomCredentialPayload(), {
|
||||
user: owner,
|
||||
|
|
Loading…
Reference in a new issue