🐛 Update: Added support for the 'find' command in settings.local.json. Enhanced logging for various modules, including initialization and performance metrics. Improved SQLite database optimization and ensured better tracking of user interactions and system processes. 📚

This commit is contained in:
2025-06-14 16:26:43 +02:00
parent ee54bc273c
commit 89037861e3
2472 changed files with 691099 additions and 1 deletions

View File

@@ -0,0 +1,150 @@
"use strict";
// File system module.
const fs = require('fs');
// Event module.
const events = require('events');
// Base authentication.
class Base extends events.EventEmitter {
// Constructor.
constructor(options, checker) {
super();
if (!options.msg401) {
options.msg401 = "401 Unauthorized";
}
if (!options.msg407) {
options.msg407 = "407 Proxy authentication required";
}
if (!options.contentType) {
options.contentType = "text/plain";
}
if (!options.realm) {
options.realm = "users";
}
// Assign values.
this.options = options;
this.checker = checker;
// Loading users from file, if file is set.
this.options.users = [];
if (!checker && options.file) {
this.loadUsers();
}
}
// Processing user line.
processLine(userLine) {
throw new Error('Not defined!');
}
// Parse auth header.
parseAuthorization(header) {
throw new Error('Not defined!');
}
// Find user.
findUser(req, clientOptions, callback) {
throw new Error('Not defined!');
}
// Generates header.
generateHeader(result) {
throw new Error('Not defined!');
}
// Ask for authentication.
ask(res, result) {
let header = this.generateHeader(result);
res.setHeader("Content-Type", this.options.contentType);
if (this.proxy) {
res.setHeader("Proxy-Authenticate", header);
res.writeHead(407);
res.end(this.options.msg407);
} else {
res.setHeader("WWW-Authenticate", header);
res.writeHead(401);
res.end(this.options.msg401);
}
}
// Checking if user is authenticated.
check(req, res, callback) {
let self = this;
this.isAuthenticated(req, (result) => {
if (result instanceof Error) {
self.emit('error', result, req);
if (callback) {
callback.apply(self, [req, res, result]);
}
} else if (!result.pass) {
self.emit('fail', result, req);
self.ask(res, result);
} else {
self.emit('success', result, req);
if (!this.options.skipUser) {
req.user = result.user;
}
if (callback) {
callback.apply(self, [req, res]);
}
}
});
}
// Is authenticated method.
isAuthenticated(req, callback) {
let self = this;
let header = undefined;
if (this.proxy) {
header = req.headers["proxy-authorization"];
} else {
header = req.headers["authorization"];
}
// Searching for user.
let searching = false;
// If header is sent.
if (header) {
let clientOptions = this.parseAuthorization(header);
if (clientOptions) {
searching = true;
this.findUser(req, clientOptions, (result) => {
callback.apply(self, [result]);
});
}
}
// Only if not searching call callback.
if (!searching) {
callback.apply(this, [{}]);
}
}
// Loading files with user details.
loadUsers() {
let users = fs.readFileSync(this.options.file, 'UTF-8').replace(/\r\n/g, "\n").split("\n");
// Process all users.
users.forEach(u => {
if(u && !u.match(/^\s*#.*/)) {
this.processLine(u);
}
});
}
}
// Export base.
module.exports = Base;

View File

@@ -0,0 +1,110 @@
"use strict";
// Base class.
const Base = require('./base');
// Utility module.
const utils = require('./utils');
// Importing apache-md5 module.
const md5 = require('apache-md5');
// Importing apache-crypt module.
const crypt = require('apache-crypt');
// Bcrypt.
const bcrypt = require('bcryptjs');
// Crypto.
const crypto = require('crypto');
// Define basic auth.
class Basic extends Base {
// Constructor.
constructor(options, checker) {
super(options, checker);
}
// Verifies if password is correct.
validate (hash, password) {
if (hash.substr(0, 5) === '{SHA}') {
hash = hash.substr(5);
return hash === utils.sha1(password);
} else if (hash.substr(0, 6) === '$apr1$' || hash.substr(0, 3) === '$1$') {
return hash === md5(password, hash);
} else if (hash.substr(0, 4) === '$2y$' || hash.substr(0, 4) === '$2a$') {
return bcrypt.compareSync(password, hash);
} else if (hash === crypt(password, hash)) {
return true;
} else if (hash.length === password.length) {
return crypto.timingSafeEqual ?
crypto.timingSafeEqual(new Buffer(hash), new Buffer(password)) : hash === password;
}
}
// Processes line from authentication file.
processLine (userLine) {
let lineSplit = userLine.split(":");
let username = lineSplit.shift();
let hash = lineSplit.join(":");
// Push user.
this.options.users.push({username: username, hash: hash});
}
// Generates request header.
generateHeader () {
return `Basic realm=\"${this.options.realm}\"`;
}
// Parsing authorization header.
parseAuthorization (header) {
let tokens = header.split(" ");
if (tokens[0] === "Basic") {
return tokens[1];
}
}
// Searching for user.
findUser(req, hash, callback) {
// Decode base64.
let splitHash = utils.decodeBase64(hash).split(":");
let username = splitHash.shift();
let password = splitHash.join(":");
let self = this;
if (this.checker) {
// Custom auth.
this.checker.apply(this, [username, password, (result) => {
let params = undefined;
if (result instanceof Error) {
params = [result]
} else {
params = [{ user: username, pass: !!result }];
}
callback.apply(self, params);
}, req]);
} else {
// File based auth.
let pass = false;
// Loop users to find the matching one.
this.options.users.forEach(user => {
if (user.username === username && this.validate(user.hash, password)) {
pass = true;
}
});
// Call final callback.
callback.apply(this, [{user: username, pass: pass}]);
}
}
}
// Export basic auth.
module.exports = (options, checker) => {
return new Basic(options, checker);
};

View File

@@ -0,0 +1,197 @@
"use strict";
// Base class.
const Base = require('./base');
// Utility module.
const utils = require('./utils');
// Unique id.
const uuid = require('uuid');
// Define digest auth.
class Digest extends Base {
// Constructor.
constructor(options, checker) {
super(options, checker);
// Array of random strings sent to clients.
this.nonces = [];
// Algorithm of encryption, could be MD5 or MD5-sess, default is MD5.
if ('MD5-sess' !== options.algorithm) {
this.options.algorithm = "MD5";
}
// Quality of protection is by default auth.
if (options.qop === 'none') {
this.options.qop = '';
} else {
this.options.qop = 'auth';
}
}
// Process user line.
processLine(line) {
let tokens = line.split(":");
// We need only users for given realm.
if (this.options.realm === tokens[1]) {
this.options.users.push({username: tokens[0], hash: tokens[2]});
}
}
// Parse authorization heder.
parseAuthorization(header) {
let opts = {};
let parts = header.split(' ');
let params = parts.slice(1).join(' ');
// Split the parameters by comma.
let tokens = params.split(/,(?=(?:[^"]|"[^"]*")*$)/);
if (parts[0].substr(0, 6) === "Digest") {
// Parse parameters.
let i = 0;
let len = tokens.length;
while (i < len) {
// Strip quotes and whitespace.
let param = /(\w+)=["]?([^"]*)["]?$/.exec(tokens[i]);
if (param) {
opts[param[1]] = param[2];
}
++i;
}
}
// Return options.
return opts;
}
// Validating hash.
validate(ha2, co, hash) {
let ha1 = hash;
// Algorithm.
if (co.algorithm === 'MD5-sess') {
ha1 = utils.md5(`${ha1}:${co.nonce}:${co.cnonce}`);
}
let response = undefined;
// Quality of protection.
if (co.qop) {
response = utils.md5(`${ha1}:${co.nonce}:${co.nc}:${co.cnonce}:${co.qop}:${ha2}`);
} else {
response = utils.md5(`${ha1}:${co.nonce}:${ha2}`);
}
// If calculated response is equal to client's response.
return response === co.response;
}
// Searching for user.
findUser(req, co, callback) {
let self = this;
if (this.validateNonce(co.nonce, co.qop, co.nc)) {
let ha2 = utils.md5(`${req.method}:${co.uri}`);
if (this.checker) {
// Custom authentication.
this.checker.apply(this, [co.username, (hash) => {
let params = undefined;
if (hash instanceof Error) {
params = [hash];
} else {
params = [{user: co.username, pass: !!self.validate(ha2, co, hash)}];
}
// Call callback.
callback.apply(this, params);
}, req]);
} else {
let pass = false;
// File based, loop users to find the matching one.
this.options.users.forEach(user => {
if (user.username === co.username && this.validate(ha2, co, user.hash)) {
pass = true;
}
});
callback.apply(this, [{user: co.username, pass: pass}]);
}
} else {
callback.apply(this, [{stale: true}]);
}
}
// Remove nonces.
removeNonces(noncesToRemove) {
noncesToRemove.forEach(nonce => {
let index = this.nonces.indexOf(nonce);
if (index != -1) {
this.nonces.splice(index, 1);
}
});
}
// Validate nonce.
validateNonce(nonce, qop, nc) {
let found = false;
// Current time.
let now = Date.now();
// Nonces for removal.
let noncesToRemove = [];
// Searching for not expired ones.
this.nonces.forEach(serverNonce => {
if ((serverNonce[1] + 3600000) > now) {
if (serverNonce[0] === nonce) {
if (qop) {
if (nc > serverNonce[2]) {
found = true;
++ serverNonce[2];
}
} else {
found = true;
}
}
} else {
noncesToRemove.push(serverNonce);
}
});
// Remove expired nonces.
this.removeNonces(noncesToRemove);
return found;
}
// Generates and returns new random nonce.
askNonce() {
let nonce = utils.md5(uuid());
this.nonces.push([nonce, Date.now(), 0]);
return nonce;
}
// Generates request header.
generateHeader(result) {
let nonce = this.askNonce();
let stale = result.stale ? true : false;
// Returning it.
return `Digest realm=\"${this.options.realm}\", qop=\"${this.options.qop}\", nonce=\"${nonce}\", algorithm=\"${this.options.algorithm}\", stale=\"${stale}\"`;
}
}
// Export digest auth.
module.exports = (options, checker) => {
return new Digest(options, checker);
};

View File

@@ -0,0 +1,43 @@
"use strict";
// Importing crypto module.
const crypto = require('crypto');
const utils = {};
// Generates md5 hash of input.
utils.md5 = (input) => {
let hash = crypto.createHash('MD5');
hash.update(input);
return hash.digest('hex');
};
// Generates sha1 hash of input.
utils.sha1 = (input) => {
let hash = crypto.createHash('sha1');
hash.update(input);
return hash.digest('base64');
};
// Encode to base64 string.
utils.base64 = (input) => {
return new Buffer(input, 'utf8').toString('base64');
};
// Decodes base64 string.
utils.decodeBase64 = (input) => {
return new Buffer(input, 'base64').toString('utf8');
};
// Check if module is available.
utils.isAvailable = (path) => {
try {
return !!require.resolve(path);
} catch (err) {
return false;
}
};
// Export utils.
module.exports = utils;

View File

@@ -0,0 +1,48 @@
"use strict";
// Utils.
const utils = require('./auth/utils');
// http integration.
require('./server/http');
// https integration.
require('./server/https');
// http-proxy integration.
if (utils.isAvailable('http-proxy')) {
require('./server/proxy');
}
// Exports.
module.exports = {
// Basic authentication.
basic: (options, checker) => {
return require('./auth/basic')(options, checker);
},
// Digest authentication.
digest: (options, checker) => {
return require('./auth/digest')(options, checker);
},
// Connect.
connect: (auth) => {
return require('./server/connect')(auth);
},
// Koa.
koa: (auth) => {
return require('./server/koa')(auth);
},
// Passport.
passport: (auth) => {
return require('./server/passport')(auth);
},
// Hapi.
hapi: () => {
return require('./server/hapi');
}
};

View File

@@ -0,0 +1,14 @@
"use strict";
// Exporting connect integration.
module.exports = (auth) => {
return (req, res, next) => {
auth.check(req, res, (req, res, err) => {
if (err) {
next(err);
} else {
next();
}
});
}
};

View File

@@ -0,0 +1,32 @@
"use strict";
// HTTP authentication scheme.
const httpScheme = (server, auth) => {
return {
authenticate: (request, reply) => {
// Is auth.
auth.isAuthenticated(request, (result) => {
if (result instanceof Error) {
return reply(result, null, { credentials: null });
} else if (!result.pass) {
let header = auth.generateHeader(result);
return reply(auth.options.msg401).code(401).header('WWW-Authenticate', header);
} else {
return reply.continue({credentials: { name: result.user }});
}
});
}
}
};
// Export plugin.
exports.register = (plugin, options, next) => {
plugin.auth.scheme('http', httpScheme);
next();
};
// Export attributes.
exports.register.attributes = {
pkg: require('../../package.json')
};

View File

@@ -0,0 +1,50 @@
"use strict";
// HTTP module.
const http = require('http');
// Base module.
const Base = require('../auth/base');
// Backup old server creation.
const oldCreateServer = http.createServer;
// Add authentication method.
http.createServer = function() {
let server = undefined;
// Mutated mode.
if (arguments[0] instanceof Base) {
let auth = arguments[0];
// With listener.
if (arguments[1]) {
let listener = arguments[1];
let newListener = (req, res) => {
auth.check(req, res, (req, res, err) => {
if (err) {
console.error (err);
res.statusCode = 400;
res.end(err.message);
} else {
listener(req, res);
}
});
};
// Mutate server.
server = oldCreateServer.apply(http, [newListener]);
} else {
// Without listener.
server = oldCreateServer.apply(http, []);
server.on('request', (req, res) => {
auth.check(req, res);
});
}
} else {
server = oldCreateServer.apply(http, arguments);
}
// Return server.
return server;
};

View File

@@ -0,0 +1,48 @@
"use strict";
// HTTPS module.
const https = require('https');
// Base module.
const Base = require('../auth/base');
// Backup old server creation.
let oldCreateServer = https.createServer;
// Mutate server.
https.createServer = function() {
let server = undefined;
if (arguments[0] instanceof Base) {
let auth = arguments[0];
if (arguments[2]) {
let listener = arguments[2];
let newListener = (req, res) => {
auth.check(req, res, (req, res, err) => {
if (err) {
console.error(err);
res.statusCode = 400;
res.end(err.message);
} else {
listener(req, res);
}
});
};
// HTTPS options and listener.
server = oldCreateServer.apply(https, [arguments[1], newListener]);
} else {
// Only HTTPS options.
server = oldCreateServer.apply(https, [arguments[1]]);
server.on('request', (req, res) => {
auth.check(req, res);
});
}
} else {
server = oldCreateServer.apply(https, arguments);
}
// Return server.
return server;
};

View File

@@ -0,0 +1,21 @@
"use strict";
// Export middleware.
module.exports = (auth) => {
// Middleware for koa.
const koa = (req, res, next) => {
auth.check(req, res, (req, res, err) => {
if (err) {
throw err;
} else {
next();
}
});
};
// Return middleware.
return function *(next) {
yield koa.bind(null, this.req, this.res);
yield next;
};
};

View File

@@ -0,0 +1,38 @@
"use strict";
// Imports.
const passport = require('passport');
const util = require('util');
// Define strategy.
function HttpStrategy(auth) {
this.name = 'http';
this.authentication = auth;
passport.Strategy.call(this);
}
// Inherit basic strategy.
util.inherits(HttpStrategy, passport.Strategy);
// Define auth method.
HttpStrategy.prototype.authenticate = function (req) {
let self = this;
// Is auth.
this.authentication.isAuthenticated(req, (result) => {
if (result instanceof Error) {
self.error(result);
} else if (!result.pass) {
let header = self.authentication.generateHeader(result);
self.fail(header);
} else {
self.success(result.user);
}
});
};
// Export.
module.exports = (auth) => {
return new HttpStrategy(auth);
};

View File

@@ -0,0 +1,55 @@
"use strict";
// Proxy module.
const httpProxy = require('http-proxy');
// Base module.
const Base = require('../auth/base');
// Backup old server creation.
const oldCreateServer = httpProxy.createServer;
// New create server.
const newCreateServer = function(auth, options) {
// Mutated mode.
if (auth instanceof Base) {
// Set proxy flag.
auth.proxy = true;
} else {
// Set correct options.
options = auth;
// Clear authentication value.
auth = null;
}
// Default listener plus authentication check.
let server = oldCreateServer.apply(httpProxy, [options]);
// Authentication provided.
if (auth) {
// Override proxyRequest.
let oldProxyRequest = server.web;
server.web = function (req, res) {
// Fetch external arguments.
let externalArguments = arguments;
// Check for authentication.
auth.check(req, res, (req, res, err) => {
if (err) {
console.error(err);
res.statusCode = 400;
res.end (err.message);
} else {
oldProxyRequest.apply(server, externalArguments)
}
});
};
}
// Return server.
return server;
};
// Add authentication method.
httpProxy.createServer = httpProxy.createProxyServer = httpProxy.createProxy = newCreateServer;