UNPKG

hfs

Version:
135 lines (134 loc) 6.47 kB
"use strict"; // 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); }