"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.router = exports.RateLimiter = void 0;
const express_1 = require("express");
const fs_1 = require("fs");
const limiter_1 = require("limiter");
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const http_1 = require("../../common/http");
const constants_1 = require("../constants");
const http_2 = require("../http");
const util_1 = require("../util");
// RateLimiter wraps around the limiter library for logins.
// It allows 2 logins every minute plus 12 logins every hour.
class RateLimiter {
    constructor() {
        this.minuteLimiter = new limiter_1.RateLimiter(2, "minute");
        this.hourLimiter = new limiter_1.RateLimiter(12, "hour");
    }
    canTry() {
        // Note: we must check using >= 1 because technically when there are no tokens left
        // you get back a number like 0.00013333333333333334
        // which would cause fail if the logic were > 0
        return this.minuteLimiter.getTokensRemaining() >= 1 || this.hourLimiter.getTokensRemaining() >= 1;
    }
    removeToken() {
        return this.minuteLimiter.tryRemoveTokens(1) || this.hourLimiter.tryRemoveTokens(1);
    }
}
exports.RateLimiter = RateLimiter;
const getRoot = async (req, error) => {
    const content = await fs_1.promises.readFile(path.join(constants_1.rootPath, "src/browser/pages/login.html"), "utf8");
    let passwordMsg = `Check the config file at ${(0, util_1.humanPath)(os.homedir(), req.args.config)} for the password.`;
    if (req.args.usingEnvPassword) {
        passwordMsg = "Password was set from $PASSWORD.";
    }
    else if (req.args.usingEnvHashedPassword) {
        passwordMsg = "Password was set from $HASHED_PASSWORD.";
    }
    return (0, http_2.replaceTemplates)(req, content
        .replace(/{{PASSWORD_MSG}}/g, passwordMsg)
        .replace(/{{ERROR}}/, error ? `<div class="error">${(0, util_1.escapeHtml)(error.message)}</div>` : ""));
};
const limiter = new RateLimiter();
exports.router = (0, express_1.Router)();
exports.router.use(async (req, res, next) => {
    const to = (typeof req.query.to === "string" && req.query.to) || "/";
    if (await (0, http_2.authenticated)(req)) {
        return (0, http_2.redirect)(req, res, to, { to: undefined });
    }
    next();
});
exports.router.get("/", async (req, res) => {
    res.send(await getRoot(req));
});
exports.router.post("/", async (req, res) => {
    const password = (0, util_1.sanitizeString)(req.body.password);
    const hashedPasswordFromArgs = req.args["hashed-password"];
    try {
        // Check to see if they exceeded their login attempts
        if (!limiter.canTry()) {
            throw new Error("Login rate limited!");
        }
        if (!password) {
            throw new Error("Missing password");
        }
        const passwordMethod = (0, util_1.getPasswordMethod)(hashedPasswordFromArgs);
        const { isPasswordValid, hashedPassword } = await (0, util_1.handlePasswordValidation)({
            passwordMethod,
            hashedPasswordFromArgs,
            passwordFromRequestBody: password,
            passwordFromArgs: req.args.password,
        });
        if (isPasswordValid) {
            // The hash does not add any actual security but we do it for
            // obfuscation purposes (and as a side effect it handles escaping).
            res.cookie(http_1.CookieKeys.Session, hashedPassword, (0, http_2.getCookieOptions)(req));
            const to = (typeof req.query.to === "string" && req.query.to) || "/";
            return (0, http_2.redirect)(req, res, to, { to: undefined });
        }
        // Note: successful logins should not count against the RateLimiter
        // which is why this logic must come after the successful login logic
        limiter.removeToken();
        console.error("Failed login attempt", JSON.stringify({
            xForwardedFor: req.headers["x-forwarded-for"],
            remoteAddress: req.connection.remoteAddress,
            userAgent: req.headers["user-agent"],
            timestamp: Math.floor(new Date().getTime() / 1000),
        }));
        throw new Error("Incorrect password");
    }
    catch (error) {
        const renderedHtml = await getRoot(req, error);
        res.send(renderedHtml);
    }
});
//# sourceMappingURL=login.js.map