feat: External Secrets storage for credentials (#6477)

Github issue / Community forum post (link here to close automatically):

---------

Co-authored-by: Romain Minaud <romain.minaud@gmail.com>
Co-authored-by: Valya Bullions <valya@n8n.io>
Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
Alex Grozav
2023-08-25 11:33:46 +03:00
committed by GitHub
parent c833078c87
commit ed927d34b2
89 changed files with 4164 additions and 57 deletions

View File

@@ -31,6 +31,7 @@ import type {
IHttpRequestHelper,
INodeTypeData,
INodeTypes,
IWorkflowExecuteAdditionalData,
ICredentialTestFunctions,
} from 'n8n-workflow';
import {
@@ -342,6 +343,7 @@ export class CredentialsHelper extends ICredentialsHelper {
* @param {boolean} [raw] Return the data as supplied without defaults or overwrites
*/
async getDecrypted(
additionalData: IWorkflowExecuteAdditionalData,
nodeCredentials: INodeCredentialsDetails,
type: string,
mode: WorkflowExecuteMode,
@@ -356,12 +358,18 @@ export class CredentialsHelper extends ICredentialsHelper {
return decryptedDataOriginal;
}
await additionalData?.secretsHelpers?.waitForInit();
const canUseSecrets = await this.credentialOwnedByOwner(nodeCredentials);
return this.applyDefaultsAndOverwrites(
additionalData,
decryptedDataOriginal,
type,
mode,
defaultTimezone,
expressionResolveValues,
canUseSecrets,
);
}
@@ -369,11 +377,13 @@ export class CredentialsHelper extends ICredentialsHelper {
* Applies credential default data and overwrites
*/
applyDefaultsAndOverwrites(
additionalData: IWorkflowExecuteAdditionalData,
decryptedDataOriginal: ICredentialDataDecryptedObject,
type: string,
mode: WorkflowExecuteMode,
defaultTimezone: string,
expressionResolveValues?: ICredentialsExpressionResolveValues,
canUseSecrets?: boolean,
): ICredentialDataDecryptedObject {
const credentialsProperties = this.getCredentialsProperties(type);
@@ -395,6 +405,10 @@ export class CredentialsHelper extends ICredentialsHelper {
decryptedData.oauthTokenData = decryptedDataOriginal.oauthTokenData;
}
const additionalKeys = NodeExecuteFunctions.getAdditionalKeys(additionalData, mode, null, {
secretsEnabled: canUseSecrets,
});
if (expressionResolveValues) {
const timezone = expressionResolveValues.workflow.settings.timezone ?? defaultTimezone;
@@ -408,7 +422,7 @@ export class CredentialsHelper extends ICredentialsHelper {
expressionResolveValues.connectionInputData,
mode,
timezone,
{},
additionalKeys,
undefined,
false,
decryptedData,
@@ -431,7 +445,7 @@ export class CredentialsHelper extends ICredentialsHelper {
decryptedData as INodeParameters,
mode,
defaultTimezone,
{},
additionalKeys,
undefined,
undefined,
decryptedData,
@@ -573,10 +587,24 @@ export class CredentialsHelper extends ICredentialsHelper {
}
if (credentialsDecrypted.data) {
credentialsDecrypted.data = CredentialsOverwrites().applyOverwrite(
credentialType,
credentialsDecrypted.data,
);
try {
const additionalData = await WorkflowExecuteAdditionalData.getBase(user.id);
credentialsDecrypted.data = this.applyDefaultsAndOverwrites(
additionalData,
credentialsDecrypted.data,
credentialType,
'internal' as WorkflowExecuteMode,
additionalData.timezone,
undefined,
user.isOwner,
);
} catch (error) {
Logger.debug('Credential test failed', error);
return {
status: 'Error',
message: error.message.toString(),
};
}
}
if (typeof credentialTestFunction === 'function') {
@@ -759,6 +787,36 @@ export class CredentialsHelper extends ICredentialsHelper {
message: 'Connection successful!',
};
}
async credentialOwnedByOwner(nodeCredential: INodeCredentialsDetails): Promise<boolean> {
if (!nodeCredential.id) {
return false;
}
const credential = await Db.collections.SharedCredentials.findOne({
where: {
role: {
scope: 'credential',
name: 'owner',
},
user: {
globalRole: {
scope: 'global',
name: 'owner',
},
},
credentials: {
id: nodeCredential.id,
},
},
});
if (!credential) {
return false;
}
return true;
}
}
/**