diff --git a/src/account-db.js b/src/account-db.js index c7fff76ed..409f00099 100644 --- a/src/account-db.js +++ b/src/account-db.js @@ -48,14 +48,18 @@ export function getActiveLoginMethod() { export function getLoginMethod(req) { if ( typeof req !== 'undefined' && - (req.body || { loginMethod: null }).loginMethod + (req.body || { loginMethod: null }).loginMethod && + config.allowedLoginMethods.includes(req.body.loginMethod) ) { return req.body.loginMethod; } - const activeMethod = getActiveLoginMethod(); + if (config.loginMethod) { + return config.loginMethod; + } - return config.loginMethod || activeMethod || 'password'; + const activeMethod = getActiveLoginMethod(); + return activeMethod || 'password'; } export async function bootstrap(loginSettings) { diff --git a/src/app.js b/src/app.js index 80504f14d..2a3269ce9 100644 --- a/src/app.js +++ b/src/app.js @@ -22,6 +22,7 @@ process.on('unhandledRejection', (reason) => { app.disable('x-powered-by'); app.use(cors()); +app.set('trust proxy', config.trustedProxies); app.use( rateLimit({ windowMs: 60 * 1000, diff --git a/src/config-types.ts b/src/config-types.ts index 3feecc9ec..464c2dc3f 100644 --- a/src/config-types.ts +++ b/src/config-types.ts @@ -1,9 +1,13 @@ import { ServerOptions } from 'https'; +type LoginMethod = 'password' | 'header' | 'openid'; + export interface Config { mode: 'test' | 'development'; - loginMethod: 'password' | 'header' | 'openid'; + loginMethod: LoginMethod; + allowedLoginMethods: LoginMethod[]; trustedProxies: string[]; + trustedAuthProxies?: string[]; dataDir: string; projectRoot: string; port: number; diff --git a/src/load-config.js b/src/load-config.js index 9c8ee34f9..f87e2b6cc 100644 --- a/src/load-config.js +++ b/src/load-config.js @@ -54,7 +54,8 @@ if (process.env.ACTUAL_CONFIG_PATH) { /** @type {Omit} */ let defaultConfig = { loginMethod: 'password', - // assume local networks are trusted for header authentication + allowedLoginMethods: ['password', 'header', 'openid'], + // assume local networks are trusted trustedProxies: [ '10.0.0.0/8', '172.16.0.0/12', @@ -62,6 +63,9 @@ let defaultConfig = { 'fc00::/7', '::1/128', ], + // fallback to trustedProxies, but in the future trustedProxies will only be used for express trust + // and trustedAuthProxies will just be for header auth + trustedAuthProxies: null, port: 5006, hostname: '::', webRoot: path.join( @@ -116,9 +120,21 @@ const finalConfig = { return value === 'true'; })() : config.multiuser, + allowedLoginMethods: process.env.ACTUAL_ALLOWED_LOGIN_METHODS + ? process.env.ACTUAL_ALLOWED_LOGIN_METHODS.split(',') + .map((q) => q.trim().toLowerCase()) + .filter(Boolean) + : config.allowedLoginMethods, trustedProxies: process.env.ACTUAL_TRUSTED_PROXIES - ? process.env.ACTUAL_TRUSTED_PROXIES.split(',').map((q) => q.trim()) + ? process.env.ACTUAL_TRUSTED_PROXIES.split(',') + .map((q) => q.trim()) + .filter(Boolean) : config.trustedProxies, + trustedAuthProxies: process.env.ACTUAL_TRUSTED_AUTH_PROXIES + ? process.env.ACTUAL_TRUSTED_AUTH_PROXIES.split(',') + .map((q) => q.trim()) + .filter(Boolean) + : config.trustedAuthProxies, port: +process.env.ACTUAL_PORT || +process.env.PORT || config.port, hostname: process.env.ACTUAL_HOSTNAME || config.hostname, serverFiles: process.env.ACTUAL_SERVER_FILES || config.serverFiles, @@ -208,6 +224,11 @@ debug(`using user files directory ${finalConfig.userFiles}`); debug(`using web root directory ${finalConfig.webRoot}`); debug(`using login method ${finalConfig.loginMethod}`); debug(`using trusted proxies ${finalConfig.trustedProxies.join(', ')}`); +debug( + `using trusted auth proxies ${ + finalConfig.trustedAuthProxies?.join(', ') ?? 'same as trusted proxies' + }`, +); if (finalConfig.https) { debug(`using https key: ${'*'.repeat(finalConfig.https.key.length)}`); diff --git a/src/util/validate-user.js b/src/util/validate-user.js index a84389e6f..934bc9806 100644 --- a/src/util/validate-user.js +++ b/src/util/validate-user.js @@ -1,5 +1,4 @@ import config from '../load-config.js'; -import proxyaddr from 'proxy-addr'; import ipaddr from 'ipaddr.js'; import { getSession } from '../account-db.js'; @@ -45,24 +44,23 @@ export default function validateSession(req, res) { } export function validateAuthHeader(req) { - if (config.trustedProxies.length == 0) { - return true; - } - - let sender = proxyaddr(req, 'uniquelocal'); - let sender_ip = ipaddr.process(sender); + // fallback to trustedProxies when trustedAuthProxies not set + const trustedAuthProxies = config.trustedAuthProxies ?? config.trustedProxies; + // ensure the first hop from our server is trusted + let peer = req.socket.remoteAddress; + let peerIp = ipaddr.process(peer); const rangeList = { - allowed_ips: config.trustedProxies.map((q) => ipaddr.parseCIDR(q)), + allowed_ips: trustedAuthProxies.map((q) => ipaddr.parseCIDR(q)), }; /* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore : there is an error in the ts definition for the function, but this is valid - var matched = ipaddr.subnetMatch(sender_ip, rangeList, 'fail'); + var matched = ipaddr.subnetMatch(peerIp, rangeList, 'fail'); /* eslint-enable @typescript-eslint/ban-ts-comment */ if (matched == 'allowed_ips') { - console.info(`Header Auth Login permitted from ${sender}`); + console.info(`Header Auth Login permitted from ${peer}`); return true; } else { - console.warn(`Header Auth Login attempted from ${sender}`); + console.warn(`Header Auth Login attempted from ${peer}`); return false; } } diff --git a/upcoming-release-notes/499.md b/upcoming-release-notes/499.md new file mode 100644 index 000000000..6c41db698 --- /dev/null +++ b/upcoming-release-notes/499.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [twk3] +--- + +Fix the auth proxy trust by ensuring the proxy is in the trust