mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
🔀 Merge pull request #68 from ccakes:jwt-auth
This commit is contained in:
parent
6908afa677
commit
92669bb77a
|
@ -141,8 +141,27 @@ const config = convict({
|
||||||
env: 'N8N_BASIC_AUTH_PASSWORD',
|
env: 'N8N_BASIC_AUTH_PASSWORD',
|
||||||
doc: 'The password of the basic auth user'
|
doc: 'The password of the basic auth user'
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
jwtAuth: {
|
||||||
|
active: {
|
||||||
|
format: 'Boolean',
|
||||||
|
default: false,
|
||||||
|
env: 'N8N_JWT_AUTH_ACTIVE',
|
||||||
|
doc: 'If JWT auth should be activated for editor and REST-API'
|
||||||
|
},
|
||||||
|
jwtHeader: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWT_AUTH_HEADER',
|
||||||
|
doc: 'The request header containing a signed JWT'
|
||||||
|
},
|
||||||
|
jwksUri: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWKS_URI',
|
||||||
|
doc: 'The URI to fetch JWK Set for JWT auh'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
endpoints: {
|
endpoints: {
|
||||||
|
|
|
@ -1,117 +1,120 @@
|
||||||
{
|
{
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.24.0",
|
"version": "0.24.0",
|
||||||
"description": "n8n Workflow Automation Tool",
|
"description": "n8n Workflow Automation Tool",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Jan Oberhauser",
|
"name": "Jan Oberhauser",
|
||||||
"email": "jan@n8n.io"
|
"email": "jan@n8n.io"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||||
},
|
},
|
||||||
"main": "dist/index",
|
"main": "dist/index",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
"oclif": {
|
"oclif": {
|
||||||
"commands": "./dist/commands",
|
"commands": "./dist/commands",
|
||||||
"bin": "n8n"
|
"bin": "n8n"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "nodemon",
|
"dev": "nodemon",
|
||||||
"postpack": "rm -f oclif.manifest.json",
|
"postpack": "rm -f oclif.manifest.json",
|
||||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||||
"start": "run-script-os",
|
"start": "run-script-os",
|
||||||
"start:default": "cd bin && ./n8n",
|
"start:default": "cd bin && ./n8n",
|
||||||
"start:windows": "cd bin && n8n",
|
"start:windows": "cd bin && n8n",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||||
"watch": "tsc --watch"
|
"watch": "tsc --watch"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"n8n": "./bin/n8n"
|
"n8n": "./bin/n8n"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"automate",
|
"automate",
|
||||||
"automation",
|
"automation",
|
||||||
"IaaS",
|
"IaaS",
|
||||||
"iPaaS",
|
"iPaaS",
|
||||||
"n8n",
|
"n8n",
|
||||||
"workflow"
|
"workflow"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"dist",
|
"dist",
|
||||||
"oclif.manifest.json"
|
"oclif.manifest.json"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@oclif/dev-cli": "^1.22.2",
|
"@oclif/dev-cli": "^1.22.2",
|
||||||
"@types/basic-auth": "^1.1.2",
|
"@types/basic-auth": "^1.1.2",
|
||||||
"@types/compression": "0.0.36",
|
"@types/compression": "0.0.36",
|
||||||
"@types/connect-history-api-fallback": "^1.3.1",
|
"@types/connect-history-api-fallback": "^1.3.1",
|
||||||
"@types/convict": "^4.2.1",
|
"@types/convict": "^4.2.1",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.16.1",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^24.0.18",
|
||||||
"@types/localtunnel": "^1.9.0",
|
"@types/localtunnel": "^1.9.0",
|
||||||
"@types/node": "^10.10.1",
|
"@types/node": "^10.10.1",
|
||||||
"@types/open": "^6.1.0",
|
"@types/open": "^6.1.0",
|
||||||
"@types/parseurl": "^1.3.1",
|
"@types/parseurl": "^1.3.1",
|
||||||
"@types/request-promise-native": "^1.0.15",
|
"@types/request-promise-native": "^1.0.15",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"nodemon": "^1.19.1",
|
"nodemon": "^1.19.1",
|
||||||
"run-script-os": "^1.0.7",
|
"run-script-os": "^1.0.7",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"tslint": "^5.17.0",
|
"tslint": "^5.17.0",
|
||||||
"typescript": "~3.5.2"
|
"typescript": "~3.5.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oclif/command": "^1.5.18",
|
"@types/jsonwebtoken": "^8.3.4",
|
||||||
"@oclif/errors": "^1.2.2",
|
"@oclif/command": "^1.5.18",
|
||||||
"basic-auth": "^2.0.1",
|
"@oclif/errors": "^1.2.2",
|
||||||
"body-parser": "^1.18.3",
|
"basic-auth": "^2.0.1",
|
||||||
"compression": "^1.7.4",
|
"body-parser": "^1.18.3",
|
||||||
"connect-history-api-fallback": "^1.6.0",
|
"compression": "^1.7.4",
|
||||||
"convict": "^5.0.0",
|
"connect-history-api-fallback": "^1.6.0",
|
||||||
"dotenv": "^8.0.0",
|
"convict": "^5.0.0",
|
||||||
"express": "^4.16.4",
|
"dotenv": "^8.0.0",
|
||||||
"flatted": "^2.0.0",
|
"express": "^4.16.4",
|
||||||
"glob-promise": "^3.4.0",
|
"flatted": "^2.0.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"glob-promise": "^3.4.0",
|
||||||
"inquirer": "^6.5.1",
|
"google-timezones-json": "^1.0.2",
|
||||||
"localtunnel": "^1.9.1",
|
"inquirer": "^6.5.1",
|
||||||
"mongodb": "^3.2.3",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"n8n-core": "~0.11.0",
|
"jwks-rsa": "^1.6.0",
|
||||||
"n8n-editor-ui": "~0.20.0",
|
"localtunnel": "^1.9.1",
|
||||||
"n8n-nodes-base": "~0.19.0",
|
"mongodb": "^3.2.3",
|
||||||
"n8n-workflow": "~0.12.0",
|
"n8n-core": "~0.11.0",
|
||||||
"open": "^6.1.0",
|
"n8n-editor-ui": "~0.20.0",
|
||||||
"pg": "^7.11.0",
|
"n8n-nodes-base": "~0.19.0",
|
||||||
"request-promise-native": "^1.0.7",
|
"n8n-workflow": "~0.12.0",
|
||||||
"sqlite3": "^4.0.6",
|
"open": "^6.1.0",
|
||||||
"sse-channel": "^3.1.1",
|
"pg": "^7.11.0",
|
||||||
"typeorm": "^0.2.16"
|
"request-promise-native": "^1.0.7",
|
||||||
},
|
"sqlite3": "^4.0.6",
|
||||||
"jest": {
|
"sse-channel": "^3.1.1",
|
||||||
"transform": {
|
"typeorm": "^0.2.16"
|
||||||
"^.+\\.tsx?$": "ts-jest"
|
},
|
||||||
},
|
"jest": {
|
||||||
"testURL": "http://localhost/",
|
"transform": {
|
||||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
"^.+\\.tsx?$": "ts-jest"
|
||||||
"testPathIgnorePatterns": [
|
},
|
||||||
"/dist/",
|
"testURL": "http://localhost/",
|
||||||
"/node_modules/"
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||||
],
|
"testPathIgnorePatterns": [
|
||||||
"moduleFileExtensions": [
|
"/dist/",
|
||||||
"ts",
|
"/node_modules/"
|
||||||
"tsx",
|
],
|
||||||
"js",
|
"moduleFileExtensions": [
|
||||||
"json"
|
"ts",
|
||||||
]
|
"tsx",
|
||||||
}
|
"js",
|
||||||
|
"json"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,11 @@ export function basicAuthAuthorizationError(resp: Response, realm: string, messa
|
||||||
resp.end(message);
|
resp.end(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function jwtAuthAuthorizationError(resp: Response, message?: string) {
|
||||||
|
resp.statusCode = 403;
|
||||||
|
resp.end(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function sendSuccessResponse(res: Response, data: any, raw?: boolean, responseCode?: number) { // tslint:disable-line:no-any
|
export function sendSuccessResponse(res: Response, data: any, raw?: boolean, responseCode?: number) { // tslint:disable-line:no-any
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
|
|
@ -74,6 +74,8 @@ import {
|
||||||
import * as basicAuth from 'basic-auth';
|
import * as basicAuth from 'basic-auth';
|
||||||
import * as compression from 'compression';
|
import * as compression from 'compression';
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
|
import * as jwt from 'jsonwebtoken';
|
||||||
|
import * as jwks from 'jwks-rsa';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as timezones from 'google-timezones-json';
|
import * as timezones from 'google-timezones-json';
|
||||||
import * as parseUrl from 'parseurl';
|
import * as parseUrl from 'parseurl';
|
||||||
|
@ -126,6 +128,7 @@ class App {
|
||||||
async config(): Promise<void> {
|
async config(): Promise<void> {
|
||||||
|
|
||||||
this.versions = await GenericHelpers.getVersions();
|
this.versions = await GenericHelpers.getVersions();
|
||||||
|
const authIgnoreRegex = new RegExp(`^\/(rest|healthz|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`);
|
||||||
|
|
||||||
// Check for basic auth credentials if activated
|
// Check for basic auth credentials if activated
|
||||||
const basicAuthActive = config.get('security.basicAuth.active') as boolean;
|
const basicAuthActive = config.get('security.basicAuth.active') as boolean;
|
||||||
|
@ -140,7 +143,6 @@ class App {
|
||||||
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const authIgnoreRegex = new RegExp(`^\/(rest|healthz|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`);
|
|
||||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (req.url.match(authIgnoreRegex)) {
|
if (req.url.match(authIgnoreRegex)) {
|
||||||
return next();
|
return next();
|
||||||
|
@ -162,6 +164,47 @@ class App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for and validate JWT if configured
|
||||||
|
const jwtAuthActive = config.get('security.jwtAuth.active') as boolean;
|
||||||
|
if (jwtAuthActive === true) {
|
||||||
|
const jwtAuthHeader = await GenericHelpers.getConfigValue('security.jwtAuth.jwtHeader') as string;
|
||||||
|
if (jwtAuthHeader === '') {
|
||||||
|
throw new Error('JWT auth is activated but no request header was defined. Please set one!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwksUri = await GenericHelpers.getConfigValue('security.jwtAuth.jwksUri') as string;
|
||||||
|
if (jwksUri === '') {
|
||||||
|
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
if (req.url.match(authIgnoreRegex)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = req.header(jwtAuthHeader) as string;
|
||||||
|
if (token === '') {
|
||||||
|
return ResponseHelper.jwtAuthAuthorizationError(res, "Missing token");
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwkClient = jwks({ cache: true, jwksUri });
|
||||||
|
function getKey(header: any, callback: Function) {
|
||||||
|
jwkClient.getSigningKey(header.kid, (err: Error, key: any) => {
|
||||||
|
if (err) throw ResponseHelper.jwtAuthAuthorizationError(res, err.message);
|
||||||
|
|
||||||
|
const signingKey = key.publicKey || key.rsaPublicKey;
|
||||||
|
callback(null, signingKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt.verify(token, getKey, {}, (err: Error, decoded: string) => {
|
||||||
|
if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token");
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get push connections
|
// Get push connections
|
||||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (req.url.indexOf('/rest/push') === 0) {
|
if (req.url.indexOf('/rest/push') === 0) {
|
||||||
|
|
Loading…
Reference in a new issue