refactor(core)!: Remove basic-auth, external-jwt-auth, and no-auth options (#6362)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
committed by
कारतोफ्फेलस्क्रिप्ट™
parent
a45a2c8c41
commit
8c008f5d22
@@ -10,7 +10,6 @@ import type { AuthenticatedRequest } from '@/requests';
|
||||
import config from '@/config';
|
||||
import { AUTH_COOKIE_NAME, EDITOR_UI_DIST_DIR } from '@/constants';
|
||||
import { issueCookie, resolveJwtContent } from '@/auth/jwt';
|
||||
import { isUserManagementEnabled } from '@/UserManagement/UserManagementHelper';
|
||||
import type { UserRepository } from '@db/repositories';
|
||||
import { canSkipAuth } from '@/decorators/registerController';
|
||||
|
||||
@@ -19,7 +18,7 @@ const jwtFromRequest = (req: Request) => {
|
||||
return (req.cookies?.[AUTH_COOKIE_NAME] as string | undefined) ?? null;
|
||||
};
|
||||
|
||||
const jwtAuth = (): RequestHandler => {
|
||||
const userManagementJwtAuth = (): RequestHandler => {
|
||||
const jwtStrategy = new Strategy(
|
||||
{
|
||||
jwtFromRequest,
|
||||
@@ -79,11 +78,10 @@ export const setupAuthMiddlewares = (
|
||||
app: Application,
|
||||
ignoredEndpoints: Readonly<string[]>,
|
||||
restEndpoint: string,
|
||||
userRepository: UserRepository,
|
||||
) => {
|
||||
// needed for testing; not adding overhead since it directly returns if req.cookies exists
|
||||
app.use(cookieParser());
|
||||
app.use(jwtAuth());
|
||||
app.use(userManagementJwtAuth());
|
||||
|
||||
app.use(async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (
|
||||
@@ -101,15 +99,6 @@ export const setupAuthMiddlewares = (
|
||||
return next();
|
||||
}
|
||||
|
||||
// skip authentication if user management is disabled
|
||||
if (!isUserManagementEnabled()) {
|
||||
req.user = await userRepository.findOneOrFail({
|
||||
relations: ['globalRole'],
|
||||
where: {},
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
return passportMiddleware(req, res, next);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import type { Application } from 'express';
|
||||
import basicAuth from 'basic-auth';
|
||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||
import { compare } from 'bcryptjs';
|
||||
import type { Config } from '@/config';
|
||||
import { basicAuthAuthorizationError } from '@/ResponseHelper';
|
||||
|
||||
export const setupBasicAuth = (app: Application, config: Config, authIgnoreRegex: RegExp) => {
|
||||
const basicAuthUser = config.getEnv('security.basicAuth.user');
|
||||
if (basicAuthUser === '') {
|
||||
throw new Error('Basic auth is activated but no user got defined. Please set one!');
|
||||
}
|
||||
|
||||
const basicAuthPassword = config.getEnv('security.basicAuth.password');
|
||||
if (basicAuthPassword === '') {
|
||||
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
||||
}
|
||||
|
||||
const basicAuthHashEnabled = config.getEnv('security.basicAuth.hash');
|
||||
|
||||
let validPassword: null | string = null;
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
// Skip basic auth for a few listed endpoints or when instance owner has been setup
|
||||
if (authIgnoreRegex.exec(req.url) || config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
||||
return next();
|
||||
}
|
||||
const realm = 'n8n - Editor UI';
|
||||
const basicAuthData = basicAuth(req);
|
||||
|
||||
if (basicAuthData === undefined) {
|
||||
// Authorization data is missing
|
||||
return basicAuthAuthorizationError(res, realm, 'Authorization is required!');
|
||||
}
|
||||
|
||||
if (basicAuthData.name === basicAuthUser) {
|
||||
if (basicAuthHashEnabled) {
|
||||
if (validPassword === null && (await compare(basicAuthData.pass, basicAuthPassword))) {
|
||||
// Password is valid so save for future requests
|
||||
validPassword = basicAuthData.pass;
|
||||
}
|
||||
|
||||
if (validPassword === basicAuthData.pass && validPassword !== null) {
|
||||
// Provided hash is correct
|
||||
return next();
|
||||
}
|
||||
} else if (basicAuthData.pass === basicAuthPassword) {
|
||||
// Provided password is correct
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
// Provided authentication data is wrong
|
||||
return basicAuthAuthorizationError(res, realm, 'Authorization data is wrong!');
|
||||
});
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
import type { Application } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import jwks from 'jwks-rsa';
|
||||
import type { Config } from '@/config';
|
||||
import { jwtAuthAuthorizationError } from '@/ResponseHelper';
|
||||
|
||||
export const setupExternalJWTAuth = (app: Application, config: Config, authIgnoreRegex: RegExp) => {
|
||||
const jwtAuthHeader = config.getEnv('security.jwtAuth.jwtHeader');
|
||||
if (jwtAuthHeader === '') {
|
||||
throw new Error('JWT auth is activated but no request header was defined. Please set one!');
|
||||
}
|
||||
|
||||
const jwksUri = config.getEnv('security.jwtAuth.jwksUri');
|
||||
if (jwksUri === '') {
|
||||
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
|
||||
}
|
||||
|
||||
const jwtHeaderValuePrefix = config.getEnv('security.jwtAuth.jwtHeaderValuePrefix');
|
||||
const jwtIssuer = config.getEnv('security.jwtAuth.jwtIssuer');
|
||||
const jwtNamespace = config.getEnv('security.jwtAuth.jwtNamespace');
|
||||
const jwtAllowedTenantKey = config.getEnv('security.jwtAuth.jwtAllowedTenantKey');
|
||||
const jwtAllowedTenant = config.getEnv('security.jwtAuth.jwtAllowedTenant');
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function isTenantAllowed(decodedToken: object): boolean {
|
||||
if (jwtNamespace === '' || jwtAllowedTenantKey === '' || jwtAllowedTenant === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries(decodedToken)) {
|
||||
if (k === jwtNamespace) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
for (const [kn, kv] of Object.entries(v)) {
|
||||
if (kn === jwtAllowedTenantKey && kv === jwtAllowedTenant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
app.use((req, res, next) => {
|
||||
if (authIgnoreRegex.exec(req.url)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
let token = req.header(jwtAuthHeader) as string;
|
||||
if (token === undefined || token === '') {
|
||||
return jwtAuthAuthorizationError(res, 'Missing token');
|
||||
}
|
||||
|
||||
if (jwtHeaderValuePrefix !== '' && token.startsWith(jwtHeaderValuePrefix)) {
|
||||
token = token.replace(`${jwtHeaderValuePrefix} `, '').trimStart();
|
||||
}
|
||||
|
||||
const jwkClient = jwks({ cache: true, jwksUri });
|
||||
const getKey: jwt.GetPublicKeyOrSecret = (header, callbackFn) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||
if (!header.kid) throw jwtAuthAuthorizationError(res, 'No JWT key found');
|
||||
jwkClient.getSigningKey(header.kid, (error, key) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||
if (error) throw jwtAuthAuthorizationError(res, error.message);
|
||||
callbackFn(null, key?.getPublicKey());
|
||||
});
|
||||
};
|
||||
|
||||
const jwtVerifyOptions: jwt.VerifyOptions = {
|
||||
issuer: jwtIssuer !== '' ? jwtIssuer : undefined,
|
||||
ignoreExpiration: false,
|
||||
};
|
||||
|
||||
jwt.verify(token, getKey, jwtVerifyOptions, (error: jwt.VerifyErrors, decoded: object) => {
|
||||
if (error) {
|
||||
jwtAuthAuthorizationError(res, 'Invalid token');
|
||||
} else if (!isTenantAllowed(decoded)) {
|
||||
jwtAuthAuthorizationError(res, 'Tenant not allowed');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { RequestHandler } from 'express';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import { isUserManagementEnabled } from '../UserManagement/UserManagementHelper';
|
||||
|
||||
export const userManagementEnabledMiddleware: RequestHandler = (req, res, next) => {
|
||||
if (isUserManagementEnabled()) {
|
||||
next();
|
||||
} else {
|
||||
LoggerProxy.debug('Request failed because user management is disabled');
|
||||
res.status(400).json({ status: 'error', message: 'User management is disabled' });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user