"use strict";
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
 * 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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidTokenError = void 0;
exports.isOIDCUserIdentifier = isOIDCUserIdentifier;
exports.startSecurity = startSecurity;
exports.getSecurityConfig = getSecurityConfig;
exports.pathForSecurityConfig = pathForSecurityConfig;
exports.saveSecurityConfig = saveSecurityConfig;
exports.getCertificateOptions = getCertificateOptions;
exports.getCAChainArray = getCAChainArray;
exports.createCertificateOptions = createCertificateOptions;
exports.requestAccess = requestAccess;
exports.getRateLimitValidationOptions = getRateLimitValidationOptions;
const fs_1 = require("fs");
const atomicWrite_1 = require("./atomicWrite");
const lodash_1 = __importDefault(require("lodash"));
const path_1 = __importDefault(require("path"));
const selfsigned_1 = require("selfsigned");
const stat_mode_1 = require("stat-mode");
const debug_1 = require("./debug");
const dummysecurity_1 = __importDefault(require("./dummysecurity"));
const debug = (0, debug_1.createDebug)('signalk-server:security');
function isOIDCUserIdentifier(value) {
    return (typeof value === 'object' &&
        value !== null &&
        typeof value.sub === 'string' &&
        typeof value.issuer === 'string');
}
class InvalidTokenError extends Error {
    constructor(...args) {
        super(...args);
        Error.captureStackTrace(this, InvalidTokenError);
    }
}
exports.InvalidTokenError = InvalidTokenError;
function startSecurity(app, securityConfig) {
    let securityStrategyModuleName = process.env.SECURITYSTRATEGY ||
        lodash_1.default.get(app, 'config.settings.security.strategy');
    if (securityStrategyModuleName) {
        if (securityStrategyModuleName === 'sk-simple-token-security') {
            console.log('The sk-simple-token-security security strategy is depricated, please update to @signalk/sk-simple-token-security');
            process.exit(1);
        }
        else if (securityStrategyModuleName === '@signalk/sk-simple-token-security') {
            securityStrategyModuleName = './tokensecurity';
        }
        const config = securityConfig || getSecurityConfig(app, true);
        // eslint-disable-next-line @typescript-eslint/no-require-imports
        app.securityStrategy = require(securityStrategyModuleName)(app, config);
        if (securityConfig) {
            app.securityStrategy.configFromArguments = true;
            app.securityStrategy.securityConfig = securityConfig;
        }
    }
    else {
        app.securityStrategy = (0, dummysecurity_1.default)();
    }
}
function getSecurityConfig(app, forceRead = false) {
    if (!forceRead && app.securityStrategy?.configFromArguments) {
        return app.securityStrategy.securityConfig;
    }
    else {
        try {
            const optionsAsString = (0, fs_1.readFileSync)(pathForSecurityConfig(app), 'utf8');
            return JSON.parse(optionsAsString);
        }
        catch (e) {
            if (e.code !== 'ENOENT' || !app.securityStrategy?.isDummy()) {
                console.error('Could not parse security config at %s: %s', pathForSecurityConfig(app), e.message);
            }
            return {};
        }
    }
}
function pathForSecurityConfig(app) {
    return path_1.default.join(app.config.configPath, 'security.json');
}
function saveSecurityConfig(app, data, callback) {
    if (app.securityStrategy.configFromArguments) {
        app.securityStrategy.securityConfig = data;
        if (callback) {
            callback(null);
        }
    }
    else {
        const configPath = pathForSecurityConfig(app);
        (0, atomicWrite_1.atomicWriteFile)(configPath, JSON.stringify(data, null, 2))
            .then(() => {
            (0, fs_1.chmodSync)(configPath, '600');
            if (callback) {
                callback(null);
            }
        })
            .catch((err) => {
            if (callback) {
                callback(err);
            }
        });
    }
}
function getCertificateOptions(app, cb) {
    let certLocation;
    if (!app.config.configPath || (0, fs_1.existsSync)('./settings/ssl-cert.pem')) {
        certLocation = './settings';
    }
    else {
        certLocation = app.config.configPath;
    }
    const certFile = path_1.default.join(certLocation, 'ssl-cert.pem');
    const keyFile = path_1.default.join(certLocation, 'ssl-key.pem');
    const chainFile = path_1.default.join(certLocation, 'ssl-chain.pem');
    if ((0, fs_1.existsSync)(certFile) && (0, fs_1.existsSync)(keyFile)) {
        if (!hasStrictPermissions((0, fs_1.statSync)(keyFile))) {
            cb(new Error(`${keyFile} must be accessible only by the user that is running the server, refusing to start`));
            return;
        }
        if (!hasStrictPermissions((0, fs_1.statSync)(certFile))) {
            cb(new Error(`${certFile} must be accessible only by the user that is running the server, refusing to start`));
            return;
        }
        let ca;
        if ((0, fs_1.existsSync)(chainFile)) {
            debug('Found ssl-chain.pem');
            ca = getCAChainArray(chainFile);
            debug.enabled && debug(JSON.stringify(ca, null, 2));
        }
        debug(`Using certificate ssl-key.pem and ssl-cert.pem in ${certLocation}`);
        cb(null, {
            key: (0, fs_1.readFileSync)(keyFile),
            cert: (0, fs_1.readFileSync)(certFile),
            ca
        });
    }
    else {
        createCertificateOptions(app, certFile, keyFile, cb);
    }
}
function hasStrictPermissions(stat) {
    if (process.platform === 'win32') {
        return true;
    }
    else {
        return /^-r[-w][-x]------$/.test(new stat_mode_1.Mode(stat).toString());
    }
}
function getCAChainArray(filename) {
    let chainCert = new Array();
    return (0, fs_1.readFileSync)(filename, 'utf8')
        .split('\n')
        .reduce((ca, line) => {
        chainCert.push(line);
        if (line.match(/-END CERTIFICATE-/)) {
            ca.push(chainCert.join('\n'));
            chainCert = [];
        }
        return ca;
    }, new Array());
}
function createCertificateOptions(app, certFile, keyFile, cb) {
    const location = app.config.configPath ? app.config.configPath : './settings';
    debug(`Creating certificate files in ${location}`);
    (0, selfsigned_1.generate)([{ name: 'commonName', value: 'localhost' }], { days: 3650, keySize: 2048 }, function (err, pems) {
        (0, fs_1.writeFileSync)(keyFile, pems.private);
        (0, fs_1.chmodSync)(keyFile, '600');
        (0, fs_1.writeFileSync)(certFile, pems.cert);
        (0, fs_1.chmodSync)(certFile, '600');
        cb(null, {
            key: pems.private,
            cert: pems.cert
        });
    });
}
function requestAccess(app, request, ip, updateCb) {
    const config = getSecurityConfig(app);
    return app.securityStrategy.requestAccess(config, request, ip, updateCb);
}
/**
 * When Express trust proxy is enabled:
 * - req.ip will reflect the client IP and we don't want rateLimit to
 *   validate the presence of x-forwarded-for.
 * - trustProxy: false prevents ERR_ERL_PERMISSIVE_TRUST_PROXY warnings
 */
function getRateLimitValidationOptions(app) {
    return app.config?.settings?.trustProxy &&
        app.config.settings.trustProxy !== 'false'
        ? { xForwardedForHeader: false, trustProxy: false }
        : undefined;
}
//# sourceMappingURL=security.js.map