mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
JWT extras (#817)
* JWT extras: check issues, expiration, namespace and tenant * idents
This commit is contained in:
parent
4596aaf5d5
commit
4c275513b4
|
@ -333,13 +333,43 @@ const config = convict({
|
||||||
env: 'N8N_JWT_AUTH_HEADER',
|
env: 'N8N_JWT_AUTH_HEADER',
|
||||||
doc: 'The request header containing a signed JWT'
|
doc: 'The request header containing a signed JWT'
|
||||||
},
|
},
|
||||||
|
jwtHeaderValuePrefix: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWT_AUTH_HEADER_VALUE_PREFIX',
|
||||||
|
doc: 'The request header value prefix to strip (optional)'
|
||||||
|
},
|
||||||
jwksUri: {
|
jwksUri: {
|
||||||
format: String,
|
format: String,
|
||||||
default: '',
|
default: '',
|
||||||
env: 'N8N_JWKS_URI',
|
env: 'N8N_JWKS_URI',
|
||||||
doc: 'The URI to fetch JWK Set for JWT auh'
|
doc: 'The URI to fetch JWK Set for JWT authentication'
|
||||||
},
|
},
|
||||||
}
|
jwtIssuer: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWT_ISSUER',
|
||||||
|
doc: 'JWT issuer to expect (optional)'
|
||||||
|
},
|
||||||
|
jwtNamespace: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWT_NAMESPACE',
|
||||||
|
doc: 'JWT namespace to expect (optional)'
|
||||||
|
},
|
||||||
|
jwtAllowedTenantKey: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWT_ALLOWED_TENANT_KEY',
|
||||||
|
doc: 'JWT tenant key name to inspect within JWT namespace (optional)'
|
||||||
|
},
|
||||||
|
jwtAllowedTenant: {
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'N8N_JWT_ALLOWED_TENANT',
|
||||||
|
doc: 'JWT tenant to allow (optional)'
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
endpoints: {
|
endpoints: {
|
||||||
|
|
|
@ -213,37 +213,64 @@ class App {
|
||||||
const jwtAuthHeader = await GenericHelpers.getConfigValue('security.jwtAuth.jwtHeader') as string;
|
const jwtAuthHeader = await GenericHelpers.getConfigValue('security.jwtAuth.jwtHeader') as string;
|
||||||
if (jwtAuthHeader === '') {
|
if (jwtAuthHeader === '') {
|
||||||
throw new Error('JWT auth is activated but no request header was defined. Please set one!');
|
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;
|
const jwksUri = await GenericHelpers.getConfigValue('security.jwtAuth.jwksUri') as string;
|
||||||
if (jwksUri === '') {
|
if (jwksUri === '') {
|
||||||
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
|
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
|
||||||
}
|
}
|
||||||
|
const jwtHeaderValuePrefix = await GenericHelpers.getConfigValue('security.jwtAuth.jwtHeaderValuePrefix') as string;
|
||||||
|
const jwtIssuer = await GenericHelpers.getConfigValue('security.jwtAuth.jwtIssuer') as string;
|
||||||
|
const jwtNamespace = await GenericHelpers.getConfigValue('security.jwtAuth.jwtNamespace') as string;
|
||||||
|
const jwtAllowedTenantKey = await GenericHelpers.getConfigValue('security.jwtAuth.jwtAllowedTenantKey') as string;
|
||||||
|
const jwtAllowedTenant = await GenericHelpers.getConfigValue('security.jwtAuth.jwtAllowedTenant') as string;
|
||||||
|
|
||||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
function isTenantAllowed(decodedToken: object): Boolean {
|
||||||
if (req.url.match(authIgnoreRegex)) {
|
if (jwtNamespace === '' || jwtAllowedTenantKey === '' || jwtAllowedTenant === '') return true;
|
||||||
return next();
|
else {
|
||||||
}
|
for (let [k, v] of Object.entries(decodedToken)) {
|
||||||
|
if (k === jwtNamespace) {
|
||||||
|
for (let [kn, kv] of Object.entries(v)) {
|
||||||
|
if (kn === jwtAllowedTenantKey && kv === jwtAllowedTenant) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const token = req.header(jwtAuthHeader) as string;
|
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (token === '') {
|
if (req.url.match(authIgnoreRegex)) {
|
||||||
return ResponseHelper.jwtAuthAuthorizationError(res, "Missing token");
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const jwkClient = jwks({ cache: true, jwksUri });
|
var token = req.header(jwtAuthHeader) as string;
|
||||||
function getKey(header: any, callback: Function) { // tslint:disable-line:no-any
|
if (token === undefined || token === '') {
|
||||||
jwkClient.getSigningKey(header.kid, (err: Error, key: any) => { // tslint:disable-line:no-any
|
return ResponseHelper.jwtAuthAuthorizationError(res, "Missing token");
|
||||||
if (err) throw ResponseHelper.jwtAuthAuthorizationError(res, err.message);
|
}
|
||||||
|
if (jwtHeaderValuePrefix != '' && token.startsWith(jwtHeaderValuePrefix)) {
|
||||||
|
token = token.replace(jwtHeaderValuePrefix + ' ', '').trimLeft();
|
||||||
|
}
|
||||||
|
|
||||||
const signingKey = key.publicKey || key.rsaPublicKey;
|
const jwkClient = jwks({ cache: true, jwksUri });
|
||||||
callback(null, signingKey);
|
function getKey(header: any, callback: Function) { // tslint:disable-line:no-any
|
||||||
});
|
jwkClient.getSigningKey(header.kid, (err: Error, key: any) => { // tslint:disable-line:no-any
|
||||||
}
|
if (err) throw ResponseHelper.jwtAuthAuthorizationError(res, err.message);
|
||||||
|
|
||||||
jwt.verify(token, getKey, {}, (err: jwt.VerifyErrors, decoded: object) => {
|
const signingKey = key.publicKey || key.rsaPublicKey;
|
||||||
if (err) return ResponseHelper.jwtAuthAuthorizationError(res, 'Invalid token');
|
callback(null, signingKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
var jwtVerifyOptions: jwt.VerifyOptions = {
|
||||||
|
issuer: jwtIssuer != '' ? jwtIssuer : undefined,
|
||||||
|
ignoreExpiration: false
|
||||||
|
}
|
||||||
|
jwt.verify(token, getKey, jwtVerifyOptions, (err: jwt.VerifyErrors, decoded: object) => {
|
||||||
|
if (err) ResponseHelper.jwtAuthAuthorizationError(res, 'Invalid token');
|
||||||
|
else if (!isTenantAllowed(decoded)) ResponseHelper.jwtAuthAuthorizationError(res, 'Tenant not allowed');
|
||||||
|
else next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue