"use strict";
/*
 * Copyright 2017 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http = __importStar(require("http"));
const https = __importStar(require("https"));
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const busboy_1 = __importDefault(require("busboy"));
const command_exists_1 = __importDefault(require("command-exists"));
const express_1 = __importDefault(require("express"));
const zip_1 = require("./zip");
const fs_1 = __importDefault(require("fs"));
const lodash_1 = require("lodash");
const moment_1 = __importDefault(require("moment"));
const ncp_1 = __importDefault(require("ncp"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const unzipper_1 = __importDefault(require("unzipper"));
const util_1 = __importDefault(require("util"));
const swagger_1 = require("./api/swagger");
const config_1 = require("./config/config");
const constants_1 = require("./constants");
const cors_1 = require("./cors");
const debug_1 = require("./debug");
const modules_1 = require("./modules");
const ports_1 = require("./ports");
const requestResponse_1 = require("./requestResponse");
const security_1 = require("./security");
const serialports_1 = require("./serialports");
const signalk_schema_1 = require("@signalk/signalk-schema");
const interfaces_1 = __importDefault(require("./interfaces"));
const redirects_json_1 = __importDefault(require("./redirects.json"));
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const child_process_1 = require("child_process");
const version_1 = require("./version");
const readdir = util_1.default.promisify(fs_1.default.readdir);
const debug = (0, debug_1.createDebug)('signalk-server:serverroutes');
const ncp = ncp_1.default.ncp;
const defaultSecurityStrategy = './tokensecurity';
const skPrefix = '/signalk/v1';
const DEFAULT_HTTP_RATE_LIMIT_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
const DEFAULT_HTTP_RATE_LIMIT_API_MAX = 1000;
const DEFAULT_HTTP_RATE_LIMIT_LOGIN_STATUS_MAX = 1000;
function getHttpRateLimitOverridesFromEnv() {
    const raw = process.env.HTTP_RATE_LIMITS;
    const defaults = {
        windowMs: DEFAULT_HTTP_RATE_LIMIT_WINDOW_MS,
        apiMax: DEFAULT_HTTP_RATE_LIMIT_API_MAX,
        loginStatusMax: DEFAULT_HTTP_RATE_LIMIT_LOGIN_STATUS_MAX
    };
    if (!raw || typeof raw !== 'string') {
        return defaults;
    }
    const parts = raw
        .split(/[\s,]+/)
        .map((p) => p.trim())
        .filter(Boolean);
    let overrides = { ...defaults };
    for (const part of parts) {
        const eqIndex = part.indexOf('=');
        if (eqIndex === -1) {
            continue;
        }
        const key = part.slice(0, eqIndex).trim().toLowerCase();
        const value = part.slice(eqIndex + 1).trim();
        const parsed = Number.parseInt(value, 10);
        if ((key === 'windowms' || key === 'window') && Number.isFinite(parsed)) {
            overrides = { ...overrides, windowMs: parsed };
        }
        else if ((key === 'api' || key === 'apimax') && Number.isFinite(parsed)) {
            overrides = { ...overrides, apiMax: parsed };
        }
        else if ((key === 'loginstatus' || key === 'loginstatusmax') &&
            Number.isFinite(parsed)) {
            overrides = { ...overrides, loginStatusMax: parsed };
        }
    }
    return overrides;
}
module.exports = function (app, saveSecurityConfig, getSecurityConfig) {
    const httpRateLimitOverrides = getHttpRateLimitOverridesFromEnv();
    const rateLimitValidationOptions = (0, security_1.getRateLimitValidationOptions)(app);
    const apiLimiter = (0, express_rate_limit_1.default)({
        windowMs: httpRateLimitOverrides.windowMs,
        max: httpRateLimitOverrides.apiMax,
        message: {
            message: 'Too many requests from this IP, please try again after 10 minutes'
        },
        validate: rateLimitValidationOptions
    });
    const loginStatusLimiter = (0, express_rate_limit_1.default)({
        windowMs: httpRateLimitOverrides.windowMs,
        max: httpRateLimitOverrides.loginStatusMax,
        message: {
            message: 'Too many requests from this IP, please try again after 10 minutes'
        },
        validate: rateLimitValidationOptions
    });
    let securityWasEnabled = false;
    const restoreSessions = new Map();
    const logopath = path_1.default.resolve(app.config.configPath, 'logo.svg');
    if (fs_1.default.existsSync(logopath)) {
        debug(`Found custom logo at ${logopath}, adding route for it`);
        // Intercept Webpack (fonts/), Vite 6 (assets/ hashed), and Vite 8 (assets/public_src/img/) paths
        app.use('/admin/fonts/signal-k-logo-image-text.*', (req, res) => res.sendFile(logopath));
        app.use('/admin/assets/signal-k-logo-image-text*.svg', (req, res) => res.sendFile(logopath));
        app.use('/admin/assets/public_src/img/signal-k-logo-image-text.svg', (req, res) => res.sendFile(logopath));
        // Check for custom logo for minimized sidebar, otherwise use the existing logo.
        const minimizedLogoPath = path_1.default.resolve(app.config.configPath, 'logo-minimized.svg');
        const minimizedLogo = fs_1.default.existsSync(minimizedLogoPath)
            ? minimizedLogoPath
            : logopath;
        // Intercept Webpack (fonts/), Vite 6 (assets/ hashed), and Vite 8 (assets/public_src/img/) paths
        app.use('/admin/fonts/signal-k-logo-image.*', (req, res) => res.sendFile(minimizedLogo));
        app.use('/admin/assets/signal-k-logo-image*.svg', (req, res) => res.sendFile(minimizedLogo));
        app.use('/admin/assets/public_src/img/signal-k-logo-image.svg', (req, res) => res.sendFile(minimizedLogo));
    }
    // Vite 8 (Rolldown) changed CSS url() rewriting for publicDir assets: the built CSS
    // references logos as url(public_src/img/...) which resolves to assets/public_src/img/
    // relative to the CSS file, not the actual img/ location. Serve default logos from there.
    app.use('/admin/assets/public_src/img', express_1.default.static(path_1.default.join(__dirname, '/../node_modules/@signalk/server-admin-ui/public/img')));
    // mount before the main /admin
    (0, swagger_1.mountSwaggerUi)(app, '/doc/openapi');
    // mount server-guide
    app.use('/documentation', express_1.default.static(__dirname + '/../docs/dist'));
    // Redirect old documentation URLs to new ones
    let oldpath;
    for (oldpath in redirects_json_1.default) {
        const from = `/documentation/${oldpath}`;
        const to = `/documentation/${redirects_json_1.default[oldpath]}`;
        app.get(from, (_, res) => {
            res.redirect(301, to);
        });
    }
    const adminUiPath = path_1.default.join(__dirname, '/../node_modules/@signalk/server-admin-ui/public');
    function serveIndexWithAddonScripts(indexPath, res) {
        fs_1.default.readFile(indexPath, (err, indexContent) => {
            if (err) {
                console.error(err);
                res.status(500);
                res.type('text/plain');
                res.send('Could not handle admin ui root request');
                return;
            }
            res.type('html');
            const addonScripts = (0, lodash_1.uniq)([]
                .concat(app.addons)
                .concat(app.pluginconfigurators)
                .concat(app.embeddablewebapps));
            setNoCache(res);
            res.send(indexContent.toString().replace(/%ADDONSCRIPTS%/g, addonScripts
                .map((moduleInfo) => moduleInfo.type === 'module'
                ? `<script type="module" src="/${moduleInfo.name}/remoteEntry.js"></script>`
                : `<script src="/${moduleInfo.name}/remoteEntry.js"></script>`)
                .join('\n')
                .toString()));
        });
    }
    app.get('/admin/', (req, res) => {
        if (!req.originalUrl.endsWith('/')) {
            res.redirect(301, req.originalUrl + '/');
            return;
        }
        serveIndexWithAddonScripts(path_1.default.join(adminUiPath, 'index.html'), res);
    });
    app.use('/admin', express_1.default.static(adminUiPath));
    app.get('/', (req, res) => {
        let landingPage = '/admin/';
        // if accessed with hostname that starts with a webapp's displayName redirect there
        //strip possible port number
        const firstHostName = (req.headers?.host || '')
            .split(':')[0]
            .split('.')[0]
            .toLowerCase();
        const targetWebapp = app.webapps.find((webapp) => 
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        webapp.signalk?.displayName?.toLowerCase() === firstHostName);
        if (targetWebapp) {
            landingPage = `/${targetWebapp.name}/`;
        }
        res.redirect(app.config.settings.landingPage || landingPage);
    });
    app.get('/@signalk/server-admin-ui', (req, res) => {
        res.redirect('/admin/');
    });
    app.put(`${constants_1.SERVERROUTESPREFIX}/restart`, (req, res) => {
        if (app.securityStrategy.allowRestart(req)) {
            res.json('Restarting...');
            setTimeout(function () {
                process.exit(0);
            }, 2000);
        }
        else {
            res.status(401).json('Restart not allowed');
        }
    });
    const securityActivationDisabled = process.env.DISABLE_SECURITY_ACTIVATION === '1' ||
        process.env.DISABLE_SECURITY_ACTIVATION === 'true';
    const getLoginStatus = (req, res) => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const result = app.securityStrategy.getLoginStatus(req);
        result.securityWasEnabled = securityWasEnabled;
        if (securityActivationDisabled) {
            delete result.noUsers;
        }
        setNoCache(res);
        res.json(result);
    };
    app.get(`${constants_1.SERVERROUTESPREFIX}/loginStatus`, loginStatusLimiter, getLoginStatus);
    //TODO remove after a grace period
    app.get(`/loginStatus`, loginStatusLimiter, (req, res) => {
        console.log(`/loginStatus is deprecated, try updating webapps to the latest version`);
        getLoginStatus(req, res);
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/security/config`, (req, res) => {
        if (app.securityStrategy.allowConfigure(req)) {
            const config = getSecurityConfig(app);
            res.json(app.securityStrategy.getConfig(config));
        }
        else {
            res.status(401).json('Security config not allowed');
        }
    });
    app.put(`${constants_1.SERVERROUTESPREFIX}/security/config`, (req, res) => {
        if (app.securityStrategy.allowConfigure(req)) {
            try {
                app.securityStrategy.validateConfiguration(req.body);
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
            }
            catch (err) {
                res.status(400).send(err.message);
                return;
            }
            let config = getSecurityConfig(app);
            const configToSave = (0, cors_1.handleAdminUICORSOrigin)(req.body);
            config = app.securityStrategy.setConfig(config, configToSave);
            saveSecurityConfig(app, config, (err) => {
                if (err) {
                    console.log(err);
                    res.status(500);
                    res.json('Unable to save configuration change');
                    return;
                }
                res.json('security config saved');
            });
        }
        else {
            res.status(401).send('Security config not allowed');
        }
    });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    function getConfigSavingCallback(success, failure, res) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return (err, config) => {
            if (err) {
                console.log(err);
                res.status(500).type('text/plain').send(failure);
            }
            else if (config) {
                saveSecurityConfig(app, config, (theError) => {
                    if (theError) {
                        console.log(theError);
                        res.status(500).send('Unable to save configuration change');
                        return;
                    }
                    res.type('text/plain').send(success);
                });
            }
            else {
                res.type('text/plain').send(success);
            }
        };
    }
    function checkAllowConfigure(req, res) {
        if (app.securityStrategy.allowConfigure(req)) {
            return true;
        }
        else {
            res.status(401).json('Security config not allowed');
            return false;
        }
    }
    app.get(`${constants_1.SERVERROUTESPREFIX}/security/devices`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            res.json(app.securityStrategy.getDevices(config));
        }
    });
    app.put(`${constants_1.SERVERROUTESPREFIX}/security/devices/:uuid`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            app.securityStrategy.updateDevice(config, req.params.uuid, req.body, getConfigSavingCallback('Device updated', 'Unable to update device', res));
        }
    });
    app.delete(`${constants_1.SERVERROUTESPREFIX}/security/devices/:uuid`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            app.securityStrategy.deleteDevice(config, req.params.uuid, getConfigSavingCallback('Device deleted', 'Unable to delete device', res));
        }
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/security/users`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            res.json(app.securityStrategy.getUsers(config));
        }
    });
    app.put(`${constants_1.SERVERROUTESPREFIX}/security/users/:id`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            app.securityStrategy.updateUser(config, req.params.id, req.body, getConfigSavingCallback('User updated', 'Unable to add user', res));
        }
    });
    app.post(`${constants_1.SERVERROUTESPREFIX}/security/users/:id`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            const user = req.body;
            user.userId = req.params.id;
            app.securityStrategy.addUser(config, user, (err, savedConfig) => {
                if (err) {
                    const status = err.message === 'User already exists' ? 400 : 500;
                    res.status(status).type('text/plain').send(err.message);
                }
                else if (savedConfig) {
                    saveSecurityConfig(app, savedConfig, (saveErr) => {
                        if (saveErr) {
                            console.log(saveErr);
                            res.status(500).send('Unable to save configuration change');
                            return;
                        }
                        res.type('text/plain').send('User added');
                    });
                }
                else {
                    res.status(500).type('text/plain').send('Unable to add user');
                }
            });
        }
    });
    app.put(`${constants_1.SERVERROUTESPREFIX}/security/user/:username/password`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            app.securityStrategy.setPassword(config, req.params.username, req.body, getConfigSavingCallback('Password changed', 'Unable to change password', res));
        }
    });
    app.delete(`${constants_1.SERVERROUTESPREFIX}/security/users/:username`, (req, res) => {
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            app.securityStrategy.deleteUser(config, req.params.username, getConfigSavingCallback('User deleted', 'Unable to delete user', res));
        }
    });
    app.put([
        `${constants_1.SERVERROUTESPREFIX}/security/access/requests/:identifier/:status`,
        '/security/access/requests/:identifier/:status' // for backwards compatibly with existing clients
    ], (req, res) => {
        if (!app.securityStrategy.setAccessRequestStatus) {
            res.status(404).json({
                message: 'Access requests not available. Server security may not be enabled.'
            });
            return;
        }
        if (checkAllowConfigure(req, res)) {
            const config = getSecurityConfig(app);
            app.securityStrategy.setAccessRequestStatus(config, req.params.identifier, req.params.status, req.body, getConfigSavingCallback('Request updated', 'Unable update request', res));
        }
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/security/access/requests`, (req, res) => {
        if (!app.securityStrategy.getAccessRequestsResponse) {
            res.status(404).json({
                message: 'Access requests not available. Server security may not be enabled.'
            });
            return;
        }
        if (checkAllowConfigure(req, res)) {
            res.json(app.securityStrategy.getAccessRequestsResponse());
        }
    });
    app.post(`${skPrefix}/access/requests`, apiLimiter, (req, res) => {
        if (req.headers['content-length'] &&
            parseInt(req.headers['content-length']) > 10 * 1024) {
            res.status(413).send('Payload too large');
            return;
        }
        const config = getSecurityConfig(app);
        const ip = req.ip;
        if (app.securityStrategy.isDummy()) {
            res.status(404).json({
                message: 'Access requests not available. Server security is not enabled.'
            });
            return;
        }
        app.securityStrategy
            .requestAccess(config, { accessRequest: req.body }, ip)
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            .then((reply) => {
            res.status(reply.state === 'PENDING' ? 202 : reply.statusCode);
            res.json(reply);
        })
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            .catch((err) => {
            console.error(err.message);
            res.status(err.statusCode || 500).send(err.message);
        });
    });
    app.get(`${skPrefix}/requests/:id`, apiLimiter, (req, res) => {
        (0, requestResponse_1.queryRequest)(req.params.id)
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            .then((reply) => {
            res.json(reply);
        })
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            .catch((err) => {
            console.log(err);
            res.status(500);
            res.type('text/plain').send(`Unable to check request: ${err.message}`);
        });
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/settings`, (req, res) => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const settings = {
            interfaces: {},
            options: {
                mdns: app.config.settings.mdns ?? true,
                wsCompression: app.config.settings.wsCompression || false,
                wsPingInterval: app.config.settings.wsPingInterval ?? 30000,
                accessLogging: (0, lodash_1.isUndefined)(app.config.settings.accessLogging) ||
                    app.config.settings.accessLogging,
                enablePluginLogging: (0, lodash_1.isUndefined)(app.config.settings.enablePluginLogging) ||
                    app.config.settings.enablePluginLogging,
                trustProxy: app.config.settings.trustProxy || false
            },
            loggingDirectory: app.config.settings.loggingDirectory,
            pruneContextsMinutes: app.config.settings.pruneContextsMinutes || 60,
            keepMostRecentLogsOnly: (0, lodash_1.isUndefined)(app.config.settings.keepMostRecentLogsOnly) ||
                app.config.settings.keepMostRecentLogsOnly,
            logCountToKeep: app.config.settings.logCountToKeep || 24,
            runFromSystemd: process.env.RUN_FROM_SYSTEMD === 'true',
            courseApi: {
                apiOnly: app.config.settings.courseApi?.apiOnly || false
            }
        };
        if (!settings.runFromSystemd) {
            settings.sslport = (0, ports_1.getSslPort)(app);
            settings.port = (0, ports_1.getHttpPort)(app);
            settings.options.ssl = app.config.settings.ssl || false;
        }
        (0, lodash_1.forIn)(interfaces_1.default, function (_interface, name) {
            settings.interfaces[name] =
                (0, lodash_1.isUndefined)(app.config.settings.interfaces) ||
                    (0, lodash_1.isUndefined)(app.config.settings.interfaces[name]) ||
                    app.config.settings.interfaces[name];
        });
        res.json(settings);
    });
    if (app.securityStrategy.getUsers(getSecurityConfig(app)).length === 0) {
        if (securityActivationDisabled) {
            app.post(`${constants_1.SERVERROUTESPREFIX}/enableSecurity`, (_req, res) => {
                res.status(403).send('Security activation is disabled');
            });
        }
        else {
            app.post(`${constants_1.SERVERROUTESPREFIX}/enableSecurity`, (req, res) => {
                if (securityWasEnabled ||
                    app.securityStrategy.getUsers(getSecurityConfig(app)).length > 0) {
                    res.status(403).send('Security already enabled');
                    return;
                }
                if (req.body.restore === true) {
                    const { username, password } = req.body;
                    if (!username || !password) {
                        res.status(400).send('Username and password are required');
                        return;
                    }
                    const securityConfigPath = (0, security_1.pathForSecurityConfig)(app);
                    const backupPath = securityConfigPath + '.disabled';
                    if (!fs_1.default.existsSync(backupPath)) {
                        res.status(404).send('No security backup found');
                        return;
                    }
                    let backupConfig;
                    try {
                        backupConfig = JSON.parse(fs_1.default.readFileSync(backupPath, 'utf8'));
                    }
                    catch (err) {
                        console.error(err);
                        res.status(500).send('Unable to read security backup');
                        return;
                    }
                    const user = backupConfig.users?.find((u) => u.username === username && u.type === 'admin');
                    const hashToCompare = user?.password || '$2b$10$invalidhashpadding';
                    bcryptjs_1.default.compare(password, hashToCompare, (err, matches) => {
                        if (err) {
                            console.error(err);
                            res.status(500).send('Unable to verify credentials');
                            return;
                        }
                        if (!matches || !user?.password) {
                            res.status(401).send('Invalid username or password');
                            return;
                        }
                        try {
                            fs_1.default.renameSync(backupPath, securityConfigPath);
                            app.config.settings.security = {
                                strategy: defaultSecurityStrategy
                            };
                            (0, config_1.writeSettingsFile)(app, app.config.settings, 
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            (err) => {
                                if (err) {
                                    console.error(err);
                                    fs_1.default.renameSync(securityConfigPath, backupPath);
                                    res.status(500).send('Unable to save settings');
                                    return;
                                }
                                securityWasEnabled = true;
                                res.send('Security restored, please restart the server');
                            });
                        }
                        catch (err) {
                            console.error(err);
                            res.status(500).send('Unable to restore security');
                        }
                    });
                    return;
                }
                if (app.securityStrategy.isDummy()) {
                    const adminUser = req.body;
                    if (!adminUser.userId ||
                        adminUser.userId.length === 0 ||
                        !adminUser.password ||
                        adminUser.password.length === 0) {
                        res.status(400).send('userId or password missing or too short');
                        return;
                    }
                    const updatedSettings = structuredClone(app.config.settings);
                    updatedSettings.security = { strategy: defaultSecurityStrategy };
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    (0, config_1.writeSettingsFile)(app, updatedSettings, (err) => {
                        if (err) {
                            console.log(err);
                            res.status(500).send('Unable to save to settings file');
                        }
                        else {
                            app.config.settings = updatedSettings;
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            const config = {};
                            // eslint-disable-next-line @typescript-eslint/no-require-imports
                            const securityStrategy = require(defaultSecurityStrategy)(app, config, saveSecurityConfig);
                            if (req.body.allow_readonly === true) {
                                config.allow_readonly = true;
                            }
                            addUser(req, res, securityStrategy, config);
                        }
                    });
                }
                else {
                    addUser(req, res, app.securityStrategy);
                }
                function addUser(request, response, securityStrategy, 
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                config) {
                    if (!config) {
                        config = app.securityStrategy.getConfiguration();
                    }
                    request.body.type = 'admin';
                    securityStrategy.addUser(config, request.body, (err, theConfig) => {
                        if (err) {
                            console.log(err);
                            response.status(500);
                            response.send('Unable to add user');
                        }
                        else {
                            saveSecurityConfig(app, theConfig, (theError) => {
                                if (theError) {
                                    console.log(theError);
                                    response.status(500);
                                    response.send('Unable to save security configuration change');
                                }
                                else {
                                    securityWasEnabled = true;
                                    response.send('Security enabled');
                                }
                            });
                        }
                    });
                }
            });
        }
    }
    app.post(`${constants_1.SERVERROUTESPREFIX}/disableSecurity`, (req, res) => {
        if (!app.securityStrategy.allowConfigure(req)) {
            res.status(401).send('Disable security not allowed');
            return;
        }
        const { username, password } = req.body || {};
        if (!username || !password) {
            res.status(400).send('Username and password are required');
            return;
        }
        if (!app.securityStrategy.login) {
            res.status(500).send('Login not supported by security strategy');
            return;
        }
        const config = getSecurityConfig(app);
        const user = config?.users?.find((u) => u.username === username && u.type === 'admin');
        if (!user) {
            res.status(401).send('Invalid username or password');
            return;
        }
        app.securityStrategy
            .login(username, password)
            .then((reply) => {
            if (reply.statusCode !== 200) {
                res.status(401).send('Invalid username or password');
                return;
            }
            const securityConfigPath = (0, security_1.pathForSecurityConfig)(app);
            const backupPath = securityConfigPath + '.disabled';
            try {
                if (fs_1.default.existsSync(securityConfigPath)) {
                    fs_1.default.renameSync(securityConfigPath, backupPath);
                }
                delete app.config.settings.security;
                (0, config_1.writeSettingsFile)(app, app.config.settings, (err) => {
                    if (err) {
                        console.error(err);
                        if (fs_1.default.existsSync(backupPath)) {
                            fs_1.default.renameSync(backupPath, securityConfigPath);
                        }
                        res.status(500).send('Unable to save settings');
                        return;
                    }
                    res.send('Security disabled, please restart the server');
                });
            }
            catch (err) {
                console.error(err);
                res.status(500).send('Unable to disable security');
            }
        })
            .catch((err) => {
            console.error(err);
            res.status(500).send('Unable to verify credentials');
        });
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/security/hasBackup`, (req, res) => {
        if (!app.securityStrategy.isDummy() &&
            !app.securityStrategy.allowConfigure(req)) {
            res.status(401).send('Not authorized');
            return;
        }
        const backupPath = (0, security_1.pathForSecurityConfig)(app) + '.disabled';
        res.json({ hasBackup: fs_1.default.existsSync(backupPath) });
    });
    app.securityStrategy.addAdminWriteMiddleware(`${constants_1.SERVERROUTESPREFIX}/settings`);
    app.put(`${constants_1.SERVERROUTESPREFIX}/settings`, (req, res) => {
        const settings = req.body;
        const updatedSettings = structuredClone(app.config.settings);
        (0, lodash_1.forIn)(settings.interfaces, (enabled, name) => {
            const interfaces = updatedSettings.interfaces || (updatedSettings.interfaces = {});
            interfaces[name] = enabled;
        });
        if (!(0, lodash_1.isUndefined)(settings.options.mdns)) {
            updatedSettings.mdns = settings.options.mdns;
        }
        if (!(0, lodash_1.isUndefined)(settings.options.ssl)) {
            updatedSettings.ssl = settings.options.ssl;
        }
        if (!(0, lodash_1.isUndefined)(settings.options.wsCompression)) {
            updatedSettings.wsCompression = settings.options.wsCompression;
        }
        if (!(0, lodash_1.isUndefined)(settings.options.wsPingInterval)) {
            updatedSettings.wsPingInterval = settings.options.wsPingInterval;
        }
        if (!(0, lodash_1.isUndefined)(settings.options.accessLogging)) {
            updatedSettings.accessLogging = settings.options.accessLogging;
        }
        if (!(0, lodash_1.isUndefined)(settings.options.enablePluginLogging)) {
            updatedSettings.enablePluginLogging = settings.options.enablePluginLogging;
        }
        if (!(0, lodash_1.isUndefined)(settings.options.trustProxy)) {
            updatedSettings.trustProxy = settings.options.trustProxy;
        }
        if (!(0, lodash_1.isUndefined)(settings.port)) {
            updatedSettings.port = Number(settings.port);
        }
        if (!(0, lodash_1.isUndefined)(settings.sslport)) {
            updatedSettings.sslport = Number(settings.sslport);
        }
        if (!(0, lodash_1.isUndefined)(settings.loggingDirectory)) {
            updatedSettings.loggingDirectory = settings.loggingDirectory;
        }
        if (!(0, lodash_1.isUndefined)(settings.pruneContextsMinutes)) {
            updatedSettings.pruneContextsMinutes = Number(settings.pruneContextsMinutes);
        }
        if (!(0, lodash_1.isUndefined)(settings.keepMostRecentLogsOnly)) {
            updatedSettings.keepMostRecentLogsOnly = settings.keepMostRecentLogsOnly;
        }
        if (!(0, lodash_1.isUndefined)(settings.logCountToKeep)) {
            updatedSettings.logCountToKeep = Number(settings.logCountToKeep);
        }
        (0, lodash_1.forIn)(settings.courseApi, (enabled, name) => {
            const courseApi = updatedSettings.courseApi || (updatedSettings.courseApi = {});
            courseApi[name] = enabled;
        });
        (0, config_1.writeSettingsFile)(app, updatedSettings, (err) => {
            if (err) {
                res.status(500).send('Unable to save to settings file');
            }
            else {
                app.config.settings = updatedSettings;
                res.type('text/plain').send('Settings changed');
            }
        });
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/vessel`, (req, res) => {
        const de = app.config.baseDeltaEditor;
        const communication = de.getSelfValue('communication');
        const draft = de.getSelfValue('design.draft');
        const length = de.getSelfValue('design.length');
        const type = de.getSelfValue('design.aisShipType');
        const json = {
            name: app.config.vesselName,
            mmsi: app.config.vesselMMSI,
            uuid: app.config.vesselUUID,
            draft: draft && draft.maximum,
            length: length && length.overall,
            beam: de.getSelfValue('design.beam'),
            height: de.getSelfValue('design.airHeight'),
            gpsFromBow: de.getSelfValue('sensors.gps.fromBow'),
            gpsFromCenter: de.getSelfValue('sensors.gps.fromCenter'),
            aisShipType: type && type.id,
            callsignVhf: communication && communication.callsignVhf
        };
        res.json(json);
    });
    function writeOldDefaults(req, res) {
        let self;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let data;
        try {
            data = (0, config_1.readDefaultsFile)(app);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
        }
        catch (e) {
            if (e.code && e.code === 'ENOENT') {
                data = {};
            }
            else {
                console.error(e);
                res.status(500).send('Unable to read defaults file');
            }
        }
        self = (0, lodash_1.get)(data, 'vessels.self');
        if ((0, lodash_1.isUndefined)(self)) {
            self = (0, lodash_1.set)(data, 'vessels.self', {});
        }
        const newVessel = req.body;
        function setString(skPath, value) {
            (0, lodash_1.set)(data.vessels.self, skPath, value && value.length > 0 ? value : undefined);
        }
        function setNumber(skPath, rmPath, value) {
            if ((0, lodash_1.isNumber)(value) || (value && value.length > 0)) {
                (0, lodash_1.set)(data.vessels.self, skPath, Number(value));
            }
            else {
                (0, lodash_1.unset)(data.vessels.self, rmPath);
            }
        }
        setString('name', newVessel.name);
        setString('mmsi', newVessel.mmsi);
        if (newVessel.uuid && !self.mmsi) {
            setString('uuid', newVessel.uuid);
        }
        else {
            delete self.uuid;
        }
        setNumber('design.draft.value.maximum', 'design.draft', newVessel.draft);
        setNumber('design.length.value.overall', 'design.length', newVessel.length);
        setNumber('design.beam.value', 'design.beam', newVessel.beam);
        setNumber('design.airHeight.value', 'design.airHeight', newVessel.height);
        setNumber('sensors.gps.fromBow.value', 'sensors.gps.fromBow', newVessel.gpsFromBow);
        setNumber('sensors.gps.fromCenter.value', 'sensors.gps.fromCenter', newVessel.gpsFromCenter);
        if (newVessel.aisShipType) {
            (0, lodash_1.set)(data.vessels.self, 'design.aisShipType.value', {
                name: (0, signalk_schema_1.getAISShipTypeName)(newVessel.aisShipType),
                id: Number(newVessel.aisShipType)
            });
        }
        else {
            delete self.design.aisShipType;
        }
        if (newVessel.callsignVhf) {
            setString('communication.callsignVhf', newVessel.callsignVhf);
        }
        else {
            delete self.communication;
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (0, config_1.writeDefaultsFile)(app, data, (err) => {
            if (err) {
                res.status(500).send('Unable to save to defaults file');
            }
            else {
                res.type('text/plain').send('Vessel changed');
            }
        });
    }
    app.securityStrategy.addAdminWriteMiddleware(`${constants_1.SERVERROUTESPREFIX}/vessel`);
    app.put(`${constants_1.SERVERROUTESPREFIX}/vessel`, (req, res) => {
        const de = app.config.baseDeltaEditor;
        const vessel = req.body;
        de.setSelfValue('name', vessel.name);
        app.config.vesselName = vessel.name;
        de.setSelfValue('mmsi', vessel.mmsi);
        app.config.vesselMMSI = vessel.mmsi;
        if (vessel.uuid && !vessel.mmsi) {
            de.setSelfValue('uuid', vessel.uuid);
            app.config.vesselUUID = vessel.uuid;
        }
        else {
            de.removeSelfValue('uuid');
            delete app.config.vesselUUID;
        }
        function makeNumber(num) {
            return !(0, lodash_1.isUndefined)(num) && ((0, lodash_1.isNumber)(num) || num.length)
                ? Number(num)
                : undefined;
        }
        de.setSelfValue('design.draft', !(0, lodash_1.isUndefined)(vessel.draft) ? { maximum: Number(vessel.draft) } : undefined);
        de.setSelfValue('design.length', !(0, lodash_1.isUndefined)(vessel.length)
            ? { overall: Number(vessel.length) }
            : undefined);
        de.setSelfValue('design.beam', makeNumber(vessel.beam));
        de.setSelfValue('design.airHeight', makeNumber(vessel.height));
        de.setSelfValue('sensors.gps.fromBow', makeNumber(vessel.gpsFromBow));
        de.setSelfValue('sensors.gps.fromCenter', makeNumber(vessel.gpsFromCenter));
        de.setSelfValue('design.aisShipType', !(0, lodash_1.isUndefined)(vessel.aisShipType)
            ? {
                name: (0, signalk_schema_1.getAISShipTypeName)(vessel.aisShipType),
                id: Number(vessel.aisShipType)
            }
            : undefined);
        de.setSelfValue('communication', !(0, lodash_1.isUndefined)(vessel.callsignVhf) && vessel.callsignVhf.length
            ? { callsignVhf: vessel.callsignVhf }
            : undefined);
        app.emit('serverevent', {
            type: 'VESSEL_INFO',
            data: {
                name: app.config.vesselName,
                mmsi: app.config.vesselMMSI,
                uuid: app.config.vesselUUID
            }
        });
        (0, config_1.sendBaseDeltas)(app);
        if (app.config.hasOldDefaults) {
            writeOldDefaults(req, res);
        }
        else {
            (0, config_1.writeBaseDeltasFile)(app)
                .then(() => {
                res.type('text/plain').send('Vessel changed');
            })
                .catch(() => {
                res.status(500).send('Unable to save to defaults file');
            });
        }
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/availablePaths`, (req, res) => {
        res.json(app.streambundle.getAvailablePaths());
    });
    app.securityStrategy.addAdminMiddleware(`${constants_1.SERVERROUTESPREFIX}/eventsRoutingData`);
    app.get(`${constants_1.SERVERROUTESPREFIX}/eventsRoutingData`, (req, res) => {
        res.json(app.wrappedEmitter.getEventRoutingData());
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/serialports`, (req, res, next) => {
        (0, serialports_1.listAllSerialPorts)()
            .then((ports) => res.json(ports))
            .catch(next);
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/hasAnalyzer`, (req, res) => {
        (0, command_exists_1.default)('analyzer')
            .then(() => res.json(true))
            .catch(() => res.json(false));
    });
    app.securityStrategy.addAdminWriteMiddleware(`${constants_1.SERVERROUTESPREFIX}/sourcePriorities`);
    app.get(`${constants_1.SERVERROUTESPREFIX}/sourcePriorities`, (req, res) => {
        res.json(app.config.settings.sourcePriorities || {});
    });
    app.put(`${constants_1.SERVERROUTESPREFIX}/sourcePriorities`, (req, res) => {
        const updatedSettings = structuredClone(app.config.settings);
        updatedSettings.sourcePriorities = req.body;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (0, config_1.writeSettingsFile)(app, updatedSettings, (err) => {
            if (err) {
                res
                    .status(500)
                    .send('Unable to save to sourcePrefences in settings file');
            }
            else {
                app.config.settings = updatedSettings;
                app.activateSourcePriorities();
                res.json({ result: 'ok' });
            }
        });
    });
    app.securityStrategy.addAdminWriteMiddleware(`${constants_1.SERVERROUTESPREFIX}/debug`);
    app.post(`${constants_1.SERVERROUTESPREFIX}/debug`, (req, res) => {
        if (!app.logging.enableDebug(req.body.value)) {
            res.status(400).send('invalid debug value');
        }
        else {
            res.status(200).send();
        }
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/debugKeys`, (req, res) => {
        res.json((0, debug_1.listKnownDebugs)());
    });
    const npmVersion = (() => {
        try {
            return (0, child_process_1.execSync)('npm --version', { encoding: 'utf8' }).trim();
        }
        catch {
            return 'unknown';
        }
    })();
    app.get(`${constants_1.SERVERROUTESPREFIX}/nodeInfo`, (_req, res) => {
        res.json({
            nodeVersion: process.version,
            npmVersion,
            recommendedNodeVersion: version_1.recommendedVersion
        });
    });
    app.securityStrategy.addAdminWriteMiddleware(`${constants_1.SERVERROUTESPREFIX}/rememberDebug`);
    app.post(`${constants_1.SERVERROUTESPREFIX}/rememberDebug`, (req, res) => {
        app.logging.rememberDebug(req.body.value);
        res.status(200).send();
    });
    app.get(`${skPrefix}/apps/list`, (req, res) => {
        res.json(app.webapps.map((webapp) => {
            return {
                name: webapp.name,
                version: webapp.version,
                description: webapp.description,
                location: `/${webapp.name}/`,
                license: webapp.license,
                author: (0, modules_1.getAuthor)(webapp)
            };
        }));
    });
    const safeFiles = [
        'settings.json',
        'defaults.json',
        'security.json',
        'package.json',
        'baseDeltas.json'
    ];
    function listSafeRestoreFiles(restorePath) {
        return new Promise((resolve, reject) => {
            readdir(restorePath)
                .catch(reject)
                .then((filenames) => {
                const goodFiles = filenames?.filter((name) => safeFiles.indexOf(name) !== -1) || [];
                filenames?.forEach((name) => {
                    try {
                        const stats = fs_1.default.lstatSync(path_1.default.join(restorePath, name));
                        if (stats.isDirectory()) {
                            goodFiles.push(name + '/');
                        }
                        resolve(goodFiles);
                    }
                    catch (err) {
                        reject(err);
                    }
                });
            });
        });
    }
    function sendRestoreStatus(state, message, percentComplete) {
        const status = {
            state,
            message,
            percentComplete: percentComplete ? percentComplete * 100 : '-'
        };
        app.emit('serverevent', {
            type: 'RESTORESTATUS',
            from: 'signalk-server',
            data: status
        });
    }
    app.post(`${constants_1.SERVERROUTESPREFIX}/restore`, (req, res) => {
        if (!app.securityStrategy.isDummy() &&
            !app.securityStrategy.allowConfigure(req)) {
            res.status(401).send('Restore not allowed');
            return;
        }
        const sessionId = getCookie(req, 'restoreSession');
        const restoreFilePath = sessionId
            ? restoreSessions.get(sessionId)
            : undefined;
        if (!restoreFilePath) {
            res.status(400).send('not exting restore file');
        }
        else if (!fs_1.default.existsSync(restoreFilePath)) {
            res.status(400).send('restore file does not exist');
        }
        else {
            res.status(202).send();
        }
        listSafeRestoreFiles(restoreFilePath)
            .then((files) => {
            const wanted = files.filter((name) => {
                return req.body[name];
            });
            let hasError = false;
            for (let i = 0; i < wanted.length; i++) {
                const name = wanted[i];
                sendRestoreStatus('Copying Files', `Copying ${name}`, i / wanted.length);
                ncp(path_1.default.join(restoreFilePath, name), path_1.default.join(app.config.configPath, name), { stopOnErr: true }, 
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                (err) => {
                    if (err) {
                        sendRestoreStatus('error', err.message, null);
                        hasError = true;
                    }
                });
            }
            if (!hasError) {
                sendRestoreStatus('Installing Plugins', '', 1);
                (0, modules_1.restoreModules)(app.config, (output) => {
                    sendRestoreStatus('Installing Plugins', `${output}`, 1);
                    console.log(`stdout: ${output}`);
                }, (output) => {
                    //sendRestoreStatus('Error', `${output}`, 1)
                    console.error(`stderr: ${output}`);
                }, () => {
                    sendRestoreStatus('Complete', 'Please restart', 1);
                });
            }
        })
            .catch((err) => {
            console.error(err);
            sendRestoreStatus('error', err.message, null);
        });
    });
    app.post(`${constants_1.SERVERROUTESPREFIX}/validateBackup`, (req, res) => {
        if (!app.securityStrategy.isDummy() &&
            !app.securityStrategy.allowConfigure(req)) {
            res.status(401).send('Validate backup not allowed');
            return;
        }
        const bb = (0, busboy_1.default)({ headers: req.headers });
        bb.on('file', (fieldname, 
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        file, { filename }) => {
            try {
                if (!filename.endsWith('.backup')) {
                    res
                        .status(400)
                        .send('the backup file does not have the .backup extension');
                    return;
                }
                if (!filename.startsWith('signalk-')) {
                    res
                        .status(400)
                        .send('the backup file does not start with signalk-');
                    return;
                }
                const tmpDir = os_1.default.tmpdir();
                const restoreFilePath = fs_1.default.mkdtempSync(`${tmpDir}${path_1.default.sep}`);
                const sessionId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
                restoreSessions.set(sessionId, restoreFilePath);
                setTimeout(() => restoreSessions.delete(sessionId), 15 * 60 * 1000);
                res.cookie('restoreSession', sessionId, {
                    httpOnly: true,
                    sameSite: 'strict'
                });
                const zipFileDir = fs_1.default.mkdtempSync(`${tmpDir}${path_1.default.sep}`);
                const zipFile = path_1.default.join(zipFileDir, 'backup.zip');
                file
                    .pipe(fs_1.default.createWriteStream(zipFile))
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    .on('error', (err) => {
                    console.error(err);
                    res.status(500).send(err.message);
                })
                    .on('close', () => {
                    const zipStream = fs_1.default.createReadStream(zipFile);
                    const extractPromises = [];
                    const resolvedBase = path_1.default.resolve(restoreFilePath);
                    zipStream
                        .pipe(unzipper_1.default.Parse())
                        .on('entry', (entry) => {
                        const targetPath = path_1.default.join(restoreFilePath, entry.path);
                        const resolvedTarget = path_1.default.resolve(targetPath);
                        if (!resolvedTarget.startsWith(resolvedBase + path_1.default.sep)) {
                            console.error(`Zip slip attempt blocked: ${entry.path}`);
                            entry.autodrain();
                            return;
                        }
                        if (entry.type === 'Directory') {
                            fs_1.default.mkdirSync(resolvedTarget, { recursive: true });
                            entry.autodrain();
                        }
                        else {
                            fs_1.default.mkdirSync(path_1.default.dirname(resolvedTarget), {
                                recursive: true
                            });
                            const writePromise = new Promise((resolve, reject) => {
                                entry
                                    .pipe(fs_1.default.createWriteStream(resolvedTarget))
                                    .on('close', resolve)
                                    .on('error', reject);
                            });
                            extractPromises.push(writePromise);
                        }
                    })
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        .on('error', (err) => {
                        console.error(err);
                        res.status(500).send(err.message);
                    })
                        .on('close', () => {
                        Promise.all(extractPromises)
                            .then(() => {
                            fs_1.default.unlinkSync(zipFile);
                            return listSafeRestoreFiles(restoreFilePath);
                        })
                            .then((files) => {
                            res.type('text/plain').send(files);
                        })
                            .catch((err) => {
                            console.error(err);
                            res.status(500).send(err.message);
                        });
                    });
                });
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
            }
            catch (err) {
                console.log(err);
                res.status(500).send(err.message);
            }
        });
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        bb.on('error', (err) => {
            console.log(err);
            res.status(500).send(err.message);
        });
        bb.on('finish', function () {
            console.log('finish');
        });
        req.pipe(bb);
    });
    app.get(`${constants_1.SERVERROUTESPREFIX}/backup`, (req, res) => {
        readdir(app.config.configPath).then((filenames) => {
            const files = filenames
                .filter((file) => {
                return ((file !== 'node_modules' ||
                    (file === 'node_modules' &&
                        req.query.includePlugins === 'true')) &&
                    !file.endsWith('.log') &&
                    file !== 'signalk-server' &&
                    file !== '.npmrc');
            })
                .map((name) => {
                const filename = path_1.default.join(app.config.configPath, name);
                return {
                    path: filename,
                    name
                };
            });
            (0, zip_1.sendZip)(res, {
                files,
                filename: `signalk-${(0, moment_1.default)().format('MMM-DD-YYYY-HHTmm')}.backup`
            });
        });
    });
    app.securityStrategy.addAdminMiddleware(`${constants_1.SERVERROUTESPREFIX}/testSignalKConnection`);
    app.securityStrategy.addAdminMiddleware(`${constants_1.SERVERROUTESPREFIX}/requestAccess`);
    app.securityStrategy.addAdminMiddleware(`${constants_1.SERVERROUTESPREFIX}/checkAccessRequest`);
    app.post(`${constants_1.SERVERROUTESPREFIX}/testSignalKConnection`, (req, res) => {
        const { host, port, useTLS, token, selfsignedcert } = req.body;
        makeRemoteRequest(host, port, useTLS, selfsignedcert, '/signalk')
            .then((discovery) => {
            if (discovery.status !== 200) {
                return res.json({
                    success: false,
                    error: `Discovery failed: HTTP ${discovery.status}`
                });
            }
            let server;
            try {
                server = JSON.parse(discovery.data).server;
            }
            catch (_e) {
                // ignore parse errors for server info
            }
            if (!token) {
                return res.json({
                    success: true,
                    authenticated: false,
                    server
                });
            }
            return makeRemoteRequest(host, port, useTLS, selfsignedcert, '/skServer/loginStatus', 'GET', { Authorization: `JWT ${token}` }).then((loginResult) => {
                let loginStatus;
                try {
                    loginStatus = JSON.parse(loginResult.data);
                }
                catch (_e) {
                    // ignore parse errors
                }
                if (loginResult.status !== 200 ||
                    !loginStatus ||
                    loginStatus.status !== 'loggedIn') {
                    return res.json({
                        success: false,
                        connected: true,
                        error: 'Authentication failed: token may be invalid or revoked',
                        server
                    });
                }
                res.json({
                    success: true,
                    authenticated: true,
                    userLevel: loginStatus.userLevel,
                    username: loginStatus.username,
                    server
                });
            });
        })
            .catch((err) => {
            res.json({ success: false, error: err.message });
        });
    });
    app.post(`${constants_1.SERVERROUTESPREFIX}/requestAccess`, (req, res) => {
        const { host, port, useTLS, selfsignedcert, clientId, description } = req.body;
        makeRemoteRequest(host, port, useTLS, selfsignedcert, '/signalk/v1/access/requests', 'POST', {}, { clientId, description })
            .then((result) => {
            try {
                const data = JSON.parse(result.data);
                res.json(data);
            }
            catch (_e) {
                res.json({
                    state: 'ERROR',
                    error: `Unexpected response: HTTP ${result.status}`
                });
            }
        })
            .catch((err) => {
            res.json({ state: 'ERROR', error: err.message });
        });
    });
    app.post(`${constants_1.SERVERROUTESPREFIX}/checkAccessRequest`, (req, res) => {
        const { host, port, useTLS, selfsignedcert, requestId } = req.body;
        makeRemoteRequest(host, port, useTLS, selfsignedcert, `/signalk/v1/requests/${requestId}`)
            .then((result) => {
            try {
                const data = JSON.parse(result.data);
                res.json(data);
            }
            catch (_e) {
                res.json({
                    state: 'ERROR',
                    error: `Unexpected response: HTTP ${result.status}`
                });
            }
        })
            .catch((err) => {
            res.json({ state: 'ERROR', error: err.message });
        });
    });
};
function makeRemoteRequest(host, port, useTLS, selfsignedcert, path, method, headers, body) {
    const protocol = useTLS ? https : http;
    return new Promise((resolve, reject) => {
        const options = {
            hostname: host,
            port,
            path,
            method: method || 'GET',
            headers: {
                ...(headers || {}),
                ...(body ? { 'Content-Type': 'application/json' } : {})
            },
            rejectUnauthorized: !selfsignedcert
        };
        const req = protocol.request(options, (response) => {
            let data = '';
            response.on('data', (chunk) => {
                data += chunk;
            });
            response.on('end', () => {
                resolve({ status: response.statusCode, data });
            });
        });
        req.on('error', reject);
        req.setTimeout(10000, () => {
            req.destroy(new Error('Connection timed out'));
        });
        if (body) {
            req.write(JSON.stringify(body));
        }
        req.end();
    });
}
const setNoCache = (res) => {
    res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
    res.header('Pragma', 'no-cache');
    res.header('Expires', '0');
};
function getCookie(req, name) {
    if (req.headers.cookie) {
        const value = '; ' + req.headers.cookie;
        const parts = value.split('; ' + name + '=');
        if (parts.length === 2) {
            return parts.pop()?.split(';').shift();
        }
    }
    return undefined;
}
//# sourceMappingURL=serverroutes.js.map