diff --git a/.gitignore b/.gitignore
index b3eac3920..0441d445b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ _START_PACKAGE
.env
.vscode
.idea
+.prettierrc.js
diff --git a/docker/compose/subfolderWithSSL/.env b/docker/compose/subfolderWithSSL/.env
new file mode 100644
index 000000000..7008bd631
--- /dev/null
+++ b/docker/compose/subfolderWithSSL/.env
@@ -0,0 +1,25 @@
+# Folder where data should be saved
+DATA_FOLDER=/root/n8n/
+
+# The top level domain to serve from
+DOMAIN_NAME=example.com
+
+# The subfolder to serve from
+SUBFOLDER=app1
+N8N_PATH=/app1/
+
+# DOMAIN_NAME and SUBDOMAIN combined decide where n8n will be reachable from
+# above example would result in: https://example.com/n8n/
+
+# The user name to use for autentication - IMPORTANT ALWAYS CHANGE!
+N8N_BASIC_AUTH_USER=user
+
+# The password to use for autentication - IMPORTANT ALWAYS CHANGE!
+N8N_BASIC_AUTH_PASSWORD=password
+
+# Optional timezone to set which gets used by Cron-Node by default
+# If not set New York time will be used
+GENERIC_TIMEZONE=Europe/Berlin
+
+# The email address to use for the SSL certificate creation
+SSL_EMAIL=user@example.com
diff --git a/docker/compose/subfolderWithSSL/README.md b/docker/compose/subfolderWithSSL/README.md
new file mode 100644
index 000000000..61fcb5b7e
--- /dev/null
+++ b/docker/compose/subfolderWithSSL/README.md
@@ -0,0 +1,26 @@
+# n8n on Subfolder with SSL
+
+Starts n8n and deployes it on a subfolder
+
+
+## Start
+
+To start n8n in a subfolder simply start docker-compose by executing the following
+command in the current folder.
+
+
+**IMPORTANT:** But before you do that change the default users and passwords in the `.env` file!
+
+```
+docker-compose up -d
+```
+
+To stop it execute:
+
+```
+docker-compose stop
+```
+
+## Configuration
+
+The default name of the database, user and password for MongoDB can be changed in the `.env` file in the current directory.
diff --git a/docker/compose/subfolderWithSSL/docker-compose.yml b/docker/compose/subfolderWithSSL/docker-compose.yml
new file mode 100644
index 000000000..5e540abbb
--- /dev/null
+++ b/docker/compose/subfolderWithSSL/docker-compose.yml
@@ -0,0 +1,57 @@
+version: "3"
+
+services:
+ traefik:
+ image: "traefik"
+ command:
+ - "--api=true"
+ - "--api.insecure=true"
+ - "--api.dashboard=true"
+ - "--providers.docker=true"
+ - "--providers.docker.exposedbydefault=false"
+ - "--entrypoints.websecure.address=:443"
+ - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
+ - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
+ - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
+ - /home/jan/www/n8n/n8n:/data
+ ports:
+ - "443:443"
+ - "80:80"
+ volumes:
+ - ${DATA_FOLDER}/letsencrypt:/letsencrypt
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ n8n:
+ image: n8nio/n8n
+ ports:
+ - "127.0.0.1:5678:5678"
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.n8n.rule=Host(`${DOMAIN_NAME}`)
+ - traefik.http.routers.n8n.tls=true
+ - traefik.http.routers.n8n.entrypoints=websecure
+ - "traefik.http.routers.n8n.rule=PathPrefix(`/${SUBFOLDER}{regex:$$|/.*}`)"
+ - "traefik.http.middlewares.n8n-stripprefix.stripprefix.prefixes=/${SUBFOLDER}"
+ - "traefik.http.routers.n8n.middlewares=n8n-stripprefix"
+ - traefik.http.routers.n8n.tls.certresolver=mytlschallenge
+ - traefik.http.middlewares.n8n.headers.SSLRedirect=true
+ - traefik.http.middlewares.n8n.headers.STSSeconds=315360000
+ - traefik.http.middlewares.n8n.headers.browserXSSFilter=true
+ - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
+ - traefik.http.middlewares.n8n.headers.forceSTSHeader=true
+ - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME}
+ - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
+ - traefik.http.middlewares.n8n.headers.STSPreload=true
+ environment:
+ - N8N_BASIC_AUTH_ACTIVE=true
+ - N8N_BASIC_AUTH_USER
+ - N8N_BASIC_AUTH_PASSWORD
+ - N8N_HOST=${DOMAIN_NAME}
+ - N8N_PORT=5678
+ - N8N_PROTOCOL=https
+ - NODE_ENV=production
+ - N8N_PATH
+ - WEBHOOK_TUNNEL_URL=http://${DOMAIN_NAME}${N8N_PATH}
+ - VUE_APP_URL_BASE_API=http://${DOMAIN_NAME}${N8N_PATH}
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ${DATA_FOLDER}/.n8n:/root/.n8n
diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md
index 7170dda59..977c53fef 100644
--- a/docker/images/n8n/README.md
+++ b/docker/images/n8n/README.md
@@ -6,25 +6,23 @@ n8n is a free and open [fair-code](http://faircode.io) licensed node based Workf
-
## Contents
-- [Demo](#demo)
-- [Available integrations](#available-integrations)
-- [Documentation](#documentation)
-- [Start n8n in Docker](#start-n8n-in-docker)
-- [Start with tunnel](#start-with-tunnel)
-- [Securing n8n](#securing-n8n)
-- [Persist data](#persist-data)
-- [Passing Sensitive Data via File](#passing-sensitive-data-via-file)
-- [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance)
-- [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt)
-- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
-- [Support](#support)
-- [Jobs](#jobs)
-- [Upgrading](#upgrading)
-- [License](#license)
-
+ - [Demo](#demo)
+ - [Available integrations](#available-integrations)
+ - [Documentation](#documentation)
+ - [Start n8n in Docker](#start-n8n-in-docker)
+ - [Start with tunnel](#start-with-tunnel)
+ - [Securing n8n](#securing-n8n)
+ - [Persist data](#persist-data)
+ - [Passing Sensitive Data via File](#passing-sensitive-data-via-file)
+ - [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance)
+ - [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt)
+ - [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
+ - [Support](#support)
+ - [Jobs](#jobs)
+ - [Upgrading](#upgrading)
+ - [License](#license)
## Demo
@@ -49,9 +47,9 @@ Additional information and example workflows on the n8n.io website: [https://n8n
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
- n8nio/n8n
+ --name n8n \
+ -p 5678:5678 \
+ n8nio/n8n
```
You can then access n8n by opening:
@@ -71,14 +69,13 @@ To use it simply start n8n with `--tunnel`
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
- -v ~/.n8n:/root/.n8n \
- n8nio/n8n \
- n8n start --tunnel
+ --name n8n \
+ -p 5678:5678 \
+ -v ~/.n8n:/root/.n8n \
+ n8nio/n8n \
+ n8n start --tunnel
```
-
## Securing n8n
By default n8n can be accessed by everybody. This is OK if you have it only running
@@ -93,7 +90,6 @@ N8N_BASIC_AUTH_USER=
N8N_BASIC_AUTH_PASSWORD=
```
-
## Persist data
The workflow data gets by default saved in an SQLite database in the user
@@ -102,10 +98,10 @@ settings like webhook URL and encryption key.
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
- -v ~/.n8n:/root/.n8n \
- n8nio/n8n
+ --name n8n \
+ -p 5678:5678 \
+ -v ~/.n8n:/root/.n8n \
+ n8nio/n8n
```
### Start with other Database
@@ -121,7 +117,6 @@ for the credentials. If none gets found n8n creates automatically one on
startup. In case credentials are already saved with a different encryption key
it can not be used anymore as encrypting it is not possible anymore.
-
#### Use with MongoDB
> **WARNING**: Use Postgres if possible! Mongo has problems with saving large
@@ -129,40 +124,39 @@ it can not be used anymore as encrypting it is not possible anymore.
> may be dropped in the future.
Replace the following placeholders with the actual data:
- -
- -
- -
- -
- -
+ - MONGO_DATABASE
+ - MONGO_HOST
+ - MONGO_PORT
+ - MONGO_USER
+ - MONGO_PASSWORD
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
+ --name n8n \
+ -p 5678:5678 \
-e DB_TYPE=mongodb \
-e DB_MONGODB_CONNECTION_URL="mongodb://:@:/" \
- -v ~/.n8n:/root/.n8n \
- n8nio/n8n \
- n8n start
+ -v ~/.n8n:/root/.n8n \
+ n8nio/n8n \
+ n8n start
```
A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withMongo/README.md)
-
#### Use with PostgresDB
Replace the following placeholders with the actual data:
- -
- -
- -
- -
- -
- -
+ - POSTGRES_DATABASE
+ - POSTGRES_HOST
+ - POSTGRES_PASSWORD
+ - POSTGRES_PORT
+ - POSTGRES_USER
+ - POSTGRES_SCHEMA
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
+ --name n8n \
+ -p 5678:5678 \
-e DB_TYPE=postgresdb \
-e DB_POSTGRESDB_DATABASE= \
-e DB_POSTGRESDB_HOST= \
@@ -170,39 +164,37 @@ docker run -it --rm \
-e DB_POSTGRESDB_USER= \
-e DB_POSTGRESDB_SCHEMA= \
-e DB_POSTGRESDB_PASSWORD= \
- -v ~/.n8n:/root/.n8n \
- n8nio/n8n \
- n8n start
+ -v ~/.n8n:/root/.n8n \
+ n8nio/n8n \
+ n8n start
```
A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withPostgres/README.md)
-
#### Use with MySQL
Replace the following placeholders with the actual data:
- -
- -
- -
- -
- -
+ - MYSQLDB_DATABASE
+ - MYSQLDB_HOST
+ - MYSQLDB_PASSWORD
+ - MYSQLDB_PORT
+ - MYSQLDB_USER
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
+ --name n8n \
+ -p 5678:5678 \
-e DB_TYPE=mysqldb \
-e DB_MYSQLDB_DATABASE= \
-e DB_MYSQLDB_HOST= \
-e DB_MYSQLDB_PORT= \
-e DB_MYSQLDB_USER= \
-e DB_MYSQLDB_PASSWORD= \
- -v ~/.n8n:/root/.n8n \
- n8nio/n8n \
- n8n start
+ -v ~/.n8n:/root/.n8n \
+ n8nio/n8n \
+ n8n start
```
-
## Passing Sensitive Data via File
To avoid passing sensitive information via environment variables "_FILE" may be
@@ -211,16 +203,15 @@ with the given name. That makes it possible to load data easily from
Docker- and Kubernetes-Secrets.
The following environment variables support file input:
- - DB_MONGODB_CONNECTION_URL_FILE
- - DB_POSTGRESDB_DATABASE_FILE
- - DB_POSTGRESDB_HOST_FILE
- - DB_POSTGRESDB_PASSWORD_FILE
- - DB_POSTGRESDB_PORT_FILE
- - DB_POSTGRESDB_USER_FILE
- - DB_POSTGRESDB_SCHEMA_FILE
- - N8N_BASIC_AUTH_PASSWORD_FILE
- - N8N_BASIC_AUTH_USER_FILE
-
+ - DB_MONGODB_CONNECTION_URL_FILE
+ - DB_POSTGRESDB_DATABASE_FILE
+ - DB_POSTGRESDB_HOST_FILE
+ - DB_POSTGRESDB_PASSWORD_FILE
+ - DB_POSTGRESDB_PORT_FILE
+ - DB_POSTGRESDB_USER_FILE
+ - DB_POSTGRESDB_SCHEMA_FILE
+ - N8N_BASIC_AUTH_PASSWORD_FILE
+ - N8N_BASIC_AUTH_USER_FILE
## Example Setup with Lets Encrypt
@@ -235,7 +226,7 @@ docker pull n8nio/n8n
# Stop current setup
sudo docker-compose stop
# Delete it (will only delete the docker-containers, data is stored separately)
-sudo docker-compose rm
+sudo docker-compose rm
# Then start it again
sudo docker-compose up -d
```
@@ -251,11 +242,11 @@ the environment variable `TZ`.
Example to use the same timezone for both:
```
docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
+ --name n8n \
+ -p 5678:5678 \
-e GENERIC_TIMEZONE="Europe/Berlin" \
-e TZ="Europe/Berlin" \
- n8nio/n8n
+ n8nio/n8n
```
diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts
index 0022c08b7..239061746 100644
--- a/packages/cli/config/index.ts
+++ b/packages/cli/config/index.ts
@@ -225,6 +225,13 @@ const config = convict({
},
// How n8n can be reached (Editor & REST-API)
+ path: {
+ format: String,
+ default: '/',
+ arg: 'path',
+ env: 'N8N_PATH',
+ doc: 'Path n8n is deployed to'
+ },
host: {
format: String,
default: 'localhost',
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 0bda408ec..e9b16ad9e 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n",
- "version": "0.73.0",
+ "version": "0.73.1",
"description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -102,7 +102,7 @@
"mysql2": "^2.0.1",
"n8n-core": "~0.38.0",
"n8n-editor-ui": "~0.49.0",
- "n8n-nodes-base": "~0.68.0",
+ "n8n-nodes-base": "~0.68.1",
"n8n-workflow": "~0.34.0",
"oauth-1.0a": "^2.2.6",
"open": "^7.0.0",
diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts
index 8b02b73e8..cab67f7bc 100644
--- a/packages/cli/src/GenericHelpers.ts
+++ b/packages/cli/src/GenericHelpers.ts
@@ -40,11 +40,12 @@ export function getBaseUrl(): string {
const protocol = config.get('protocol') as string;
const host = config.get('host') as string;
const port = config.get('port') as number;
+ const path = config.get('path') as string;
if (protocol === 'http' && port === 80 || protocol === 'https' && port === 443) {
- return `${protocol}://${host}/`;
+ return `${protocol}://${host}${path}`;
}
- return `${protocol}://${host}:${port}/`;
+ return `${protocol}://${host}:${port}${path}`;
}
diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts
index 2507b6b13..1a0b75842 100644
--- a/packages/cli/src/Server.ts
+++ b/packages/cli/src/Server.ts
@@ -931,7 +931,8 @@ class App {
// Authorize OAuth Data
this.app.get(`/${this.restEndpoint}/oauth1-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => {
if (req.query.id === undefined) {
- throw new Error('Required credential id is missing!');
+ res.status(500).send('Required credential id is missing!');
+ return '';
}
const result = await Db.collections.Credentials!.findOne(req.query.id as string);
@@ -943,7 +944,8 @@ class App {
let encryptionKey = undefined;
encryptionKey = await UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
- throw new Error('No encryption key got found to decrypt the credentials!');
+ res.status(500).send('No encryption key got found to decrypt the credentials!');
+ return '';
}
// Decrypt the currently saved credentials
@@ -1015,7 +1017,8 @@ class App {
const { oauth_verifier, oauth_token, cid } = req.query;
if (oauth_verifier === undefined || oauth_token === undefined) {
- throw new Error('Insufficient parameters for OAuth1 callback');
+ const errorResponse = new ResponseHelper.ResponseError('Insufficient parameters for OAuth1 callback. Received following query parameters: ' + JSON.stringify(req.query), undefined, 503);
+ return ResponseHelper.sendErrorResponse(res, errorResponse);
}
const result = await Db.collections.Credentials!.findOne(cid as any); // tslint:disable-line:no-any
@@ -1085,7 +1088,8 @@ class App {
// Authorize OAuth Data
this.app.get(`/${this.restEndpoint}/oauth2-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => {
if (req.query.id === undefined) {
- throw new Error('Required credential id is missing!');
+ res.status(500).send('Required credential id is missing.');
+ return '';
}
const result = await Db.collections.Credentials!.findOne(req.query.id as string);
@@ -1097,7 +1101,8 @@ class App {
let encryptionKey = undefined;
encryptionKey = await UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
- throw new Error('No encryption key got found to decrypt the credentials!');
+ res.status(500).send('No encryption key got found to decrypt the credentials!');
+ return '';
}
// Decrypt the currently saved credentials
@@ -1161,7 +1166,8 @@ class App {
const {code, state: stateEncoded } = req.query;
if (code === undefined || stateEncoded === undefined) {
- throw new Error('Insufficient parameters for OAuth2 callback');
+ const errorResponse = new ResponseHelper.ResponseError('Insufficient parameters for OAuth2 callback. Received following query parameters: ' + JSON.stringify(req.query), undefined, 503);
+ return ResponseHelper.sendErrorResponse(res, errorResponse);
}
let state;
@@ -1211,17 +1217,20 @@ class App {
},
};
}
+ const redirectUri = `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`;
const oAuthObj = new clientOAuth2({
clientId: _.get(oauthCredentials, 'clientId') as string,
clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string,
accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string,
authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string,
- redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`,
+ redirectUri,
scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',')
});
- const oauthToken = await oAuthObj.code.getToken(req.originalUrl, options);
+ const queryParameters = req.originalUrl.split('?').splice(1, 1).join('');
+
+ const oauthToken = await oAuthObj.code.getToken(`${redirectUri}?${queryParameters}`, options);
if (oauthToken === undefined) {
const errorResponse = new ResponseHelper.ResponseError('Unable to get access tokens!', undefined, 404);
@@ -1693,9 +1702,21 @@ class App {
});
}
+
+ // Read the index file and replace the path placeholder
+ const editorUiPath = require.resolve('n8n-editor-ui');
+ const filePath = pathJoin(pathDirname(editorUiPath), 'dist', 'index.html');
+ let readIndexFile = readFileSync(filePath, 'utf8');
+ const n8nPath = config.get('path');
+ readIndexFile = readIndexFile.replace(/\/%BASE_PATH%\//g, n8nPath);
+
+ // Serve the altered index.html file separately
+ this.app.get(`/index.html`, async (req: express.Request, res: express.Response) => {
+ res.send(readIndexFile);
+ });
+
// Serve the website
const startTime = (new Date()).toUTCString();
- const editorUiPath = require.resolve('n8n-editor-ui');
this.app.use('/', express.static(pathJoin(pathDirname(editorUiPath), 'dist'), {
index: 'index.html',
setHeaders: (res, path) => {
diff --git a/packages/editor-ui/public/index.html b/packages/editor-ui/public/index.html
index 2f2450023..9193fda97 100644
--- a/packages/editor-ui/public/index.html
+++ b/packages/editor-ui/public/index.html
@@ -4,7 +4,8 @@
-
+
+
n8n.io - Workflow Automation
diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue
index 71388c2cd..a9cf787ef 100644
--- a/packages/editor-ui/src/components/MainSidebar.vue
+++ b/packages/editor-ui/src/components/MainSidebar.vue
@@ -16,7 +16,7 @@
-
+
n8n.io
@@ -208,6 +208,8 @@ export default mixins(
data () {
return {
aboutDialogVisible: false,
+ // @ts-ignore
+ basePath: window.BASE_PATH,
isCollapsed: true,
credentialNewDialogVisible: false,
credentialOpenDialogVisible: false,
diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue
index b3729e4f6..42fbaa5b1 100644
--- a/packages/editor-ui/src/components/RunData.vue
+++ b/packages/editor-ui/src/components/RunData.vue
@@ -19,7 +19,7 @@