hfs
Version:
HTTP File Server
88 lines (87 loc) • 4.28 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.invalidateSessionBefore = void 0;
exports.srpServerStep1 = srpServerStep1;
exports.srpCheck = srpCheck;
exports.getCurrentUsername = getCurrentUsername;
exports.clearTextLogin = clearTextLogin;
exports.setLoggedIn = setLoggedIn;
const perm_1 = require("./perm");
const cross_const_1 = require("./cross-const");
const tssrp6a_1 = require("tssrp6a");
const srp_1 = require("./srp");
const cross_1 = require("./cross");
const expiringCache_1 = require("./expiringCache");
const node_crypto_1 = require("node:crypto");
const events_1 = __importDefault(require("./events"));
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
async function srpServerStep1(account) {
if (!account.srp)
throw cross_const_1.HTTP_NOT_ACCEPTABLE;
const [salt, verifier] = account.srp.split('|');
if (!salt || !verifier)
throw Error("malformed account");
const srpSession = new tssrp6a_1.SRPServerSession(srp6aNimbusRoutines);
const srpServer = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier));
return { srpServer, salt, pubKey: String(srpServer.B) }; // cast to string cause bigint can't be jsonized
}
const cache = (0, expiringCache_1.expiringCache)(60000);
async function srpCheck(username, password) {
const account = (0, perm_1.getAccount)(username);
if (!(account === null || account === void 0 ? void 0 : account.srp) || !password)
return;
const k = (0, node_crypto_1.createHash)('sha256').update(username + password + account.srp).digest("hex");
const good = await cache.try(k, async () => {
const { srpServer, salt, pubKey } = await srpServerStep1(account);
const client = await (0, srp_1.srpClientPart)(username, password, salt, pubKey);
return srpServer.step2(client.A, client.M1).then(() => true, () => false);
});
return good ? account : undefined;
}
function getCurrentUsername(ctx) {
var _a;
return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
}
async function clearTextLogin(ctx, u, p, via) {
var _a;
if ((_a = (await events_1.default.emitAsync('attemptingLogin', { ctx, username: u, via }))) === null || _a === void 0 ? void 0 : _a.isDefaultPrevented())
return;
const plugins = await events_1.default.emitAsync('clearTextLogin', { ctx, username: u, password: p, via }); // provide clear password to plugins
const a = (plugins === null || plugins === void 0 ? void 0 : plugins.some(x => x === true)) ? (0, perm_1.getAccount)(u) : await srpCheck(u, p);
if (a) {
await setLoggedIn(ctx, a.username);
ctx.headers['x-username'] = a.username; // give an easier way to determine if the login was successful
}
else if (u)
events_1.default.emit('failedLogin', { ctx, username: u, via });
return a;
}
// centralized log-in state
async function setLoggedIn(ctx, username) {
var _a;
const s = ctx.session;
if (!s)
return ctx.throw(cross_const_1.HTTP_SERVER_ERROR, 'session');
if (username === false) {
events_1.default.emit('logout', ctx);
delete s.username;
delete s.allowNet;
return;
}
const a = ctx.state.account = (0, perm_1.getAccount)(username);
if (!a)
return;
await events_1.default.emitAsync('finalizingLogin', { ctx, username, inputs: { ...ctx.state.params, ...ctx.query } });
s.username = (0, perm_1.normalizeUsername)(username);
s.ts = Date.now();
const k = cross_const_1.ALLOW_SESSION_IP_CHANGE;
s[k] = k in ctx.query || Boolean((_a = ctx.state.params) === null || _a === void 0 ? void 0 : _a[k]) || undefined; // login APIs will get ctx.state.params, others can rely on ctx.query
if (!a.expire && a.days_to_live)
(0, perm_1.updateAccount)(a, { expire: new Date(Date.now() + a.days_to_live * cross_1.DAY) });
await events_1.default.emitAsync('login', ctx);
}
// since session are currently stored in cookies, we need to store this information
exports.invalidateSessionBefore = new Map();
;