hfs
Version:
HTTP File Server
135 lines (134 loc) • 6.47 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.change_my_srp = exports.refresh_session = exports.logout = exports.loginSrp2 = exports.loginSrp1 = exports.login = void 0;
const perm_1 = require("./perm");
const apiMiddleware_1 = require("./apiMiddleware");
const const_1 = require("./const");
const adminApis_1 = require("./adminApis");
const middlewares_1 = require("./middlewares");
const auth_1 = require("./auth");
const config_1 = require("./config");
const events_1 = __importDefault(require("./events"));
const ongoingLogins = {}; // store data that doesn't fit session object
const keepSessionAlive = (0, config_1.defineConfig)('keep_session_alive', true);
const login = async ({ username, password }, ctx) => {
var _a;
if (!username)
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
if (!ctx.session)
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
try {
const account = await (0, auth_1.clearTextLogin)(ctx, username, password, 'api');
if (!account)
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
}
catch (e) {
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, String(e));
}
return {
redirect: (_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.redirect,
...await (0, exports.refresh_session)({}, ctx)
};
};
exports.login = login;
const loginSrp1 = async ({ username }, ctx) => {
var _a, _b;
if (!username)
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
const account = (0, perm_1.getAccount)(username);
if (!ctx.session)
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
if ((_a = account === null || account === void 0 ? void 0 : account.plugin) === null || _a === void 0 ? void 0 : _a.auth) // tell client to do clear-text login, before firing attemptingLogin, before triggering anti-brute
return new apiMiddleware_1.ApiError(const_1.HTTP_METHOD_NOT_ALLOWED);
if ((_b = (await events_1.default.emitAsync('attemptingLogin', { ctx, username }))) === null || _b === void 0 ? void 0 : _b.isDefaultPrevented())
return;
if (!account || !(0, perm_1.accountCanLogin)(account)) { // TODO simulate fake account to prevent knowing valid usernames
ctx.logExtra({ u: username });
ctx.state.dontLog = false; // log even if log_api is false
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
}
if ((0, middlewares_1.failAllowNet)(ctx, account))
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
try {
const { srpServer, ...rest } = await (0, auth_1.srpServerStep1)(account);
const sid = Math.random();
ongoingLogins[sid] = srpServer;
setTimeout(() => delete ongoingLogins[sid], 60000);
ctx.session.loggingIn = { username, sid }; // temporarily store until process is complete
return rest;
}
catch (code) {
return new apiMiddleware_1.ApiError(code);
}
};
exports.loginSrp1 = loginSrp1;
const loginSrp2 = async ({ pubKey, proof }, ctx) => {
var _a;
if (!ctx.session)
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
if (!ctx.session.loggingIn)
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT);
const { username, sid } = ctx.session.loggingIn;
delete ctx.session.loggingIn;
const step1 = ongoingLogins[sid];
if (!step1)
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
try {
const M2 = await step1.step2(BigInt(pubKey), BigInt(proof))
.catch(() => { throw ''; });
await (0, auth_1.setLoggedIn)(ctx, username);
return {
proof: String(M2),
redirect: (_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.redirect,
...await (0, exports.refresh_session)({}, ctx)
};
}
catch (e) {
ctx.logExtra({ u: username });
ctx.state.dontLog = false; // log even if log_api is false
events_1.default.emit('failedLogin', { ctx, username });
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, e ? String(e) : undefined);
}
finally {
delete ongoingLogins[sid];
}
};
exports.loginSrp2 = loginSrp2;
// this api is here for consistency, but frontend is actually using
const logout = async ({}, ctx) => {
if (!ctx.session)
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
await (0, auth_1.setLoggedIn)(ctx, false);
// 401 is a convenient code for OK: the browser clears a possible http authentication (hopefully), and Admin automatically triggers login dialog
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
};
exports.logout = logout;
const refresh_session = async ({}, ctx) => {
var _a, _b;
const username = (0, auth_1.getCurrentUsername)(ctx);
return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
username,
expandedUsername: (0, perm_1.expandUsername)(username),
adminUrl: (0, adminApis_1.ctxAdminAccess)(ctx) ? ctx.state.revProxyPath + const_1.ADMIN_URI : undefined,
canChangePassword: canChangePassword(ctx.state.account),
requireChangePassword: (_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.require_password_change,
exp: keepSessionAlive.get() ? new Date(Date.now() + middlewares_1.sessionDuration.compiled()) : undefined,
accountExp: (_b = ctx.state.account) === null || _b === void 0 ? void 0 : _b.expire,
};
};
exports.refresh_session = refresh_session;
const change_my_srp = async ({ salt, verifier }, ctx) => {
const a = ctx.state.account;
return !a || !canChangePassword(a) ? new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED)
: (0, perm_1.changeSrpHelper)(a, salt, verifier).then(() => {
delete a.require_password_change;
});
};
exports.change_my_srp = change_my_srp;
function canChangePassword(account) {
return account && !(0, perm_1.getFromAccount)(account, a => a.disable_password_change);
}
;