Skip to content

Commit

Permalink
Added Header Authentication - Server Part (#312)
Browse files Browse the repository at this point in the history
* Added loginMethod config option

Added a new config option loginMethod that can be password (default) or header for header authentication. This value is returned to the client to be used on the front end


---------

Signed-off-by: dependabot[bot] <[email protected]>
Signed-off-by: Johannes Löthberg <[email protected]>
Co-authored-by: DJ Mountney <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Johannes Löthberg <[email protected]>
Co-authored-by: Matt Fiddaman <[email protected]>
Co-authored-by: DJ Mountney <[email protected]>
  • Loading branch information
6 people authored May 4, 2024
1 parent 33c204d commit 3a486ed
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 3 deletions.
16 changes: 16 additions & 0 deletions src/account-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ export function needsBootstrap() {
return rows.length === 0;
}

/*
* Get the Login Method in the following order
* req (the frontend can say which method in the case it wants to resort to forcing password auth)
* config options
* fall back to using password
*/
export function getLoginMethod(req) {
if (
typeof req !== 'undefined' &&
(req.body || { loginMethod: null }).loginMethod
) {
return req.body.loginMethod;
}
return config.loginMethod || 'password';
}

export function bootstrap(password) {
if (password === undefined || password === '') {
return { error: 'invalid-password' };
Expand Down
32 changes: 29 additions & 3 deletions src/app-account.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from 'express';
import errorMiddleware from './util/error-middleware.js';
import validateUser from './util/validate-user.js';
import validateUser, { validateAuthHeader } from './util/validate-user.js';
import {
bootstrap,
login,
changePassword,
needsBootstrap,
getLoginMethod,
} from './account-db.js';

let app = express();
Expand All @@ -22,7 +23,7 @@ export { app as handlers };
app.get('/needs-bootstrap', (req, res) => {
res.send({
status: 'ok',
data: { bootstrapped: !needsBootstrap() },
data: { bootstrapped: !needsBootstrap(), loginMethod: getLoginMethod() },
});
});

Expand All @@ -38,7 +39,32 @@ app.post('/bootstrap', (req, res) => {
});

app.post('/login', (req, res) => {
let { error, token } = login(req.body.password);
let loginMethod = getLoginMethod(req);
console.log('Logging in via ' + loginMethod);
let tokenRes = null;
switch (loginMethod) {
case 'header': {
let headerVal = req.get('x-actual-password') || '';
console.debug('HEADER VALUE: ' + headerVal);
if (headerVal == '') {
res.send({ status: 'error', reason: 'invalid-header' });
return;
} else {
if (validateAuthHeader(req)) {
tokenRes = login(headerVal);
} else {
res.send({ status: 'error', reason: 'proxy-not-trusted' });
return;
}
}
break;
}
case 'password':
default:
tokenRes = login(req.body.password);
break;
}
let { error, token } = tokenRes;

if (error) {
res.status(400).send({ status: 'error', reason: error });
Expand Down
2 changes: 2 additions & 0 deletions src/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ServerOptions } from 'https';

export interface Config {
mode: 'test' | 'development';
loginMethod: 'password' | 'header';
trustedProxies: string[];
dataDir: string;
projectRoot: string;
port: number;
Expand Down
17 changes: 17 additions & 0 deletions src/load-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ if (process.env.ACTUAL_CONFIG_PATH) {

/** @type {Omit<import('./config-types.js').Config, 'mode' | 'dataDir' | 'serverFiles' | 'userFiles'>} */
let defaultConfig = {
loginMethod: 'password',
// assume local networks are trusted for header authentication
trustedProxies: [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'fc00::/7',
'::1/128',
],
port: 5006,
hostname: '::',
webRoot: path.join(
Expand Down Expand Up @@ -88,6 +97,12 @@ if (process.env.NODE_ENV === 'test') {

const finalConfig = {
...config,
loginMethod: process.env.ACTUAL_LOGIN_METHOD
? process.env.ACTUAL_LOGIN_METHOD.toLowerCase()
: config.loginMethod,
trustedProxies: process.env.ACTUAL_TRUSTED_PROXIES
? process.env.ACTUAL_TRUSTED_PROXIES.split(',').map((q) => q.trim())
: config.trustedProxies,
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,
Expand Down Expand Up @@ -127,6 +142,8 @@ debug(`using data directory ${finalConfig.dataDir}`);
debug(`using server files directory ${finalConfig.serverFiles}`);
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(', ')}`);

if (finalConfig.https) {
debug(`using https key: ${'*'.repeat(finalConfig.https.key.length)}`);
Expand Down
26 changes: 26 additions & 0 deletions src/util/validate-user.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { getSession } from '../account-db.js';
import config from '../load-config.js';
import proxyaddr from 'proxy-addr';
import ipaddr from 'ipaddr.js';

/**
* @param {import('express').Request} req
Expand All @@ -25,3 +28,26 @@ export default function validateUser(req, res) {

return session;
}

export function validateAuthHeader(req) {
if (config.trustedProxies.length == 0) {
return true;
}

let sender = proxyaddr(req, 'uniquelocal');
let sender_ip = ipaddr.process(sender);
const rangeList = {
allowed_ips: config.trustedProxies.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');
/* eslint-enable @typescript-eslint/ban-ts-comment */
if (matched == 'allowed_ips') {
console.info(`Header Auth Login permitted from ${sender}`);
return true;
} else {
console.warn(`Header Auth Login attempted from ${sender}`);
return false;
}
}
6 changes: 6 additions & 0 deletions upcoming-release-notes/312.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [joewashear007]
---

Add option to authenticate with HTTP header from Auth Proxy.

0 comments on commit 3a486ed

Please sign in to comment.