mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-12 15:44:06 -08:00
feat(core): Allow transferring credentials in Public API (#10259)
This commit is contained in:
parent
489ce10063
commit
07d7b247f0
|
@ -142,6 +142,12 @@ export declare namespace CredentialRequest {
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type Delete = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>;
|
type Delete = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>;
|
||||||
|
|
||||||
|
type Transfer = AuthenticatedRequest<
|
||||||
|
{ workflowId: string },
|
||||||
|
{},
|
||||||
|
{ destinationProjectId: string }
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OperationID = 'getUsers' | 'getUser';
|
export type OperationID = 'getUsers' | 'getUser';
|
||||||
|
|
|
@ -19,6 +19,8 @@ import {
|
||||||
toJsonSchema,
|
toJsonSchema,
|
||||||
} from './credentials.service';
|
} from './credentials.service';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { EnterpriseCredentialsService } from '@/credentials/credentials.service.ee';
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
createCredential: [
|
createCredential: [
|
||||||
|
@ -44,6 +46,20 @@ export = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
transferCredential: [
|
||||||
|
projectScope('credential:move', 'credential'),
|
||||||
|
async (req: CredentialRequest.Transfer, res: express.Response) => {
|
||||||
|
const body = z.object({ destinationProjectId: z.string() }).parse(req.body);
|
||||||
|
|
||||||
|
await Container.get(EnterpriseCredentialsService).transferOne(
|
||||||
|
req.user,
|
||||||
|
req.params.workflowId,
|
||||||
|
body.destinationProjectId,
|
||||||
|
);
|
||||||
|
|
||||||
|
res.status(204).send();
|
||||||
|
},
|
||||||
|
],
|
||||||
deleteCredential: [
|
deleteCredential: [
|
||||||
projectScope('credential:delete', 'credential'),
|
projectScope('credential:delete', 'credential'),
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
put:
|
||||||
|
x-eov-operation-id: transferCredential
|
||||||
|
x-eov-operation-handler: v1/handlers/credentials/credentials.handler
|
||||||
|
tags:
|
||||||
|
- Workflow
|
||||||
|
summary: Transfer a credential to another project.
|
||||||
|
description: Transfer a credential to another project.
|
||||||
|
parameters:
|
||||||
|
- $ref: '../schemas/parameters/credentialId.yml'
|
||||||
|
requestBody:
|
||||||
|
description: Destination project for the credential transfer.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
destinationProjectId:
|
||||||
|
type: string
|
||||||
|
description: The ID of the project to transfer the credential to.
|
||||||
|
required:
|
||||||
|
- destinationProjectId
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Operation successful.
|
||||||
|
'400':
|
||||||
|
$ref: '../../../../shared/spec/responses/badRequest.yml'
|
||||||
|
'401':
|
||||||
|
$ref: '../../../../shared/spec/responses/unauthorized.yml'
|
||||||
|
'404':
|
||||||
|
$ref: '../../../../shared/spec/responses/notFound.yml'
|
|
@ -0,0 +1,6 @@
|
||||||
|
name: id
|
||||||
|
in: path
|
||||||
|
description: The ID of the credential.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
|
@ -62,6 +62,8 @@ paths:
|
||||||
$ref: './handlers/workflows/spec/paths/workflows.id.deactivate.yml'
|
$ref: './handlers/workflows/spec/paths/workflows.id.deactivate.yml'
|
||||||
/workflows/{id}/transfer:
|
/workflows/{id}/transfer:
|
||||||
$ref: './handlers/workflows/spec/paths/workflows.id.transfer.yml'
|
$ref: './handlers/workflows/spec/paths/workflows.id.transfer.yml'
|
||||||
|
/credentials/{id}/transfer:
|
||||||
|
$ref: './handlers/credentials/spec/paths/credentials.id.transfer.yml'
|
||||||
/workflows/{id}/tags:
|
/workflows/{id}/tags:
|
||||||
$ref: './handlers/workflows/spec/paths/workflows.id.tags.yml'
|
$ref: './handlers/workflows/spec/paths/workflows.id.tags.yml'
|
||||||
/users:
|
/users:
|
||||||
|
|
|
@ -9,9 +9,10 @@ import { randomApiKey, randomName } from '../shared/random';
|
||||||
import * as utils from '../shared/utils/';
|
import * as utils from '../shared/utils/';
|
||||||
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
import { affixRoleToSaveCredential } from '../shared/db/credentials';
|
import { affixRoleToSaveCredential, createCredentials } from '../shared/db/credentials';
|
||||||
import { addApiKey, createUser, createUserShell } from '../shared/db/users';
|
import { addApiKey, createUser, createUserShell } from '../shared/db/users';
|
||||||
import type { SuperAgentTest } from '../shared/types';
|
import type { SuperAgentTest } from '../shared/types';
|
||||||
|
import { createTeamProject } from '@test-integration/db/projects';
|
||||||
|
|
||||||
let owner: User;
|
let owner: User;
|
||||||
let member: User;
|
let member: User;
|
||||||
|
@ -256,6 +257,53 @@ describe('GET /credentials/schema/:credentialType', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PUT /credentials/:id/transfer', () => {
|
||||||
|
test('should transfer credential to project', async () => {
|
||||||
|
/**
|
||||||
|
* Arrange
|
||||||
|
*/
|
||||||
|
const [firstProject, secondProject] = await Promise.all([
|
||||||
|
createTeamProject('first-project', owner),
|
||||||
|
createTeamProject('second-project', owner),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const credentials = await createCredentials(
|
||||||
|
{ name: 'Test', type: 'test', data: '' },
|
||||||
|
firstProject,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Act
|
||||||
|
*/
|
||||||
|
const response = await authOwnerAgent.put(`/credentials/${credentials.id}/transfer`).send({
|
||||||
|
destinationProjectId: secondProject.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert
|
||||||
|
*/
|
||||||
|
expect(response.statusCode).toBe(204);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('if no destination project, should reject', async () => {
|
||||||
|
/**
|
||||||
|
* Arrange
|
||||||
|
*/
|
||||||
|
const project = await createTeamProject('first-project', member);
|
||||||
|
const credentials = await createCredentials({ name: 'Test', type: 'test', data: '' }, project);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Act
|
||||||
|
*/
|
||||||
|
const response = await authOwnerAgent.put(`/credentials/${credentials.id}/transfer`).send({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert
|
||||||
|
*/
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const credentialPayload = (): CredentialPayload => ({
|
const credentialPayload = (): CredentialPayload => ({
|
||||||
name: randomName(),
|
name: randomName(),
|
||||||
type: 'githubApi',
|
type: 'githubApi',
|
||||||
|
|
|
@ -1493,7 +1493,7 @@ describe('PUT /workflows/:id/transfer', () => {
|
||||||
* Arrange
|
* Arrange
|
||||||
*/
|
*/
|
||||||
const firstProject = await createTeamProject('first-project', member);
|
const firstProject = await createTeamProject('first-project', member);
|
||||||
const secondProject = await createTeamProject('secon-project', member);
|
const secondProject = await createTeamProject('second-project', member);
|
||||||
const workflow = await createWorkflow({}, firstProject);
|
const workflow = await createWorkflow({}, firstProject);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,11 +38,24 @@ export async function createManyCredentials(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCredentials(attributes: Partial<CredentialsEntity> = emptyAttributes) {
|
export async function createCredentials(
|
||||||
|
attributes: Partial<CredentialsEntity> = emptyAttributes,
|
||||||
|
project?: Project,
|
||||||
|
) {
|
||||||
const credentialsRepository = Container.get(CredentialsRepository);
|
const credentialsRepository = Container.get(CredentialsRepository);
|
||||||
const entity = credentialsRepository.create(attributes);
|
const credentials = await credentialsRepository.save(credentialsRepository.create(attributes));
|
||||||
|
|
||||||
return await credentialsRepository.save(entity);
|
if (project) {
|
||||||
|
await Container.get(SharedCredentialsRepository).save(
|
||||||
|
Container.get(SharedCredentialsRepository).create({
|
||||||
|
project,
|
||||||
|
credentials,
|
||||||
|
role: 'credential:owner',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue