"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebServer = void 0;
const os = require("os");
const path = require("path");
const http = require("http");
const https = require("https");
const express = require("express");
const compression = require("compression");
const cookieParser = require("cookie-parser");
const cookieSession = require("cookie-session");
const flash = require("flash");
const SamlProvider_1 = require("./SamlProvider");
const LoginProvider_1 = require("./LoginProvider");
const Config_1 = require("./Config");
const openflow_api_1 = require("@openiap/openflow-api");
const { RateLimiterMemory } = require('rate-limiter-flexible');
const Logger_1 = require("./Logger");
const WebSocketServerClient_1 = require("./WebSocketServerClient");
const Crypt_1 = require("./Crypt");
var _hostname = "";
const rateLimiter = async (req, res, next) => {
    if (req.originalUrl.indexOf('/oidc') > -1)
        return next();
    // var span = Logger.otel.startSpanExpress("rateLimiter", req);
    try {
        Logger_1.Logger.instanse.verbose("Validate for " + req.originalUrl, null);
        var e = await WebServer.BaseRateLimiter.consume(WebServer.remoteip(req));
        Logger_1.Logger.instanse.verbose("consumedPoints: " + e.consumedPoints + " remainingPoints: " + e.remainingPoints, null);
        next();
    }
    catch (error) {
        // const route = url.parse(req.url).pathname;
        // if (!NoderedUtil.IsNullUndefinded(websocket_rate_limit)) websocket_rate_limit.bind({ ...Logger.otel.defaultlabels, route: route }).update(e.consumedPoints);
        var span = Logger_1.Logger.otel.startSpanExpress("rateLimiter", req);
        Logger_1.Logger.instanse.warn("API_RATE_LIMIT consumedPoints: " + error.consumedPoints + " remainingPoints: " + error.remainingPoints + " msBeforeNext: " + error.msBeforeNext, span);
        span.end();
        res.status(429).json({ response: 'RATE_LIMIT' });
    }
    finally {
        // span.end();
    }
};
class WebServer {
    static remoteip(req) {
        let remoteip = req.socket.remoteAddress;
        if (req.headers["X-Forwarded-For"] != null)
            remoteip = req.headers["X-Forwarded-For"];
        if (req.headers["X-real-IP"] != null)
            remoteip = req.headers["X-real-IP"];
        if (req.headers["x-forwarded-for"] != null)
            remoteip = req.headers["x-forwarded-for"];
        if (req.headers["x-real-ip"] != null)
            remoteip = req.headers["x-real-ip"];
        return remoteip;
    }
    static async isBlocked(req) {
        try {
            var remoteip = LoginProvider_1.LoginProvider.remoteip(req);
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(remoteip)) {
                remoteip = remoteip.toLowerCase();
                var blocks = await Logger_1.Logger.DBHelper.GetIPBlockList(null);
                if (blocks && blocks.length > 0) {
                    for (var i = 0; i < blocks.length; i++) {
                        var block = blocks[i];
                        var blocklist = block.ips;
                        if (blocklist && Array.isArray(blocklist)) {
                            for (var x = 0; x < blocklist.length; x++) {
                                var ip = blocklist[x];
                                if (!openflow_api_1.NoderedUtil.IsNullEmpty(ip)) {
                                    ip = ip.toLowerCase();
                                    if (ip == remoteip) {
                                        return true;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
        return false;
    }
    static async configure(baseurl, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("WebServer.configure", parent);
        span === null || span === void 0 ? void 0 : span.addEvent("create RateLimiterMemory");
        WebServer.BaseRateLimiter = new RateLimiterMemory({
            points: Config_1.Config.api_rate_limit_points,
            duration: Config_1.Config.api_rate_limit_duration,
        });
        try {
            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel)) {
                // websocket_rate_limit = Logger.otel.meter.createObservableUpDownCounter("openflow_webserver_rate_limit_count", {
                //     description: 'Total number of rate limited web request'
                // }) // "route"
            }
            span === null || span === void 0 ? void 0 : span.addEvent("Create Express");
            this.app = express();
            this.app.disable("x-powered-by");
            this.app.use(async (req, res, next) => {
                if (await WebServer.isBlocked(req)) {
                    var remoteip = WebSocketServerClient_1.WebSocketServerClient.remoteip(req);
                    if (Config_1.Config.log_blocked_ips)
                        Logger_1.Logger.instanse.error(remoteip + " is blocked", null);
                    try {
                        res.status(429).json({ "message": "ip blocked" });
                    }
                    catch (error) {
                    }
                    return;
                }
                next();
            });
            this.app.use("/", express.static(path.join(__dirname, "/public")));
            this.app.use(compression());
            this.app.use(express.urlencoded({ extended: true }));
            this.app.use(express.json());
            this.app.use(cookieParser());
            this.app.set('trust proxy', 1);
            span === null || span === void 0 ? void 0 : span.addEvent("Add cookieSession");
            this.app.use(cookieSession({
                name: "session", secret: Config_1.Config.cookie_secret, httpOnly: true
            }));
            this.app.use(flash());
            if (Config_1.Config.api_rate_limit)
                this.app.use(rateLimiter);
            this.app.get("/livenessprobe", WebServer.get_livenessprobe.bind(this));
            // https://scaleup.us/2020/06/21/how-to-block-ips-in-your-traefik-proxy-server/
            this.app.get("/ipblock", async (req, res, next) => {
                if (await WebServer.isBlocked(req)) {
                    var remoteip = LoginProvider_1.LoginProvider.remoteip(req);
                    if (Config_1.Config.log_blocked_ips)
                        Logger_1.Logger.instanse.error(remoteip + " is blocked", null);
                    res.statusCode = 401;
                    res.setHeader('WWW-Authenticate', 'Basic realm="OpenFlow"');
                    res.end('Unauthorized');
                    return;
                }
                return res.status(200).send({ message: 'ok.' });
            });
            // Add headers
            this.app.use(function (req, res, next) {
                Logger_1.Logger.instanse.verbose("add for " + req.originalUrl, null);
                // const origin: string = (req.headers.origin as any);
                // if (NoderedUtil.IsNullEmpty(origin)) {
                //     res.header('Access-Control-Allow-Origin', '*');
                // } else {
                //     res.header('Access-Control-Allow-Origin', origin);
                // }
                // Website you wish to allow to connect
                res.setHeader('Access-Control-Allow-Origin', '*');
                // Request methods you wish to allow
                res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
                // Request headers you wish to allow
                res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Access-Control-Allow-Headers, Authorization");
                // Set to true if you need the website to include cookies in the requests sent
                // to the API (e.g. in case you use sessions)
                res.setHeader('Access-Control-Allow-Credentials', "true");
                // Disable Caching
                res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
                res.header('Expires', '-1');
                res.header('Pragma', 'no-cache');
                if (req.originalUrl == "/me") {
                    res.redirect('/oidc/me');
                    return next();
                }
                // Grafana hack
                if (req.originalUrl == "/oidc/me" && req.method == "OPTIONS") {
                    return res.send("ok");
                }
                if (req.originalUrl.indexOf('/oidc') > -1)
                    return next();
                // Pass to next layer of middleware
                next();
            });
            // https://www.section.io/engineering-education/push-notification-in-nodejs-using-service-worker/
            // https://github.com/mercymeave/code-space/tree/main/push-notifications/client
            // https://swina.github.io/2019/02/vue-service-worker-for-webpush-notifications/
            //setting vapid keys details
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.wapid_pub) && !openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.wapid_key)) {
                span === null || span === void 0 ? void 0 : span.addEvent("Setting openflow for WebPush");
                var mail = Config_1.Config.wapid_mail;
                if (openflow_api_1.NoderedUtil.IsNullEmpty(mail))
                    mail = "me@email.com";
                this.webpush.setVapidDetails('mailto:' + mail, Config_1.Config.wapid_pub, Config_1.Config.wapid_key);
                this.app.post('/webpushsubscribe', async (req, res) => {
                    var subspan = Logger_1.Logger.otel.startSpanExpress("webpushsubscribe", req);
                    try {
                        const subscription = req.body;
                        span === null || span === void 0 ? void 0 : span.setAttribute("subscription", JSON.stringify(subscription));
                        if (openflow_api_1.NoderedUtil.IsNullUndefinded(subscription) && openflow_api_1.NoderedUtil.IsNullEmpty(subscription.jwt)) {
                            Logger_1.Logger.instanse.error("Received invalid subscription request", null);
                            return res.status(500).json({ "error": "no subscription" });
                        }
                        const jwt = subscription.jwt;
                        const tuser = await Crypt_1.Crypt.verityToken(jwt);
                        if (openflow_api_1.NoderedUtil.IsNullUndefinded(tuser)) {
                            Logger_1.Logger.instanse.error("jwt is invalid", null);
                            return res.status(500).json({ "error": "no subscription" });
                        }
                        delete subscription.jwt;
                        if (openflow_api_1.NoderedUtil.IsNullEmpty(subscription._type))
                            subscription._type = "unknown";
                        subscription.userid = tuser._id;
                        subscription.name = tuser.name + " " + subscription._type + " " + subscription.host;
                        var msg = new openflow_api_1.InsertOrUpdateOneMessage();
                        msg.collectionname = "webpushsubscriptions";
                        msg.jwt = jwt;
                        msg.item = subscription;
                        msg.uniqeness = "userid,_type,host,endpoint";
                        await Config_1.Config.db._InsertOrUpdateOne(msg, null);
                        Logger_1.Logger.instanse.info("Registered webpush subscription for " + tuser.name, span);
                        res.status(201).json({});
                    }
                    catch (error) {
                        Logger_1.Logger.instanse.error(error, subspan);
                        try {
                            return res.status(500).json({ "error": error.message ? error.message : error });
                        }
                        catch (error) {
                        }
                    }
                    finally {
                        subspan === null || subspan === void 0 ? void 0 : subspan.end();
                    }
                });
            }
            span === null || span === void 0 ? void 0 : span.addEvent("Configure LoginProvider");
            await LoginProvider_1.LoginProvider.configure(this.app, baseurl, span);
            try {
                span === null || span === void 0 ? void 0 : span.addEvent("Configure FormioEP");
                let FormioEPProxy = null;
                try {
                    FormioEPProxy = require("./ee/FormioEP");
                }
                catch (error) {
                }
                if (!openflow_api_1.NoderedUtil.IsNullUndefinded(FormioEPProxy)) {
                    await FormioEPProxy.FormioEP.configure(this.app, baseurl);
                }
            }
            catch (error) {
            }
            span === null || span === void 0 ? void 0 : span.addEvent("Configure SamlProvider");
            await SamlProvider_1.SamlProvider.configure(this.app, baseurl);
            WebServer.server = null;
            if (Config_1.Config.tls_crt != '' && Config_1.Config.tls_key != '') {
                let options = {
                    cert: Config_1.Config.tls_crt,
                    key: Config_1.Config.tls_key
                };
                if (Config_1.Config.tls_crt.indexOf("---") == -1) {
                    options = {
                        cert: Buffer.from(Config_1.Config.tls_crt, 'base64').toString('ascii'),
                        key: Buffer.from(Config_1.Config.tls_key, 'base64').toString('ascii')
                    };
                }
                let ca = Config_1.Config.tls_ca;
                if (ca !== "") {
                    if (ca.indexOf("---") === -1) {
                        ca = Buffer.from(Config_1.Config.tls_ca, 'base64').toString('ascii');
                    }
                    options.ca = ca;
                }
                if (Config_1.Config.tls_passphrase !== "") {
                    options.passphrase = Config_1.Config.tls_passphrase;
                }
                WebServer.server = https.createServer(options, this.app);
            }
            else {
                WebServer.server = http.createServer(this.app);
            }
            await Config_1.Config.db.connect(span);
            return WebServer.server;
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, span);
            return null;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    static Listen() {
        WebServer.server.listen(Config_1.Config.port).on('error', function (error) {
            Logger_1.Logger.instanse.error(error, null);
            if (Config_1.Config.NODE_ENV == "production") {
                WebServer.server.close();
                process.exit(404);
            }
        });
        Logger_1.Logger.instanse.info("Listening on " + Config_1.Config.baseurl(), null);
    }
    static get_livenessprobe(req, res, next) {
        let span = Logger_1.Logger.otel.startSpanExpress("get_livenessprobe", req);
        try {
            const [traceId, spanId] = Logger_1.Logger.otel.GetTraceSpanId(span);
            if (openflow_api_1.NoderedUtil.IsNullEmpty(_hostname))
                _hostname = (Config_1.Config.getEnv("HOSTNAME", undefined) || os.hostname()) || "unknown";
            res.end(JSON.stringify({ "success": "true", "hostname": _hostname, dt: new Date(), traceId, spanId }));
            res.end();
            span.setStatus({ code: 200 });
        }
        catch (error) {
            console.error(error);
            span.setStatus({
                code: 500,
                message: error instanceof Error ? error.message : undefined,
            });
        }
        finally {
            span.end();
        }
        // https://lightrun.com/answers/googleapis-nodejs-pubsub-opentelemetry-integration-misses-the-point-we-need-to-propagate-the-spancontext
        // https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-jaeger
        // https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-propagator-jaeger/test/JaegerPropagator.test.ts
        // tracer.startActiveSpan('findme22', {}, context, (span) => {
        //     try {
        //         if (NoderedUtil.IsNullEmpty(_hostname)) _hostname = (Config.getEnv("HOSTNAME", undefined) || os.hostname()) || "unknown";
        //         // @ts-ignore
        //         const [ traceId, spanId ] = Logger.otel.GetTraceSpanId(span);
        //         res.end(JSON.stringify({ "success": "true", "hostname": _hostname, traceId, spanId, traceFlags }));
        //         res.end();
        //         span.setStatus({ code: 200 });
        //     } catch (error) {
        //         console.error(error);
        //         span.setStatus({
        //             code: 500,
        //             message: error instanceof Error ? error.message : undefined,
        //         });
        //     } finally {
        //         span.end();
        //     }
        // });
    }
}
exports.WebServer = WebServer;
WebServer.server = null;
WebServer.webpush = require('web-push');
//# sourceMappingURL=WebServer.js.map