diff --git a/.gitignore b/.gitignore index b25164b547e58..a78cea1dba966 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ packages/**/.turbo cypress/videos/* cypress/screenshots/* *.swp +.env +.env.local diff --git a/PROCFILE b/PROCFILE deleted file mode 100644 index 797d9ec999a16..0000000000000 --- a/PROCFILE +++ /dev/null @@ -1 +0,0 @@ -web: diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000000..ee846863e8eee --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: npm start +worker: npm start worker diff --git a/packages/cli/src/AbstractServer.ts b/packages/cli/src/AbstractServer.ts index 2a58444e8b1f5..224bf51f78f21 100644 --- a/packages/cli/src/AbstractServer.ts +++ b/packages/cli/src/AbstractServer.ts @@ -8,6 +8,7 @@ import bodyParserXml from 'body-parser-xml'; import compression from 'compression'; import parseUrl from 'parseurl'; import type { RedisOptions } from 'ioredis'; +import { parseRedisUrl } from '@/ParserHelper' import type { WebhookHttpMethod } from 'n8n-workflow'; import { LoggerProxy as Logger } from 'n8n-workflow'; @@ -188,14 +189,15 @@ export abstract class AbstractServer { let lastTimer = 0; let cumulativeTimeout = 0; - const { host, port, username, password, db }: RedisOptions = config.getEnv('queue.bull.redis'); + let tlsConfig = {}; + const { host, port, password, db}: RedisOptions = parseRedisUrl() || config.getEnv('queue.bull.redis'); const redisConnectionTimeoutLimit = config.getEnv('queue.bull.redis.timeoutThreshold'); + Logger.debug(`Redis is configured to: host: ${host}, port: ${port}, db: ${db}`); - const redis = new Redis({ + let redisConfig: RedisOptions = { host, port, db, - username, password, retryStrategy: (): number | null => { const now = Date.now(); @@ -215,7 +217,13 @@ export abstract class AbstractServer { } return 500; }, - }); + } + + if ( host !== 'localhost' && '127.0.0.1' ) { + // If redis is in localhost mode then there is no need to configre any ssl options + redisConfig['tls'] = { rejectUnauthorized: false } + } + const redis = new Redis(redisConfig); redis.on('close', () => { Logger.warn('Redis unavailable - trying to reconnect...'); diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index f2157061f640d..e2626d4d8913a 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -45,6 +45,7 @@ import { WorkflowStatisticsRepository, WorkflowTagMappingRepository, } from '@db/repositories'; +import { parsePostgresUrl } from './ParserHelper'; export const collections = {} as IDatabaseCollections; @@ -89,20 +90,23 @@ export async function transaction(fn: (entityManager: EntityManager) => Promi export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions { switch (dbType) { case 'postgresdb': - const sslCa = config.getEnv('database.postgresdb.ssl.ca'); - const sslCert = config.getEnv('database.postgresdb.ssl.cert'); - const sslKey = config.getEnv('database.postgresdb.ssl.key'); - const sslRejectUnauthorized = config.getEnv('database.postgresdb.ssl.rejectUnauthorized'); - process.env['PGSSLMODE'] = 'require'; - let ssl: TlsOptions | undefined; - if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) { - ssl = { - ca: sslCa || undefined, - cert: sslCert || undefined, - key: sslKey || undefined, - rejectUnauthorized: sslRejectUnauthorized, - }; + + if (!isPostgresRunningLocally()) { + const sslCa = config.getEnv('database.postgresdb.ssl.ca'); + const sslCert = config.getEnv('database.postgresdb.ssl.cert'); + const sslKey = config.getEnv('database.postgresdb.ssl.key'); + const sslRejectUnauthorized = config.getEnv('database.postgresdb.ssl.rejectUnauthorized'); + process.env['PGSSLMODE'] = 'require'; + + if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) { + ssl = { + ca: sslCa || undefined, + cert: sslCert || undefined, + key: sslKey || undefined, + rejectUnauthorized: sslRejectUnauthorized, + }; + } } return { @@ -206,3 +210,14 @@ export const close = async () => { if (connection.isInitialized) await connection.destroy(); }; + +function isPostgresRunningLocally(): Boolean { + let host: String | undefined; + const postgresConfig = parsePostgresUrl(); + if (postgresConfig != null) { + host = postgresConfig.host; + } else { + host = config.getEnv('database.postgresdb.host') + } + return (host === ('localhost' || '127.0.0.1')); +} diff --git a/packages/cli/src/License.ts b/packages/cli/src/License.ts index 4d252d4ad6767..e241f5fbd18c6 100644 --- a/packages/cli/src/License.ts +++ b/packages/cli/src/License.ts @@ -114,11 +114,11 @@ export class License { } isLdapEnabled() { - return this.isFeatureEnabled(LICENSE_FEATURES.LDAP); + return true; } isSamlEnabled() { - return this.isFeatureEnabled(LICENSE_FEATURES.SAML); + return true; } isAdvancedExecutionFiltersEnabled() { diff --git a/packages/cli/src/ParserHelper.ts b/packages/cli/src/ParserHelper.ts new file mode 100644 index 0000000000000..0bf745c19301e --- /dev/null +++ b/packages/cli/src/ParserHelper.ts @@ -0,0 +1,39 @@ +import type { RedisOptions } from 'ioredis'; +import { LoggerProxy as Logger } from 'n8n-workflow'; +import { URL } from 'url'; +import config from '@/config'; + +export function parseRedisUrl(): RedisOptions | null{ + const redisUri = process.env.REDIS_URL; + if (redisUri) { + const parsedURL = new URL(redisUri); + Logger.debug(`Global Redis is configured`); + return { + host: parsedURL.hostname, + port: parseInt(parsedURL.port), + password: parsedURL.password, + db: config.getEnv('queue.bull.redis.db') + } + } else { + Logger.debug(`Global Redis is not configured, working with specific environment variables`); + return null; + } +} + +export function parsePostgresUrl(): any { + const postgresUri = process.env.DATABASE_URL; + if (postgresUri) { + const parsedURL = new URL(postgresUri); + Logger.debug(`Global Postgres is configured`); + return { + database: parsedURL.pathname.slice(1), + username: parsedURL.username, + password: parsedURL.password, + host: parsedURL.hostname, + port: parseInt(parsedURL.port), + } + } else { + Logger.debug(`Global Postgres is not configured, working with specific environment variables`); + return null; + } +} \ No newline at end of file diff --git a/packages/cli/src/Queue.ts b/packages/cli/src/Queue.ts index 1bd7fb5fcf4c0..cb6eb154bb4c9 100644 --- a/packages/cli/src/Queue.ts +++ b/packages/cli/src/Queue.ts @@ -5,6 +5,7 @@ import type { IExecuteResponsePromiseData } from 'n8n-workflow'; import config from '@/config'; import { ActiveExecutions } from '@/ActiveExecutions'; import * as WebhookHelpers from '@/WebhookHelpers'; +import { parseRedisUrl } from './ParserHelper'; export type JobId = Bull.JobId; export type Job = Bull.Job; @@ -32,7 +33,11 @@ export class Queue { async init() { const prefix = config.getEnv('queue.bull.prefix'); - const redisOptions: RedisOptions = config.getEnv('queue.bull.redis'); + const redisOptions: RedisOptions = parseRedisUrl() ||config.getEnv('queue.bull.redis'); + if ( redisOptions.host !== 'localhost' && '127.0.0.1' ) { + // If redis is in localhost mode then there is no need to configre any ssl options + redisOptions['tls'] = { rejectUnauthorized: false } + } // eslint-disable-next-line @typescript-eslint/naming-convention const { default: Bull } = await import('bull'); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index da4535ba4cafa..e936eb26921a8 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -304,13 +304,13 @@ export class Server extends AbstractServer { external: process.env.NODE_FUNCTION_ALLOW_EXTERNAL?.split(',') ?? undefined, }, enterprise: { - sharing: false, - ldap: false, - saml: false, - logStreaming: false, - advancedExecutionFilters: false, - variables: false, - versionControl: false, + sharing: true, + ldap: true, + saml: true, + logStreaming: true, + advancedExecutionFilters: true, + variables: true, + versionControl: true, }, hideUsagePage: config.getEnv('hideUsagePage'), license: { diff --git a/packages/cli/src/UserManagement/UserManagementHelper.ts b/packages/cli/src/UserManagement/UserManagementHelper.ts index e299544840759..b00e8155f9a1f 100644 --- a/packages/cli/src/UserManagement/UserManagementHelper.ts +++ b/packages/cli/src/UserManagement/UserManagementHelper.ts @@ -54,8 +54,7 @@ export function isUserManagementEnabled(): boolean { } export function isSharingEnabled(): boolean { - const license = Container.get(License); - return isUserManagementEnabled() && license.isSharingEnabled(); + return true; } export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise { diff --git a/packages/cli/src/config/schema.ts b/packages/cli/src/config/schema.ts index 402f7353a760a..91304a505f152 100644 --- a/packages/cli/src/config/schema.ts +++ b/packages/cli/src/config/schema.ts @@ -47,7 +47,7 @@ export const schema = { enabled: { doc: 'Typeorm logging enabled flag.', format: Boolean, - default: false, + default: true, env: 'DB_LOGGING_ENABLED', }, options: { @@ -97,7 +97,7 @@ export const schema = { schema: { doc: 'PostgresDB Schema', format: String, - default: 'public', + default: 'n8n', env: 'DB_POSTGRESDB_SCHEMA', }, @@ -123,7 +123,7 @@ export const schema = { rejectUnauthorized: { doc: 'If unauthorized SSL connections should be rejected', format: 'Boolean', - default: true, + default: false, env: 'DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED', }, }, @@ -1056,7 +1056,7 @@ export const schema = { enabled: { doc: 'Whether diagnostic mode is enabled.', format: Boolean, - default: true, + default: false, env: 'N8N_DIAGNOSTICS_ENABLED', }, config: { diff --git a/packages/cli/src/databases/config.ts b/packages/cli/src/databases/config.ts index b496ea5bcf6f0..250c6e7924744 100644 --- a/packages/cli/src/databases/config.ts +++ b/packages/cli/src/databases/config.ts @@ -10,6 +10,8 @@ import { postgresMigrations } from './migrations/postgresdb'; import { sqliteMigrations } from './migrations/sqlite'; import type { DatabaseType } from '@db/types'; import config from '@/config'; +import { parsePostgresUrl } from '@/ParserHelper'; +import { LoggerProxy as Logger } from 'n8n-workflow'; const entitiesDir = path.resolve(__dirname, 'entities'); @@ -17,7 +19,13 @@ const getDBConnectionOptions = (dbType: DatabaseType) => { const entityPrefix = config.getEnv('database.tablePrefix'); const migrationsDir = path.resolve(__dirname, 'migrations', dbType); const configDBType = dbType === 'mariadb' ? 'mysqldb' : dbType; - const connectionDetails = + let connectionDetails; + + if (dbType == 'postgresdb') { + connectionDetails = parsePostgresUrl(); + } + if (!connectionDetails) { + connectionDetails = configDBType === 'sqlite' ? { database: path.resolve( @@ -32,6 +40,10 @@ const getDBConnectionOptions = (dbType: DatabaseType) => { host: config.getEnv(`database.${configDBType}.host`), port: config.getEnv(`database.${configDBType}.port`), }; + } else { + Logger.debug(`Postgres is configured globally to: host: ${connectionDetails.host}, port: ${connectionDetails.port}, db: ${connectionDetails.database}`); + } + return { entityPrefix, entities: Object.values(entities), @@ -41,13 +53,22 @@ const getDBConnectionOptions = (dbType: DatabaseType) => { }; }; -export const getOptionOverrides = (dbType: 'postgresdb' | 'mysqldb') => ({ - database: config.getEnv(`database.${dbType}.database`), - host: config.getEnv(`database.${dbType}.host`), - port: config.getEnv(`database.${dbType}.port`), - username: config.getEnv(`database.${dbType}.user`), - password: config.getEnv(`database.${dbType}.password`), -}); +export const getOptionOverrides = (dbType: 'postgresdb' | 'mysqldb') => { + let connectionDetails; + if (dbType == 'postgresdb') { + connectionDetails = parsePostgresUrl(); + } + if (!connectionDetails) { + connectionDetails = { + database: config.getEnv(`database.${dbType}.database`), + host: config.getEnv(`database.${dbType}.host`), + port: config.getEnv(`database.${dbType}.port`), + username: config.getEnv(`database.${dbType}.user`), + password: config.getEnv(`database.${dbType}.password`), + } + } + return connectionDetails; +} export const getSqliteConnectionOptions = (): SqliteConnectionOptions => ({ type: 'sqlite', diff --git a/packages/editor-ui/src/__tests__/server/endpoints/settings.ts b/packages/editor-ui/src/__tests__/server/endpoints/settings.ts index 0ffed3e14a6b1..91cad386cc44d 100644 --- a/packages/editor-ui/src/__tests__/server/endpoints/settings.ts +++ b/packages/editor-ui/src/__tests__/server/endpoints/settings.ts @@ -10,13 +10,13 @@ const defaultSettings: IN8nUISettings = { endpointWebhook: '', endpointWebhookTest: '', enterprise: { - sharing: false, - ldap: false, - saml: false, - logStreaming: false, - advancedExecutionFilters: false, + sharing: true, + ldap: true, + saml: true, + logStreaming: true, + advancedExecutionFilters: true, variables: true, - versionControl: false, + versionControl: true, }, executionMode: 'regular', executionTimeout: 0, @@ -44,8 +44,8 @@ const defaultSettings: IN8nUISettings = { saveDataSuccessExecution: 'DEFAULT', saveManualExecutions: false, sso: { - ldap: { loginEnabled: false, loginLabel: '' }, - saml: { loginEnabled: false, loginLabel: '' }, + ldap: { loginEnabled: true, loginLabel: '' }, + saml: { loginEnabled: true, loginLabel: '' }, }, telemetry: { enabled: false, diff --git a/packages/editor-ui/src/__tests__/utils.ts b/packages/editor-ui/src/__tests__/utils.ts index 9fa734192de18..4e59ae160aacb 100644 --- a/packages/editor-ui/src/__tests__/utils.ts +++ b/packages/editor-ui/src/__tests__/utils.ts @@ -38,13 +38,13 @@ export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = { endpointWebhook: '', endpointWebhookTest: '', enterprise: { - advancedExecutionFilters: false, - sharing: false, - ldap: false, - saml: false, - logStreaming: false, - variables: false, - versionControl: false, + advancedExecutionFilters: true, + sharing: true, + ldap: true, + saml: true, + logStreaming: true, + variables: true, + versionControl: true, }, executionMode: 'regular', executionTimeout: 0, @@ -72,8 +72,8 @@ export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = { saveDataSuccessExecution: 'all', saveManualExecutions: false, sso: { - ldap: { loginEnabled: false, loginLabel: '' }, - saml: { loginEnabled: false, loginLabel: '' }, + ldap: { loginEnabled: true, loginLabel: '' }, + saml: { loginEnabled: true, loginLabel: '' }, }, telemetry: { enabled: false }, templates: { enabled: false, host: '' }, @@ -123,11 +123,11 @@ export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = { }, ldap: { loginLabel: '', - loginEnabled: false, + loginEnabled: true, }, saml: { loginLabel: '', - loginEnabled: false, + loginEnabled: true, }, onboardingCallPromptEnabled: false, saveDataErrorExecution: 'all', diff --git a/packages/editor-ui/src/stores/settings.store.ts b/packages/editor-ui/src/stores/settings.store.ts index e36a1af7be8ab..42dff1de93956 100644 --- a/packages/editor-ui/src/stores/settings.store.ts +++ b/packages/editor-ui/src/stores/settings.store.ts @@ -54,11 +54,11 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { }, ldap: { loginLabel: '', - loginEnabled: false, + loginEnabled: true, }, saml: { loginLabel: '', - loginEnabled: false, + loginEnabled: true, }, onboardingCallPromptEnabled: false, saveDataErrorExecution: 'all', @@ -67,7 +67,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { }), getters: { isEnterpriseFeatureEnabled() { - return (feature: EnterpriseEditionFeature): boolean => this.settings.enterprise[feature]; + return (feature: EnterpriseEditionFeature): boolean => true; }, versionCli(): string { return this.settings.versionCli;