⚡ Make it possible to set credentials to fixed values
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
CredentialsOverwrites,
|
||||
Db,
|
||||
GenericHelpers,
|
||||
IWorkflowBase,
|
||||
@@ -100,6 +101,10 @@ export class Execute extends Command {
|
||||
// Wait till the n8n-packages have been read
|
||||
await loadNodesAndCredentialsPromise;
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
await credentialsOverwrites.init();
|
||||
|
||||
// Add the found types to an instance other parts of the application can use
|
||||
const nodeTypes = NodeTypes();
|
||||
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as config from '../config';
|
||||
import {
|
||||
ActiveWorkflowRunner,
|
||||
CredentialTypes,
|
||||
CredentialsOverwrites,
|
||||
Db,
|
||||
GenericHelpers,
|
||||
LoadNodesAndCredentials,
|
||||
@@ -112,6 +113,10 @@ export class Start extends Command {
|
||||
const loadNodesAndCredentials = LoadNodesAndCredentials();
|
||||
await loadNodesAndCredentials.init();
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
await credentialsOverwrites.init();
|
||||
|
||||
// Add the found types to an instance other parts of the application can use
|
||||
const nodeTypes = NodeTypes();
|
||||
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
||||
|
||||
@@ -54,6 +54,19 @@ const config = convict({
|
||||
},
|
||||
},
|
||||
|
||||
credentials: {
|
||||
overwrite: {
|
||||
// Allows to set default values for credentials which
|
||||
// get automatically prefilled and the user does not get
|
||||
// displayed and can not change.
|
||||
// Format: { CREDENTIAL_NAME: { PARAMTER: VALUE }}
|
||||
doc: 'Overwrites for credentials',
|
||||
format: '*',
|
||||
default: '{}',
|
||||
env: 'CREDENTIALS_OVERWRITE'
|
||||
}
|
||||
},
|
||||
|
||||
executions: {
|
||||
// If a workflow executes all the data gets saved by default. This
|
||||
// could be a problem when a workflow gets executed a lot and processes
|
||||
|
||||
@@ -3,6 +3,9 @@ import {
|
||||
ICredentialTypes as ICredentialTypesInterface,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
CredentialsOverwrites,
|
||||
} from './';
|
||||
|
||||
class CredentialTypesClass implements ICredentialTypesInterface {
|
||||
|
||||
@@ -13,6 +16,19 @@ class CredentialTypesClass implements ICredentialTypesInterface {
|
||||
|
||||
async init(credentialTypes: { [key: string]: ICredentialType }): Promise<void> {
|
||||
this.credentialTypes = credentialTypes;
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites().getAll();
|
||||
|
||||
for (const credentialType of Object.keys(credentialsOverwrites)) {
|
||||
if (credentialTypes[credentialType] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add which properties got overwritten that the Editor-UI knows
|
||||
// which properties it should hide
|
||||
credentialTypes[credentialType].__overwrittenProperties = Object.keys(credentialsOverwrites[credentialType]);
|
||||
}
|
||||
}
|
||||
|
||||
getAll(): ICredentialType[] {
|
||||
|
||||
81
packages/cli/src/CredentialsHelper.ts
Normal file
81
packages/cli/src/CredentialsHelper.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
Credentials,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsHelper,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
CredentialsOverwrites,
|
||||
Db,
|
||||
ICredentialsDb,
|
||||
} from './';
|
||||
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
|
||||
/**
|
||||
* Returns the credentials instance
|
||||
*
|
||||
* @param {string} name Name of the credentials to return instance of
|
||||
* @param {string} type Type of the credentials to return instance of
|
||||
* @returns {Credentials}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
getCredentials(name: string, type: string): Credentials {
|
||||
if (!this.workflowCredentials[type]) {
|
||||
throw new Error(`No credentials of type "${type}" exist.`);
|
||||
}
|
||||
if (!this.workflowCredentials[type][name]) {
|
||||
throw new Error(`No credentials with name "${name}" exist for type "${type}".`);
|
||||
}
|
||||
const credentialData = this.workflowCredentials[type][name];
|
||||
|
||||
return new Credentials(credentialData.name, credentialData.type, credentialData.nodesAccess, credentialData.data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the decrypted credential data with applied overwrites
|
||||
*
|
||||
* @param {string} name Name of the credentials to return data of
|
||||
* @param {string} type Type of the credentials to return data of
|
||||
* @returns {ICredentialDataDecryptedObject}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
getDecrypted(name: string, type: string): ICredentialDataDecryptedObject {
|
||||
const credentials = this.getCredentials(name, type);
|
||||
|
||||
// Load and apply the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
return credentialsOverwrites.applyOverwrite(credentials.type, credentials.getData(this.encryptionKey));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates credentials in the database
|
||||
*
|
||||
* @param {string} name Name of the credentials to set data of
|
||||
* @param {string} type Type of the credentials to set data of
|
||||
* @param {ICredentialDataDecryptedObject} data The data to set
|
||||
* @returns {Promise<void>}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
async updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void> {
|
||||
const credentials = await this.getCredentials(name, type);
|
||||
|
||||
credentials.setData(data, this.encryptionKey);
|
||||
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
|
||||
|
||||
// Add special database related data
|
||||
newCredentialsData.updatedAt = new Date();
|
||||
|
||||
// TODO: also add user automatically depending on who is logged in, if anybody is logged in
|
||||
|
||||
// Save the credentials in DB
|
||||
await Db.collections.Credentials!.save(newCredentialsData);
|
||||
}
|
||||
|
||||
}
|
||||
56
packages/cli/src/CredentialsOverwrites.ts
Normal file
56
packages/cli/src/CredentialsOverwrites.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
ICredentialsOverwrite,
|
||||
GenericHelpers,
|
||||
} from './';
|
||||
|
||||
|
||||
class CredentialsOverwritesClass {
|
||||
|
||||
private overwriteData: ICredentialsOverwrite = {};
|
||||
|
||||
async init() {
|
||||
const data = await GenericHelpers.getConfigValue('credentials.overwrite') as string;
|
||||
|
||||
try {
|
||||
this.overwriteData = JSON.parse(data);
|
||||
} catch (error) {
|
||||
throw new Error(`The credentials-overwrite is not valid JSON.`);
|
||||
}
|
||||
}
|
||||
|
||||
applyOverwrite(type: string, data: ICredentialDataDecryptedObject) {
|
||||
const overwrites = this.get(type);
|
||||
|
||||
if (overwrites === undefined) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const returnData = JSON.parse(JSON.stringify(data));
|
||||
Object.assign(returnData, overwrites);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
get(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return this.overwriteData[type];
|
||||
}
|
||||
|
||||
getAll(): ICredentialsOverwrite {
|
||||
return this.overwriteData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let credentialsOverwritesInstance: CredentialsOverwritesClass | undefined;
|
||||
|
||||
export function CredentialsOverwrites(): CredentialsOverwritesClass {
|
||||
if (credentialsOverwritesInstance === undefined) {
|
||||
credentialsOverwritesInstance = new CredentialsOverwritesClass();
|
||||
}
|
||||
|
||||
return credentialsOverwritesInstance;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsEncrypted,
|
||||
IDataObject,
|
||||
@@ -34,6 +35,9 @@ export interface ICustomRequest extends Request {
|
||||
parsedUrl: Url | undefined;
|
||||
}
|
||||
|
||||
export interface ICredentialsOverwrite {
|
||||
[key: string]: ICredentialDataDecryptedObject;
|
||||
}
|
||||
|
||||
export interface IDatabaseCollections {
|
||||
Credentials: Repository<ICredentialsDb> | null;
|
||||
|
||||
@@ -137,7 +137,7 @@ class LoadNodesAndCredentialsClass {
|
||||
}
|
||||
}
|
||||
|
||||
this.credentialTypes[credentialName] = tempCredential;
|
||||
this.credentialTypes[tempCredential.name] = tempCredential;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
// Some nodeTypes need to get special parameters applied like the
|
||||
// polling nodes the polling times
|
||||
for (const nodeTypeData of Object.values(nodeTypes)) {
|
||||
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type)
|
||||
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type);
|
||||
|
||||
if (applyParameters.length) {
|
||||
nodeTypeData.type.description.properties.unshift.apply(nodeTypeData.type.description.properties, applyParameters);
|
||||
|
||||
@@ -18,6 +18,7 @@ import * as csrf from 'csrf';
|
||||
import {
|
||||
ActiveExecutions,
|
||||
ActiveWorkflowRunner,
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
IActivationError,
|
||||
@@ -648,6 +649,10 @@ class App {
|
||||
this.app.post('/rest/credentials', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<ICredentialsResponse> => {
|
||||
const incomingData = req.body;
|
||||
|
||||
if (!incomingData.name || incomingData.name.length < 3) {
|
||||
throw new ResponseHelper.ResponseError(`Credentials name must be at least 3 characters long.`, undefined, 400);
|
||||
}
|
||||
|
||||
// Add the added date for node access permissions
|
||||
for (const nodeAccess of incomingData.nodesAccess) {
|
||||
nodeAccess.date = this.getCurrentDate();
|
||||
@@ -684,6 +689,7 @@ class App {
|
||||
|
||||
// Save the credentials in DB
|
||||
const result = await Db.collections.Credentials!.save(newCredentialsData);
|
||||
result.data = incomingData.data;
|
||||
|
||||
// Convert to response format in which the id is a string
|
||||
(result as unknown as ICredentialsResponse).id = result.id.toString();
|
||||
@@ -805,14 +811,6 @@ class App {
|
||||
|
||||
const results = await Db.collections.Credentials!.find(findQuery) as unknown as ICredentialsResponse[];
|
||||
|
||||
let encryptionKey = undefined;
|
||||
if (req.query.includeData === true) {
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
throw new Error('No encryption key got found to decrypt the credentials!');
|
||||
}
|
||||
}
|
||||
|
||||
let result;
|
||||
for (result of results) {
|
||||
(result as ICredentialsDecryptedResponse).id = result.id.toString();
|
||||
@@ -866,19 +864,17 @@ class App {
|
||||
}
|
||||
|
||||
const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data);
|
||||
(result as ICredentialsDecryptedDb).data = credentials.getData(encryptionKey!);
|
||||
(result as ICredentialsDecryptedResponse).id = result.id.toString();
|
||||
const savedCredentialsData = credentials.getData(encryptionKey);
|
||||
|
||||
const oauthCredentials = (result as ICredentialsDecryptedDb).data;
|
||||
if (oauthCredentials === undefined) {
|
||||
throw new Error('Unable to read OAuth credentials');
|
||||
}
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData);
|
||||
|
||||
const token = new csrf();
|
||||
// Generate a CSRF prevention token and send it as a OAuth2 state stringma/ERR
|
||||
oauthCredentials.csrfSecret = token.secretSync();
|
||||
const csrfSecret = token.secretSync();
|
||||
const state = {
|
||||
token: token.create(oauthCredentials.csrfSecret),
|
||||
token: token.create(csrfSecret),
|
||||
cid: req.query.id
|
||||
};
|
||||
const stateEncodedStr = Buffer.from(JSON.stringify(state)).toString('base64') as string;
|
||||
@@ -893,7 +889,8 @@ class App {
|
||||
state: stateEncodedStr,
|
||||
});
|
||||
|
||||
credentials.setData(oauthCredentials, encryptionKey);
|
||||
savedCredentialsData.csrfSecret = csrfSecret;
|
||||
credentials.setData(savedCredentialsData, encryptionKey);
|
||||
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
|
||||
|
||||
// Add special database related data
|
||||
@@ -939,12 +936,11 @@ class App {
|
||||
}
|
||||
|
||||
const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data);
|
||||
(result as ICredentialsDecryptedDb).data = credentials.getData(encryptionKey!);
|
||||
const oauthCredentials = (result as ICredentialsDecryptedDb).data;
|
||||
if (oauthCredentials === undefined) {
|
||||
const errorResponse = new ResponseHelper.ResponseError('Unable to read OAuth credentials!', undefined, 503);
|
||||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
const savedCredentialsData = credentials.getData(encryptionKey!);
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData);
|
||||
|
||||
const token = new csrf();
|
||||
if (oauthCredentials.csrfSecret === undefined || !token.verify(oauthCredentials.csrfSecret as string, state.token)) {
|
||||
@@ -968,9 +964,10 @@ class App {
|
||||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
oauthCredentials.oauthTokenData = JSON.stringify(oauthToken.data);
|
||||
_.unset(oauthCredentials, 'csrfSecret');
|
||||
credentials.setData(oauthCredentials, encryptionKey);
|
||||
savedCredentialsData.oauthTokenData = JSON.stringify(oauthToken.data);
|
||||
_.unset(savedCredentialsData, 'csrfSecret');
|
||||
|
||||
credentials.setData(savedCredentialsData, encryptionKey);
|
||||
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
|
||||
// Add special database related data
|
||||
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
CredentialsHelper,
|
||||
Db,
|
||||
ICredentialsDb,
|
||||
IExecutionDb,
|
||||
IExecutionFlattedDb,
|
||||
IPushDataExecutionFinished,
|
||||
@@ -14,13 +14,11 @@ import {
|
||||
} from './';
|
||||
|
||||
import {
|
||||
Credentials,
|
||||
UserSettings,
|
||||
WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteWorkflowInfo,
|
||||
@@ -372,40 +370,6 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates credentials with new data
|
||||
*
|
||||
* @export
|
||||
* @param {string} name Name of the credentials to update
|
||||
* @param {string} type Type of the credentials to update
|
||||
* @param {ICredentialDataDecryptedObject} data The new credential data
|
||||
* @param {string} encryptionKey The encryption key to use
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject, encryptionKey: string): Promise<void> {
|
||||
const foundCredentials = await Db.collections.Credentials!.find({ name, type });
|
||||
|
||||
if (!foundCredentials.length) {
|
||||
throw new Error(`Could not find credentials for type "${type}" with name "${name}".`);
|
||||
}
|
||||
|
||||
const credentialsDb = foundCredentials[0];
|
||||
|
||||
// Encrypt the data
|
||||
const credentials = new Credentials(credentialsDb.name, credentialsDb.type, credentialsDb.nodesAccess);
|
||||
credentials.setData(data, encryptionKey);
|
||||
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
|
||||
|
||||
// Add special database related data
|
||||
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||
|
||||
// TODO: also add user automatically depending on who is logged in, if anybody is logged in
|
||||
|
||||
// Save the credentials in DB
|
||||
await Db.collections.Credentials!.save(newCredentialsData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the base additional data without webhooks
|
||||
*
|
||||
@@ -428,11 +392,11 @@ export async function getBase(credentials: IWorkflowCredentials, currentNodePara
|
||||
|
||||
return {
|
||||
credentials,
|
||||
credentialsHelper: new CredentialsHelper(credentials, encryptionKey),
|
||||
encryptionKey,
|
||||
executeWorkflow,
|
||||
restApiUrl: urlBaseWebhook + config.get('endpoints.rest') as string,
|
||||
timezone,
|
||||
updateCredentials,
|
||||
webhookBaseUrl,
|
||||
webhookTestBaseUrl,
|
||||
currentNodeParameters,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './CredentialsHelper';
|
||||
export * from './CredentialTypes';
|
||||
export * from './CredentialsOverwrites';
|
||||
export * from './Interfaces';
|
||||
export * from './LoadNodesAndCredentials';
|
||||
export * from './NodeTypes';
|
||||
|
||||
Reference in New Issue
Block a user