Skip to content

Commit

Permalink
Merge branch 'feature/resource-mapping-component' into NODE-398-resou…
Browse files Browse the repository at this point in the history
…rce-mapper-ui-P2

* feature/resource-mapping-component:
  feat(core): Manage version control settings (#6079)
  ci: Fix linting error on `master` (no-changelog) (#6075)
  ci: Increase the timeout on the release process (no-changelog) (#6078)
  fix(editor): Update LDAP and Log streaming paywalls (#6069)
  feat(core): Add instanceId to n8n.ready hook (no-changelog) (#6007)
  ci: Update linting dependencies, and setup eslint-plugin-unicorn (no-changelog) (#6070)
  • Loading branch information
MiloradFilipovic committed Apr 25, 2023
2 parents be34112 + d30dbaf commit b35717e
Show file tree
Hide file tree
Showing 19 changed files with 502 additions and 341 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
permissions:
contents: write

timeout-minutes: 10
timeout-minutes: 60

steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"[email protected]": "patches/[email protected]",
"[email protected]": "patches/[email protected]",
"@sentry/[email protected]": "patches/@[email protected]",
"@typescript-eslint/eslint-plugin@5.45.0": "patches/@typescript-eslint__eslint-plugin@5.45.0.patch"
"@typescript-eslint/eslint-plugin@5.59.0": "patches/@typescript-eslint__eslint-plugin@5.59.0.patch"
}
}
}
9 changes: 9 additions & 0 deletions packages/@n8n_io/eslint-config/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const config = (module.exports = {
* https://github.com/ivov/eslint-plugin-n8n-local-rules
*/
'eslint-plugin-n8n-local-rules',

/** https://github.com/sindresorhus/eslint-plugin-unicorn */
'eslint-plugin-unicorn',
],

extends: [
Expand Down Expand Up @@ -422,6 +425,12 @@ const config = (module.exports = {
* https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md
*/
'import/prefer-default-export': 'off',

/** https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-await.md */
'unicorn/no-unnecessary-await': 'error',

/** https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md */
'unicorn/no-useless-promise-resolve-reject': 'error',
},

overrides: [
Expand Down
13 changes: 7 additions & 6 deletions packages/@n8n_io/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
"private": true,
"version": "0.0.1",
"devDependencies": {
"@types/eslint": "~8.4",
"@typescript-eslint/eslint-plugin": "~5.45",
"@typescript-eslint/parser": "~5.45",
"@types/eslint": "~8.37",
"@typescript-eslint/eslint-plugin": "~5.59",
"@typescript-eslint/parser": "~5.59",
"@vue/eslint-config-typescript": "~8.0",
"eslint": "~8.28",
"eslint": "~8.39",
"eslint-config-airbnb-typescript": "~17.0",
"eslint-config-prettier": "~8.5",
"eslint-config-prettier": "~8.8",
"eslint-import-resolver-typescript": "~3.5",
"eslint-plugin-diff": "~2.0",
"eslint-plugin-import": "~2.26",
"eslint-plugin-import": "~2.27",
"eslint-plugin-n8n-local-rules": "~1.0",
"eslint-plugin-prettier": "~4.2",
"eslint-plugin-unicorn": "~46.0",
"eslint-plugin-vue": "~7.17"
},
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/AbstractServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export abstract class AbstractServer {

protected endpointWebhookWaiting: string;

protected instanceId = '';

abstract configure(): Promise<void>;

constructor() {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,13 +447,15 @@ class Server extends AbstractServer {
async configure(): Promise<void> {
configureMetrics(this.app);

this.instanceId = await UserSettings.getInstanceId();

this.frontendSettings.isNpmAvailable = await exec('npm --version')
.then(() => true)
.catch(() => false);

this.frontendSettings.versionCli = N8N_VERSION;

this.frontendSettings.instanceId = await UserSettings.getInstanceId();
this.frontendSettings.instanceId = this.instanceId;

await this.externalHooks.run('frontend.settings', [this.frontendSettings]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
import type { RequestHandler } from 'express';
import type { AuthenticatedRequest } from '@/requests';
import {
isVersionControlLicensed,
isVersionControlLicensedAndEnabled,
} from '../versionControlHelper';

export const versionControlLicensedOwnerMiddleware: RequestHandler = (
req: AuthenticatedRequest,
res,
next,
) => {
if (isVersionControlLicensed() && req.user?.globalRole.name === 'owner') {
next();
} else {
res.status(401).json({ status: 'error', message: 'Unauthorized' });
}
};

export const versionControlLicensedAndEnabledMiddleware: RequestHandler = (req, res, next) => {
if (isVersionControlLicensedAndEnabled()) {
next();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { AuthenticatedRequest } from '@/requests';
import type { VersionControlPreferences } from './versionControlPreferences';

export declare namespace VersionControlRequest {
type UpdatePreferences = AuthenticatedRequest<{}, {}, Partial<VersionControlPreferences>, {}>;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
import { IsString } from 'class-validator';
import { IsBoolean, IsEmail, IsHexColor, IsOptional, IsString } from 'class-validator';

export class VersionControlPreferences {
constructor(preferences: Partial<VersionControlPreferences> | undefined = undefined) {
if (preferences) Object.assign(this, preferences);
}

@IsBoolean()
connected: boolean;

@IsString()
repositoryUrl: string;

@IsString()
authorName: string;

@IsEmail()
authorEmail: string;

@IsString()
branchName: string;

@IsBoolean()
branchReadOnly: boolean;

@IsHexColor()
branchColor: string;

@IsOptional()
@IsString()
privateKey: string;
readonly privateKey?: string;

@IsOptional()
@IsString()
publicKey: string;
readonly publicKey?: string;
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import { Get, RestController } from '../../decorators';
import {
versionControlLicensedMiddleware,
versionControlLicensedOwnerMiddleware,
} from './middleware/versionControlEnabledMiddleware';
import { Authorized, Get, Post, RestController } from '@/decorators';
import { versionControlLicensedMiddleware } from './middleware/versionControlEnabledMiddleware';
import { VersionControlService } from './versionControl.service.ee';
import { VersionControlRequest } from './types/requests';
import type { VersionControlPreferences } from './types/versionControlPreferences';

@RestController('/versionControl')
export class VersionControlController {
constructor(private versionControlService: VersionControlService) {}

@Authorized('any')
@Get('/preferences', { middlewares: [versionControlLicensedMiddleware] })
async getPreferences() {
async getPreferences(): Promise<VersionControlPreferences> {
// returns the settings with the privateKey property redacted
return this.versionControlService.versionControlPreferences;
}

@Authorized(['global', 'owner'])
@Post('/preferences', { middlewares: [versionControlLicensedMiddleware] })
async setPreferences(req: VersionControlRequest.UpdatePreferences) {
const sanitizedPreferences: Partial<VersionControlPreferences> = {
...req.body,
privateKey: undefined,
publicKey: undefined,
};
await this.versionControlService.validateVersionControlPreferences(sanitizedPreferences);
return this.versionControlService.setPreferences(sanitizedPreferences);
}

//TODO: temporary function to generate key and save new pair
@Get('/generateKeyPair', { middlewares: [versionControlLicensedOwnerMiddleware] })
// REMOVE THIS FUNCTION AFTER TESTING
@Authorized(['global', 'owner'])
@Get('/generateKeyPair', { middlewares: [versionControlLicensedMiddleware] })
async generateKeyPair() {
return this.versionControlService.generateAndSaveKeyPair();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { generateSshKeyPair } from './versionControlHelper';
import { VersionControlPreferences } from './types/versionControlPreferences';
import { VERSION_CONTROL_PREFERENCES_DB_KEY } from './constants';
import * as Db from '@/Db';
import { jsonParse } from 'n8n-workflow';
import { jsonParse, LoggerProxy } from 'n8n-workflow';
import type { ValidationError } from 'class-validator';
import { validate } from 'class-validator';

@Service()
export class VersionControlService {
Expand All @@ -16,55 +18,91 @@ export class VersionControlService {
public get versionControlPreferences(): VersionControlPreferences {
return {
...this._versionControlPreferences,
privateKey: '',
privateKey: '(redacted)',
};
}

async generateAndSaveKeyPair(keyType: 'ed25519' | 'rsa' = 'ed25519') {
const keyPair = generateSshKeyPair(keyType);
public set versionControlPreferences(preferences: Partial<VersionControlPreferences>) {
this._versionControlPreferences = {
connected: preferences.connected ?? this._versionControlPreferences.connected,
authorEmail: preferences.authorEmail ?? this._versionControlPreferences.authorEmail,
authorName: preferences.authorName ?? this._versionControlPreferences.authorName,
branchName: preferences.branchName ?? this._versionControlPreferences.branchName,
branchColor: preferences.branchColor ?? this._versionControlPreferences.branchColor,
branchReadOnly: preferences.branchReadOnly ?? this._versionControlPreferences.branchReadOnly,
privateKey: preferences.privateKey ?? this._versionControlPreferences.privateKey,
publicKey: preferences.publicKey ?? this._versionControlPreferences.publicKey,
repositoryUrl: preferences.repositoryUrl ?? this._versionControlPreferences.repositoryUrl,
};
}

async generateAndSaveKeyPair() {
const keyPair = generateSshKeyPair('ed25519');
if (keyPair.publicKey && keyPair.privateKey) {
this.setPreferences({ ...keyPair });
await this.saveSamlPreferencesToDb();
await this.setPreferences({ ...keyPair });
} else {
LoggerProxy.error('Failed to generate key pair');
}
return keyPair;
}

setPreferences(prefs: Partial<VersionControlPreferences>) {
this._versionControlPreferences = {
...this._versionControlPreferences,
...prefs,
};
async validateVersionControlPreferences(
preferences: Partial<VersionControlPreferences>,
): Promise<ValidationError[]> {
const preferencesObject = new VersionControlPreferences(preferences);
const validationResult = await validate(preferencesObject, {
forbidUnknownValues: false,
skipMissingProperties: true,
stopAtFirstError: false,
validationError: { target: false },
});
if (validationResult.length > 0) {
throw new Error(`Invalid version control preferences: ${JSON.stringify(validationResult)}`);
}
// TODO: if repositoryUrl is changed, check if it is valid
// TODO: if branchName is changed, check if it is valid
return validationResult;
}

async setPreferences(
preferences: Partial<VersionControlPreferences>,
saveToDb = true,
): Promise<VersionControlPreferences> {
this.versionControlPreferences = preferences;
if (saveToDb) {
const settingsValue = JSON.stringify(this._versionControlPreferences);
try {
await Db.collections.Settings.save({
key: VERSION_CONTROL_PREFERENCES_DB_KEY,
value: settingsValue,
loadOnStartup: true,
});
} catch (error) {
throw new Error(`Failed to save version control preferences: ${(error as Error).message}`);
}
}
return this.versionControlPreferences;
}

async loadFromDbAndApplyVersionControlPreferences(): Promise<
VersionControlPreferences | undefined
> {
const loadedPrefs = await Db.collections.Settings.findOne({
const loadedPreferences = await Db.collections.Settings.findOne({
where: { key: VERSION_CONTROL_PREFERENCES_DB_KEY },
});
if (loadedPrefs) {
if (loadedPreferences) {
try {
const prefs = jsonParse<VersionControlPreferences>(loadedPrefs.value);
if (prefs) {
this.setPreferences(prefs);
return prefs;
const preferences = jsonParse<VersionControlPreferences>(loadedPreferences.value);
if (preferences) {
await this.setPreferences(preferences, false);
return preferences;
}
} catch {}
} catch (error) {
LoggerProxy.warn(
`Could not parse Version Control settings from database: ${(error as Error).message}`,
);
}
}
return;
}

async saveSamlPreferencesToDb(): Promise<VersionControlPreferences | undefined> {
const settingsValue = JSON.stringify(this._versionControlPreferences);
const result = await Db.collections.Settings.save({
key: VERSION_CONTROL_PREFERENCES_DB_KEY,
value: settingsValue,
loadOnStartup: true,
});
if (result)
try {
return jsonParse<VersionControlPreferences>(result.value);
} catch {}
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@ export function generateSshKeyPair(keyType: 'ed25519' | 'rsa' = 'ed25519') {
keyPair.publicKey = keyPublic.toString('ssh');
const keyPrivate = sshpk.parsePrivateKey(generatedKeyPair.privateKey, 'pem');
keyPair.privateKey = keyPrivate.toString('ssh-private');
return keyPair;
return {
privateKey: keyPair.privateKey,
publicKey: keyPair.publicKey,
};
}
2 changes: 1 addition & 1 deletion packages/core/src/BinaryDataManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export class BinaryDataManager {
async (executionDataArray) => {
if (executionDataArray) {
return Promise.all(
executionDataArray.map((executionData) => {
executionDataArray.map(async (executionData) => {
if (executionData.binary) {
return this.duplicateBinaryDataInExecData(executionData, executionId);
}
Expand Down
13 changes: 7 additions & 6 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1234,9 +1234,9 @@
"settings.log-streaming": "Log Streaming",
"settings.log-streaming.heading": "Log Streaming",
"settings.log-streaming.add": "Add new destination",
"settings.log-streaming.actionBox.title": "Available on custom plans",
"settings.log-streaming.actionBox.description": "Log Streaming is available as a paid feature. Get in touch to learn more about it.",
"settings.log-streaming.actionBox.button": "Contact us",
"settings.log-streaming.actionBox.title": "Available on Enterprise plan",
"settings.log-streaming.actionBox.description": "Log Streaming is available as a paid feature. Learn more about it.",
"settings.log-streaming.actionBox.button": "See plans",
"settings.log-streaming.infoText": "Send logs to external endpoints of your choice. You can also write logs to a file or the console using environment variables. <a href=\"https://docs.n8n.io/hosting/logging/\" target=\"_blank\">More info</a>",
"settings.log-streaming.addFirstTitle": "Set up a destination to get started",
"settings.log-streaming.addFirst": "Add your first destination by clicking on the button and selecting a destination type.",
Expand Down Expand Up @@ -1706,6 +1706,7 @@
"contextual.upgradeLinkUrl.desktop": "https://n8n.io/pricing/?utm_source=n8n-internal&utm_medium=desktop",

"settings.ldap": "LDAP",
"settings.ldap.note": "LDAP allows users to authenticate with their centralized account. It's compatible with services that provide an LDAP interface like Active Directory, Okta and Jumpcloud.",
"settings.ldap.infoTip": "Learn more about <a href='https://docs.n8n.io/user-management/ldap/' target='_blank'>LDAP in the Docs</a>",
"settings.ldap.save": "Save connection",
"settings.ldap.connectionTestError": "Problem testing LDAP connection",
Expand All @@ -1730,9 +1731,9 @@
"settings.ldap.confirmMessage.beforeSaveForm.confirmButtonText": "No",
"settings.ldap.confirmMessage.beforeSaveForm.headline": "Are you sure you want to disable LDAP login?",
"settings.ldap.confirmMessage.beforeSaveForm.message": "If you do so, all LDAP users will be converted to email users.",
"settings.ldap.disabled.title": "Available in custom plans",
"settings.ldap.disabled.description": "LDAP is available as a paid feature. Get in touch to learn more about it.",
"settings.ldap.disabled.buttonText": "Contact us",
"settings.ldap.disabled.title": "Available on Enterprise plan",
"settings.ldap.disabled.description": "LDAP is available as a paid feature. Learn more about it.",
"settings.ldap.disabled.buttonText": "See plans",
"settings.ldap.toast.sync.success": "Synchronization succeeded",
"settings.ldap.toast.connection.success": "Connection succeeded",
"settings.ldap.form.loginEnabled.label": "Enable LDAP Login",
Expand Down
Loading

0 comments on commit b35717e

Please sign in to comment.