n8n
Version:
n8n Workflow Automation Tool
783 lines • 37.7 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CredentialsService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const permissions_1 = require("@n8n/permissions");
const typeorm_1 = require("@n8n/typeorm");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const credential_types_1 = require("../credential-types");
const credentials_helper_1 = require("../credentials-helper");
const bad_request_error_1 = require("../errors/response-errors/bad-request.error");
const credential_not_found_error_1 = require("../errors/credential-not-found.error");
const forbidden_error_1 = require("../errors/response-errors/forbidden.error");
const not_found_error_1 = require("../errors/response-errors/not-found.error");
const external_hooks_1 = require("../external-hooks");
const generic_helpers_1 = require("../generic-helpers");
const external_secrets_config_1 = require("../modules/external-secrets.ee/external-secrets.config");
const secret_provider_access_check_service_ee_1 = require("../modules/external-secrets.ee/secret-provider-access-check.service.ee");
const validate_oauth_url_1 = require("../oauth/validate-oauth-url");
const check_access_1 = require("../permissions.ee/check-access");
const credentials_tester_service_1 = require("../services/credentials-tester.service");
const ownership_service_1 = require("../services/ownership.service");
const project_service_ee_1 = require("../services/project.service.ee");
const role_service_1 = require("../services/role.service");
const credential_dependency_service_1 = require("./credential-dependency.service");
const credentials_finder_service_1 = require("./credentials-finder.service");
const validation_1 = require("./validation");
const CUSTOM_AUTH_JSON_REDACTED_VALUE = '***';
let CredentialsService = class CredentialsService {
constructor(credentialsRepository, credentialDependencyService, sharedCredentialsRepository, ownershipService, logger, errorReporter, credentialsTester, externalHooks, credentialTypes, projectRepository, projectService, roleService, userRepository, credentialsFinderService, credentialsHelper, externalSecretsConfig, externalSecretsProviderAccessCheckService) {
this.credentialsRepository = credentialsRepository;
this.credentialDependencyService = credentialDependencyService;
this.sharedCredentialsRepository = sharedCredentialsRepository;
this.ownershipService = ownershipService;
this.logger = logger;
this.errorReporter = errorReporter;
this.credentialsTester = credentialsTester;
this.externalHooks = externalHooks;
this.credentialTypes = credentialTypes;
this.projectRepository = projectRepository;
this.projectService = projectService;
this.roleService = roleService;
this.userRepository = userRepository;
this.credentialsFinderService = credentialsFinderService;
this.credentialsHelper = credentialsHelper;
this.externalSecretsConfig = externalSecretsConfig;
this.externalSecretsProviderAccessCheckService = externalSecretsProviderAccessCheckService;
}
async addGlobalCredentials(credentials, includeData, dependencyFilter, type) {
const globalCredentials = await this.credentialsRepository.findAllGlobalCredentials({
includeData,
...(type ? { type } : {}),
filters: { dependency: dependencyFilter },
});
const credentialIds = new Set(credentials.map((c) => c.id));
const newGlobalCreds = globalCredentials.filter((gc) => !credentialIds.has(gc.id));
return [...credentials, ...newGlobalCreds];
}
extractTypeFilter(listQueryOptions) {
const filterType = listQueryOptions.filter?.type;
return typeof filterType === 'string' && filterType !== '' ? filterType : undefined;
}
async getMany(user, { listQueryOptions = {}, includeScopes = false, includeData = false, onlySharedWithMe = false, includeGlobal = false, filters = {}, } = {}) {
const { externalSecretsStore } = filters;
const returnAll = (0, permissions_1.hasGlobalScope)(user, 'credential:list');
const isDefaultSelect = !listQueryOptions.select;
const dependencyFilter = externalSecretsStore
? await this.credentialDependencyService.resolveExternalSecretsStoreDependencyFilter(externalSecretsStore)
: undefined;
if (externalSecretsStore && !dependencyFilter) {
return [];
}
if (includeData) {
includeScopes = true;
}
let credentials;
if (returnAll) {
credentials = await this.getManyForAdminUser(user, {
listQueryOptions,
includeGlobal,
includeData,
onlySharedWithMe,
filters: { dependency: dependencyFilter },
});
}
else {
credentials = await this.getManyForMemberUser(user, {
listQueryOptions,
includeGlobal,
includeData,
onlySharedWithMe,
filters: { dependency: dependencyFilter },
});
}
return await this.enrichCredentials(credentials, user, isDefaultSelect, includeScopes, includeData, listQueryOptions, onlySharedWithMe);
}
async getManyForAdminUser(user, { listQueryOptions, includeGlobal, includeData, onlySharedWithMe, filters, }) {
const { dependency: dependencyFilter } = filters ?? {};
const typeFilter = this.extractTypeFilter(listQueryOptions);
if (onlySharedWithMe || dependencyFilter) {
const sharingOptions = {
...(onlySharedWithMe ? { onlySharedWithMe: true } : {}),
};
const { credentials } = await this.credentialsRepository.getManyAndCountWithSharingSubquery(user, sharingOptions, {
...listQueryOptions,
...(includeData ? { includeData: true } : {}),
filters: {
dependency: dependencyFilter,
},
});
if (includeGlobal) {
return await this.addGlobalCredentials(credentials, includeData, dependencyFilter, typeFilter);
}
return credentials;
}
await this.applyPersonalProjectFilter(listQueryOptions);
let credentials = await this.credentialsRepository.findMany({
...listQueryOptions,
...(includeData ? { includeData: true } : {}),
});
if (includeGlobal) {
credentials = await this.addGlobalCredentials(credentials, includeData, dependencyFilter, typeFilter);
}
return credentials;
}
async getManyForMemberUser(user, { listQueryOptions, includeGlobal, includeData, onlySharedWithMe, filters, }) {
const { dependency: dependencyFilter } = filters ?? {};
const typeFilter = this.extractTypeFilter(listQueryOptions);
let isPersonalProject = false;
let personalProjectOwnerId = null;
if (listQueryOptions.filter?.projectId) {
const project = await this.projectRepository.findOneBy({
id: listQueryOptions.filter.projectId,
});
if (!project) {
return [];
}
isPersonalProject = project.type === 'personal';
personalProjectOwnerId = project.creatorId;
}
const sharingOptions = {};
if (isPersonalProject && personalProjectOwnerId) {
if (personalProjectOwnerId !== user.id && !(0, permissions_1.hasGlobalScope)(user, 'credential:read')) {
return [];
}
sharingOptions.isPersonalProject = true;
sharingOptions.personalProjectOwnerId = personalProjectOwnerId;
}
else if (onlySharedWithMe) {
sharingOptions.onlySharedWithMe = true;
}
else {
const projectRoles = await this.roleService.rolesWithScope('project', ['credential:read']);
const credentialRoles = await this.roleService.rolesWithScope('credential', [
'credential:read',
]);
sharingOptions.scopes = ['credential:read'];
sharingOptions.projectRoles = projectRoles;
sharingOptions.credentialRoles = credentialRoles;
}
const { credentials } = await this.credentialsRepository.getManyAndCountWithSharingSubquery(user, sharingOptions, {
...listQueryOptions,
...(includeData ? { includeData: true } : {}),
filters: {
dependency: dependencyFilter,
},
});
if (includeGlobal) {
return await this.addGlobalCredentials(credentials, includeData, dependencyFilter, typeFilter);
}
return credentials;
}
async applyPersonalProjectFilter(listQueryOptions) {
const projectId = typeof listQueryOptions.filter?.projectId === 'string'
? listQueryOptions.filter.projectId
: undefined;
if (!projectId) {
return;
}
let project;
try {
project = await this.projectService.getProject(projectId);
}
catch { }
if (project?.type === 'personal') {
listQueryOptions.filter = {
...listQueryOptions.filter,
withRole: 'credential:owner',
};
}
}
async enrichCredentials(credentials, user, isDefaultSelect, includeScopes, includeData, listQueryOptions, onlySharedWithMe) {
if (isDefaultSelect) {
credentials = await this.populateSharedRelations(credentials, listQueryOptions, onlySharedWithMe);
}
if (includeScopes) {
credentials = await this.addScopesToCredentials(credentials, user);
}
if (includeData) {
return await this.addDecryptedDataToCredentials(credentials);
}
return credentials;
}
async populateSharedRelations(credentials, listQueryOptions, onlySharedWithMe) {
const needsRelations = listQueryOptions.filter?.shared &&
typeof listQueryOptions.filter.shared === 'object' &&
'projectId' in listQueryOptions.filter.shared
? listQueryOptions.filter.shared.projectId
: onlySharedWithMe;
if (needsRelations) {
const relations = await this.sharedCredentialsRepository.getAllRelationsForCredentials(credentials.map((c) => c.id));
credentials.forEach((c) => {
c.shared = relations.filter((r) => r.credentialsId === c.id);
});
}
return credentials.map((c) => this.ownershipService.addOwnedByAndSharedWith(c));
}
async addScopesToCredentials(credentials, user) {
const projectRelations = await this.projectService.getProjectRelationsForUser(user);
return credentials.map((c) => this.roleService.addScopes(c, user, projectRelations));
}
async addDecryptedDataToCredentials(credentials) {
return await Promise.all(credentials.map(async (c) => {
const data = c.scopes.includes('credential:update') ? await this.decrypt(c) : undefined;
if (data?.oauthTokenData) {
data.oauthTokenData = true;
}
return {
...c,
data,
};
}));
}
async getCredentialsAUserCanUseInAWorkflow(user, options) {
const projectRelations = await this.projectService.getProjectRelationsForUser(user);
const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
'credential:read',
]);
const allCredentialsForWorkflow = 'workflowId' in options
? (await this.findAllCredentialIdsForWorkflow(options.workflowId)).map((c) => c.id)
: (await this.findAllCredentialIdsForProject(options.projectId)).map((c) => c.id);
const intersection = allCredentials.filter((c) => allCredentialsForWorkflow.includes(c.id) || c.isGlobal);
return intersection
.map((c) => this.roleService.addScopes(c, user, projectRelations))
.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
scopes: c.scopes,
isManaged: c.isManaged,
isGlobal: c.isGlobal,
isResolvable: c.isResolvable,
}));
}
async findAllGlobalCredentialIds(includeData = false) {
const globalCredentials = await this.credentialsRepository.findAllGlobalCredentials({
includeData,
});
return globalCredentials;
}
async findAllCredentialIdsForWorkflow(workflowId) {
const user = await this.userRepository.findPersonalOwnerForWorkflow(workflowId);
if (user && (0, permissions_1.hasGlobalScope)(user, 'credential:read')) {
return await this.credentialsRepository.findAllPersonalCredentials();
}
return await this.credentialsRepository.findAllCredentialsForWorkflow(workflowId);
}
async findAllCredentialIdsForProject(projectId) {
const user = await this.userRepository.findPersonalOwnerForProject(projectId);
if (user && (0, permissions_1.hasGlobalScope)(user, 'credential:read')) {
return await this.credentialsRepository.findAllPersonalCredentials();
}
return await this.credentialsRepository.findAllCredentialsForProject(projectId);
}
async getSharing(user, credentialId, globalScopes, relations = { credentials: true }) {
let where = { credentialsId: credentialId };
if (!(0, permissions_1.hasGlobalScope)(user, globalScopes, { mode: 'allOf' })) {
where = {
...where,
role: 'credential:owner',
project: {
projectRelations: {
role: { slug: permissions_1.PROJECT_OWNER_ROLE_SLUG },
userId: user.id,
},
},
};
}
return await this.sharedCredentialsRepository.findOne({
where,
relations,
});
}
async prepareUpdateData(user, data, existingCredential) {
const decryptedData = await this.decrypt(existingCredential, true);
const projectOwningCredential = existingCredential.shared?.find((shared) => shared.role === 'credential:owner');
await (0, validation_1.validateExternalSecretsPermissions)({
user,
projectId: projectOwningCredential.projectId,
dataToSave: data.data,
decryptedExistingData: decryptedData,
});
if (this.externalSecretsConfig.externalSecretsForProjects && data.data) {
await (0, validation_1.validateAccessToReferencedSecretProviders)(projectOwningCredential.projectId, data.data, this.externalSecretsProviderAccessCheckService, 'update');
}
const mergedData = (0, n8n_workflow_1.deepCopy)(data);
if (mergedData.data) {
mergedData.data = this.unredact(mergedData.data, decryptedData, this.getCredentialTypeProperties(existingCredential.type));
}
const updateData = this.credentialsRepository.create(mergedData);
await (0, generic_helpers_1.validateEntity)(updateData);
if (decryptedData.oauthTokenData) {
updateData.data.oauthTokenData = decryptedData.oauthTokenData;
}
this.validateOAuthCredentialUrls(updateData.type, updateData.data);
return updateData;
}
async createEncryptedData(credential) {
const credentials = new n8n_core_1.Credentials({ id: credential.id, name: credential.name }, credential.type);
await credentials.setData(credential.data);
const newCredentialData = credentials.getDataToSave();
newCredentialData.updatedAt = new Date();
return newCredentialData;
}
async decrypt(credential, includeRawData = false) {
const coreCredential = (0, credentials_helper_1.createCredentialsFromCredentialsEntity)(credential);
try {
const data = await coreCredential.getData();
if (includeRawData) {
return data;
}
return this.redact(data, credential);
}
catch (error) {
if (error instanceof n8n_core_1.CredentialDataError) {
this.errorReporter.error(error, {
level: 'error',
extra: { credentialId: credential.id },
tags: { credentialType: credential.type },
});
return {};
}
throw error;
}
}
async update(credentialId, newCredentialData, decryptedCredentialData) {
await this.externalHooks.run('credentials.update', [newCredentialData]);
return await this.credentialsRepository.manager.transaction(async (transactionManager) => {
await transactionManager.update(db_1.CredentialsEntity, credentialId, newCredentialData);
if (decryptedCredentialData) {
await this.credentialDependencyService.syncExternalSecretProviderDependenciesForCredential({
credentialId,
decryptedCredentialData,
entityManager: transactionManager,
});
}
return await transactionManager.findOneBy(db_1.CredentialsEntity, { id: credentialId });
});
}
async resolveOwningProjectIdForNewCredential(user, projectId, entityManager) {
if (projectId !== undefined) {
return projectId;
}
const personalProject = await this.projectRepository.getPersonalProjectForUserOrFail(user.id, entityManager);
return personalProject.id;
}
async save(credential, encryptedData, user, projectId, decryptedCredentialData) {
const newCredential = new db_1.CredentialsEntity();
Object.assign(newCredential, credential, encryptedData);
await this.externalHooks.run('credentials.create', [encryptedData]);
const { manager: dbManager } = this.credentialsRepository;
const result = await dbManager.transaction(async (transactionManager) => {
const project = await this.projectService.getProjectWithScope(user, projectId, ['credential:create'], transactionManager);
if (project === null) {
if (!(await transactionManager.existsBy(db_1.Project, { id: projectId }))) {
throw new not_found_error_1.NotFoundError('Project not found');
}
throw new forbidden_error_1.ForbiddenError("You don't have the permissions to save the credential in this project.");
}
const savedCredential = await transactionManager.save(newCredential);
savedCredential.data = newCredential.data;
const newSharedCredential = this.sharedCredentialsRepository.create({
role: 'credential:owner',
credentials: savedCredential,
projectId: project.id,
});
await transactionManager.save(newSharedCredential);
if (decryptedCredentialData) {
await this.credentialDependencyService.upsertExternalSecretProviderDependenciesForCredential({
credentialId: savedCredential.id,
decryptedCredentialData,
entityManager: transactionManager,
});
}
return savedCredential;
});
this.logger.debug('New credential created', {
credentialId: newCredential.id,
ownerId: user.id,
});
return result;
}
async delete(user, credentialId) {
await this.externalHooks.run('credentials.delete', [credentialId]);
const credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:delete']);
if (!credential) {
return;
}
await this.credentialsRepository.remove(credential);
}
async test(userId, credentials) {
return await this.credentialsTester.testCredentials(userId, credentials.type, credentials);
}
async testById(userId, credentialId) {
const storedCredential = await this.credentialsFinderService.findCredentialById(credentialId);
if (!storedCredential) {
throw new credential_not_found_error_1.CredentialNotFoundError(credentialId);
}
const credentials = await this.prepareCredentialsForTest({ storedCredential });
return await this.test(userId, credentials);
}
async testWithCredentials(user, credentials) {
const storedCredential = await this.credentialsFinderService.findCredentialForUser(credentials.id, user, ['credential:read']);
if (!storedCredential) {
throw new credential_not_found_error_1.CredentialNotFoundError(credentials.id);
}
const mergedCredentials = await this.prepareCredentialsForTest({
storedCredential,
user,
credentialsToTest: credentials,
});
return await this.test(user.id, mergedCredentials);
}
redact(data, credential) {
const copiedData = (0, n8n_workflow_1.deepCopy)(data);
let credType;
try {
credType = this.credentialTypes.getByName(credential.type);
}
catch {
return data;
}
const getExtendedProps = (type) => {
const props = [];
for (const e of type.extends ?? []) {
const extendsType = this.credentialTypes.getByName(e);
const extendedProps = getExtendedProps(extendsType);
n8n_workflow_1.NodeHelpers.mergeNodeProperties(props, extendedProps);
}
n8n_workflow_1.NodeHelpers.mergeNodeProperties(props, type.properties);
return props;
};
const properties = getExtendedProps(credType);
return this.redactValues(copiedData, properties);
}
redactValues(data, props) {
for (const dataKey of Object.keys(data)) {
if (dataKey === 'oauthTokenData' || dataKey === 'csrfSecret') {
if (data[dataKey].toString().length > 0) {
data[dataKey] = n8n_workflow_1.CREDENTIAL_BLANKING_VALUE;
}
else {
data[dataKey] = n8n_workflow_1.CREDENTIAL_EMPTY_VALUE;
}
continue;
}
const prop = props.find((v) => v.name === dataKey);
if (!prop) {
continue;
}
if (prop.type === 'fixedCollection' && prop.options?.length) {
const dataObject = data[dataKey];
for (const option of prop.options) {
if ((0, n8n_workflow_1.isINodePropertyCollection)(option)) {
this.redactCollectionOption(dataObject, option);
}
}
}
if (prop.typeOptions?.password &&
(!data[dataKey].toString().startsWith('={{') || prop.noDataExpression)) {
if (data[dataKey].toString().length > 0) {
data[dataKey] = n8n_workflow_1.CREDENTIAL_BLANKING_VALUE;
}
else {
data[dataKey] = n8n_workflow_1.CREDENTIAL_EMPTY_VALUE;
}
continue;
}
if (prop.typeOptions?.redactJsonLeaves) {
const jsonStr = String(data[dataKey] ?? '');
if (!jsonStr) {
data[dataKey] = n8n_workflow_1.CREDENTIAL_EMPTY_VALUE;
}
else {
try {
const parsed = (0, n8n_workflow_1.jsonParse)(jsonStr);
data[dataKey] = JSON.stringify(this.redactJsonLeaves(parsed), null, 2);
}
catch {
}
}
}
}
return data;
}
redactCollectionOption(data, option) {
const collectionValuesKey = option.name;
const values = data?.[collectionValuesKey];
if (Array.isArray(values)) {
for (let i = 0; i < values.length; i++) {
values[i] = this.redactValues(values[i], option.values);
}
}
else if (typeof values === 'object' && values !== null) {
data[collectionValuesKey] = this.redactValues(values, option.values);
}
}
redactJsonLeaves(obj) {
if (Array.isArray(obj))
return obj.map((item) => this.redactJsonLeaves(item));
if (typeof obj === 'object' && obj !== null) {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [
k,
this.redactJsonLeaves(v),
]));
}
return CUSTOM_AUTH_JSON_REDACTED_VALUE;
}
mergeRedactedJsonLeaves(newVal, savedVal) {
if (newVal === CUSTOM_AUTH_JSON_REDACTED_VALUE)
return savedVal;
if (Array.isArray(newVal) && Array.isArray(savedVal)) {
return newVal.map((item, i) => this.mergeRedactedJsonLeaves(item, savedVal[i]));
}
if (Array.isArray(newVal) || Array.isArray(savedVal)) {
return newVal;
}
if (typeof newVal === 'object' &&
newVal !== null &&
typeof savedVal === 'object' &&
savedVal !== null) {
return Object.fromEntries(Object.entries(newVal).map(([k, v]) => [
k,
this.mergeRedactedJsonLeaves(v, savedVal[k]),
]));
}
return newVal;
}
unredactRestoreValues(unmerged, replacement) {
for (const [key, value] of Object.entries(unmerged)) {
if (value === n8n_workflow_1.CREDENTIAL_BLANKING_VALUE || value === n8n_workflow_1.CREDENTIAL_EMPTY_VALUE) {
unmerged[key] = replacement[key];
}
else if (typeof value === 'object' &&
value !== null &&
key in replacement &&
typeof replacement[key] === 'object' &&
replacement[key] !== null) {
this.unredactRestoreValues(value, replacement[key]);
}
}
}
getCredentialTypeProperties(credentialType) {
try {
return this.credentialTypes.getByName(credentialType).properties;
}
catch {
return [];
}
}
unredact(redactedData, savedData, props = []) {
const mergedData = (0, n8n_workflow_1.deepCopy)(redactedData);
this.unredactRestoreValues(mergedData, savedData);
for (const prop of props) {
if (!prop.typeOptions?.redactJsonLeaves)
continue;
if (!(prop.name in mergedData) || !(prop.name in savedData))
continue;
try {
const newJson = (0, n8n_workflow_1.jsonParse)(String(mergedData[prop.name]));
const savedJson = (0, n8n_workflow_1.jsonParse)(String(savedData[prop.name]));
mergedData[prop.name] = (0, n8n_workflow_1.jsonStringify)(this.mergeRedactedJsonLeaves(newJson, savedJson));
}
catch {
}
}
return mergedData;
}
async getOne(user, credentialId, includeDecryptedData) {
let sharing = null;
let decryptedData = null;
sharing = includeDecryptedData
?
await this.getSharing(user, credentialId, [
'credential:read',
])
: null;
if (sharing) {
decryptedData = await this.decrypt(sharing.credentials);
}
else {
sharing = await this.getSharing(user, credentialId, ['credential:read']);
}
if (!sharing) {
throw new not_found_error_1.NotFoundError(`Credential with ID "${credentialId}" could not be found.`);
}
const { credentials: credential } = sharing;
const { data: _, ...rest } = credential;
if (decryptedData) {
if (decryptedData?.oauthTokenData) {
decryptedData.oauthTokenData = true;
}
return { data: decryptedData, ...rest };
}
return { ...rest };
}
async getCredentialScopes(user, credentialId) {
const userProjectRelations = await this.projectService.getProjectRelationsForUser(user);
const projectIds = [...new Set(userProjectRelations.map((pr) => pr.projectId))];
if (projectIds.length === 0) {
return this.roleService.combineResourceScopes('credential', user, [], userProjectRelations);
}
const shared = await this.sharedCredentialsRepository.find({
where: {
projectId: (0, typeorm_1.In)(projectIds),
credentialsId: credentialId,
},
});
return this.roleService.combineResourceScopes('credential', user, shared, userProjectRelations);
}
async transferAll(fromProjectId, toProjectId, trx) {
trx = trx ?? this.credentialsRepository.manager;
const allSharedCredentials = await trx.findBy(db_1.SharedCredentials, {
projectId: (0, typeorm_1.In)([fromProjectId, toProjectId]),
});
const sharedCredentialsOfFromProject = allSharedCredentials.filter((sc) => sc.projectId === fromProjectId);
const ownedCredentialIds = sharedCredentialsOfFromProject
.filter((sc) => sc.role === 'credential:owner')
.map((sc) => sc.credentialsId);
await this.sharedCredentialsRepository.makeOwner(ownedCredentialIds, toProjectId, trx);
await this.sharedCredentialsRepository.deleteByIds(ownedCredentialIds, fromProjectId, trx);
const sharedCredentialIdsOfTransferee = allSharedCredentials
.filter((sc) => sc.projectId === toProjectId)
.map((sc) => sc.credentialsId);
const sharedCredentialsToTransfer = sharedCredentialsOfFromProject.filter((sc) => sc.role !== 'credential:owner' &&
!sharedCredentialIdsOfTransferee.includes(sc.credentialsId));
await trx.insert(db_1.SharedCredentials, sharedCredentialsToTransfer.map((sc) => ({
credentialsId: sc.credentialsId,
projectId: toProjectId,
role: sc.role,
})));
}
async replaceCredentialContentsForSharee(user, credential, decryptedData, mergedCredentials) {
if (!(await (0, check_access_1.userHasScopes)(user, ['credential:update'], false, { credentialId: credential.id }))) {
mergedCredentials.data = decryptedData;
}
}
async createUnmanagedCredential(dto, user) {
return await this.createCredential({ ...dto, isManaged: false }, user);
}
async checkCredentialData(type, data, user, projectId) {
const credentialProperties = this.credentialsHelper.getCredentialsProperties(type);
for (const property of credentialProperties) {
if (property.required &&
property.displayOptions !== undefined &&
(0, n8n_workflow_1.displayParameter)(data, property, null, null)) {
const value = data[property.name];
const hasDefault = property.default !== undefined && property.default !== null && property.default !== '';
if ((value === undefined || value === null || value === '') && !hasDefault) {
throw new bad_request_error_1.BadRequestError(`The field "${property.name}" is mandatory for credentials of type "${type}"`);
}
}
}
await (0, validation_1.validateExternalSecretsPermissions)({ user, projectId, dataToSave: data });
this.validateOAuthCredentialUrls(type, data);
}
validateOAuthCredentialUrls(type, data) {
const parentTypes = this.credentialTypes.getParentTypes(type) ?? [];
const isOAuth2 = type === 'oAuth2Api' || parentTypes.includes('oAuth2Api');
const isOAuth1 = type === 'oAuth1Api' || parentTypes.includes('oAuth1Api');
if (isOAuth2) {
const oauthUrlFields = ['authUrl', 'accessTokenUrl', 'serverUrl'];
for (const field of oauthUrlFields) {
const value = data[field];
if (typeof value === 'string' && value.trim() !== '' && !(0, n8n_workflow_1.isExpression)(value)) {
(0, validate_oauth_url_1.validateOAuthUrl)(value);
}
}
}
if (isOAuth1) {
const oauthUrlFields = ['authUrl', 'requestTokenUrl', 'accessTokenUrl'];
for (const field of oauthUrlFields) {
const value = data[field];
if (typeof value === 'string' && value.trim() !== '' && !(0, n8n_workflow_1.isExpression)(value)) {
(0, validate_oauth_url_1.validateOAuthUrl)(value);
}
}
}
}
async createManagedCredential(dto, user) {
return await this.createCredential({ ...dto, isManaged: true }, user);
}
async createCredential(opts, user) {
const targetProjectId = await this.resolveOwningProjectIdForNewCredential(user, opts.projectId);
await this.checkCredentialData(opts.type, opts.data, user, targetProjectId);
if (this.externalSecretsConfig.externalSecretsForProjects) {
await (0, validation_1.validateAccessToReferencedSecretProviders)(targetProjectId, opts.data, this.externalSecretsProviderAccessCheckService, 'create');
}
const encryptedCredential = await this.createEncryptedData({
id: null,
name: opts.name,
type: opts.type,
data: opts.data,
});
const isGlobal = opts.isGlobal;
if (isGlobal === true) {
const canShareGlobally = (0, permissions_1.hasGlobalScope)(user, 'credential:shareGlobally');
if (!canShareGlobally) {
throw new forbidden_error_1.ForbiddenError('You do not have permission to create globally shared credentials');
}
encryptedCredential.isGlobal = isGlobal;
}
const credentialEntity = this.credentialsRepository.create({
...encryptedCredential,
isManaged: opts.isManaged,
isResolvable: opts.isResolvable ?? false,
});
const { shared, ...credential } = await this.save(credentialEntity, encryptedCredential, user, targetProjectId, opts.data);
const scopes = await this.getCredentialScopes(user, credential.id);
return { ...credential, scopes };
}
async prepareCredentialsForTest({ storedCredential, user, credentialsToTest, }) {
const decryptedData = await this.decrypt(storedCredential, true);
const mergedCredentials = credentialsToTest
? (0, n8n_workflow_1.deepCopy)(credentialsToTest)
: {
id: storedCredential.id,
name: storedCredential.name,
type: storedCredential.type,
data: decryptedData,
};
if (user && credentialsToTest) {
await this.replaceCredentialContentsForSharee(user, storedCredential, decryptedData, mergedCredentials);
if (mergedCredentials.data) {
mergedCredentials.data = this.unredact(mergedCredentials.data, decryptedData, this.getCredentialTypeProperties(storedCredential.type));
}
}
return mergedCredentials;
}
};
exports.CredentialsService = CredentialsService;
exports.CredentialsService = CredentialsService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [db_1.CredentialsRepository,
credential_dependency_service_1.CredentialDependencyService,
db_1.SharedCredentialsRepository,
ownership_service_1.OwnershipService,
backend_common_1.Logger,
n8n_core_1.ErrorReporter,
credentials_tester_service_1.CredentialsTester,
external_hooks_1.ExternalHooks,
credential_types_1.CredentialTypes,
db_1.ProjectRepository,
project_service_ee_1.ProjectService,
role_service_1.RoleService,
db_1.UserRepository,
credentials_finder_service_1.CredentialsFinderService,
credentials_helper_1.CredentialsHelper,
external_secrets_config_1.ExternalSecretsConfig,
secret_provider_access_check_service_ee_1.SecretsProviderAccessCheckService])
], CredentialsService);
//# sourceMappingURL=credentials.service.js.map