feat(core): Allow transferring credentials from any project to any team project (#9563)

This commit is contained in:
Danny Martini
2024-06-04 13:54:48 +02:00
committed by GitHub
parent 245c63f216
commit 202c91e7ed
8 changed files with 371 additions and 19 deletions

View File

@@ -28,6 +28,7 @@ import { SharedCredentialsRepository } from '@/databases/repositories/sharedCred
import { In } from '@n8n/typeorm';
import { SharedCredentials } from '@/databases/entities/SharedCredentials';
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
import { z } from 'zod';
@RestController('/credentials')
export class CredentialsController {
@@ -324,4 +325,16 @@ export class CredentialsController {
credentialsName: credential.name,
});
}
@Put('/:credentialId/transfer')
@ProjectScope('credential:move')
async transfer(req: CredentialRequest.Transfer) {
const body = z.object({ destinationProjectId: z.string() }).parse(req.body);
return await this.enterpriseCredentialsService.transferOne(
req.user,
req.params.credentialId,
body.destinationProjectId,
);
}
}

View File

@@ -8,6 +8,9 @@ import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { OwnershipService } from '@/services/ownership.service';
import { Project } from '@/databases/entities/Project';
import { ProjectService } from '@/services/project.service';
import { TransferCredentialError } from '@/errors/response-errors/transfer-credential.error';
import { SharedCredentials } from '@/databases/entities/SharedCredentials';
@Service()
export class EnterpriseCredentialsService {
@@ -15,6 +18,7 @@ export class EnterpriseCredentialsService {
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
private readonly ownershipService: OwnershipService,
private readonly credentialsService: CredentialsService,
private readonly projectService: ProjectService,
) {}
async shareWithProjects(
@@ -91,4 +95,68 @@ export class EnterpriseCredentialsService {
return { ...rest };
}
async transferOne(user: User, credentialId: string, destinationProjectId: string) {
// 1. get credential
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
credentialId,
user,
['credential:move'],
);
NotFoundError.isDefinedAndNotNull(
credential,
`Could not find the credential with the id "${credentialId}". Make sure you have the permission to move it.`,
);
// 2. get owner-sharing
const ownerSharing = credential.shared.find((s) => s.role === 'credential:owner');
NotFoundError.isDefinedAndNotNull(
ownerSharing,
`Could not find owner for credential "${credential.id}"`,
);
// 3. get source project
const sourceProject = ownerSharing.project;
// 4. get destination project
const destinationProject = await this.projectService.getProjectWithScope(
user,
destinationProjectId,
['credential:create'],
);
NotFoundError.isDefinedAndNotNull(
destinationProject,
`Could not find project with the id "${destinationProjectId}". Make sure you have the permission to create credentials in it.`,
);
// 5. checks
if (sourceProject.id === destinationProject.id) {
throw new TransferCredentialError(
"You can't transfer a credential into the project that's already owning it.",
);
}
if (sourceProject.type !== 'team' && sourceProject.type !== 'personal') {
throw new TransferCredentialError(
'You can only transfer credentials out of personal or team projects.',
);
}
if (destinationProject.type !== 'team') {
throw new TransferCredentialError('You can only transfer credentials into team projects.');
}
await this.sharedCredentialsRepository.manager.transaction(async (trx) => {
// 6. transfer the credential
// remove all sharings
await trx.remove(credential.shared);
// create new owner-sharing
await trx.save(
trx.create(SharedCredentials, {
credentialsId: credential.id,
projectId: destinationProject.id,
role: 'credential:owner',
}),
);
});
}
}