feat(core): Allow transferring credentials from any project to any team project (#9563)
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user