Files
dougal-software/lib/www/server/api/middleware/auth/authentify.js
2025-07-25 14:03:43 +02:00

114 lines
2.8 KiB
JavaScript

const dns = require('dns');
const { Netmask } = require('netmask');
const cfg = require('../../../lib/config');
const jwt = require('../../../lib/jwt');
const user = require('../../../lib/db/user');
const ServerUser = require('../../../lib/db/user/User');
async function authorisedIP (req, res) {
const validIPs = await user.ip({active: true}); // Get all active IP logins
validIPs.forEach( i => i.$block = new Netmask(i.ip) );
validIPs.sort( (a, b) => b.$block.bitmask - a.$block.netmask ); // More specific IPs have precedence
for (const ip of validIPs) {
const block = ip.$block;
if (block.contains(req.ip)) {
const payload = {
...ip,
ip: req.ip,
autologin: true
};
delete payload.$block;
delete payload.hash;
delete payload.active;
jwt.issue(payload, req, res);
return true;
}
}
return false;
}
async function authorisedHost (req, res) {
const validHosts = await user.host({active: true}); // Get all active host logins
for (const key in validHosts) {
try {
const ip = await dns.promises.resolve(key);
if (ip == req.ip) {
const payload = {
...validHosts[key],
ip: req.ip,
autologin: true
};
delete payload.$block;
delete payload.hash;
delete payload.active;
jwt.issue(payload, req, res);
return true;
}
} catch (err) {
if (err.code != "ENODATA") {
console.error(err);
}
}
}
return false;
}
// TODO: Check client TLS certificates
// Probably will do this via Nginx with
// ssl_verify_client optional;
// and then putting either of the
// $ssl_client_s_dn or $ssl_client_escaped_cert
// variables into an HTTP header for Node
// to check (naturally, it must be ensured
// that a user cannot just insert the header
// in a request).
async function auth (req, res, next) {
if (res.headersSent) {
// Nothing to do, this request must have been
// handled already by another middleware.
return;
}
// Check for a valid JWT (already decoded by a previous
// middleware).
if (req.user) {
if (!req.user.autologin) {
// If this is not an automatic login, check if the token is in the
// second half of its lifetime. If so, reissue a new one, valid for
// another cfg.jwt.options.expiresIn seconds.
if (req.user.exp) {
const ttl = req.user.exp - Date.now()/1000;
if (ttl < cfg.jwt.options.expiresIn/2) {
const credentials = await ServerUser.fromSQL(null, req.user.id);
if (credentials) {
// Refresh token
payload = Object.assign({}, credentials.toJSON());
jwt.issue(Object.assign({}, credentials.toJSON()), req, res);
}
}
}
}
next();
return;
}
// Check if the IP is known to us
if (await authorisedIP(req, res)) {
next();
return;
}
// Check if the hostname is known to us
if (await authorisedHost(req, res)) {
next();
return;
}
next({status: 401, message: "Not authorised"});
}
module.exports = auth;