"use strict";
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 });
exports.getJWTRSAKeys = getJWTRSAKeys;
exports.createTokens = createTokens;
exports.refreshOperationToken = refreshOperationToken;
exports.validateOperationToken = validateOperationToken;
exports.validateRefreshToken = validateRefreshToken;
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const node_path_1 = __importDefault(require("node:path"));
const joi_1 = __importDefault(require("joi"));
const validationWrapper_js_1 = require("../validation/validationWrapper.js");
const hdbTerms_ts_1 = require("../utility/hdbTerms.js");
const hdbError_js_1 = require("../utility/errors/hdbError.js");
const { HTTP_STATUS_CODES, AUTHENTICATION_ERROR_MSGS } = hdbError_js_1.hdbErrors;
const harper_logger_js_1 = __importDefault(require("../utility/logging/harper_logger.js"));
const password = __importStar(require("../utility/password.js"));
const user_ts_1 = require("./user.js");
const insert_js_1 = require("../dataLayer/insert.js");
const UpdateObject_js_1 = __importDefault(require("../dataLayer/UpdateObject.js"));
const signalling_js_1 = __importDefault(require("../utility/signalling.js"));
const itc_js_1 = require("../server/threads/itc.js");
const environmentManager_js_1 = __importDefault(require("../utility/environment/environmentManager.js"));
environmentManager_js_1.default.initSync();
const OPERATION_TOKEN_TIMEOUT = environmentManager_js_1.default.get(hdbTerms_ts_1.CONFIG_PARAMS.AUTHENTICATION_OPERATIONTOKENTIMEOUT) || '1d';
const REFRESH_TOKEN_TIMEOUT = environmentManager_js_1.default.get(hdbTerms_ts_1.CONFIG_PARAMS.AUTHENTICATION_REFRESHTOKENTIMEOUT) || '30d';
const RSA_ALGORITHM = 'RS256';
const TOKEN_TYPE = {
    OPERATION: 'operation',
    REFRESH: 'refresh',
};
/**
 * fetches the rsa keys from cache var or disk
 * @returns {Promise<JWTRSAKeys>}
 */
let rsaKeys = undefined;
async function getJWTRSAKeys() {
    if (rsaKeys)
        return rsaKeys;
    try {
        const keysDir = node_path_1.default.join(environmentManager_js_1.default.getHdbBasePath(), hdbTerms_ts_1.LICENSE_KEY_DIR_NAME);
        const passphrase = await fs_extra_1.default.readFile(node_path_1.default.join(keysDir, hdbTerms_ts_1.JWT_ENUM.JWT_PASSPHRASE_NAME), 'utf8');
        const privateKey = await fs_extra_1.default.readFile(node_path_1.default.join(keysDir, hdbTerms_ts_1.JWT_ENUM.JWT_PRIVATE_KEY_NAME), 'utf8');
        const publicKey = await fs_extra_1.default.readFile(node_path_1.default.join(keysDir, hdbTerms_ts_1.JWT_ENUM.JWT_PUBLIC_KEY_NAME), 'utf8');
        rsaKeys = { publicKey, privateKey, passphrase };
        return rsaKeys;
    }
    catch (err) {
        harper_logger_js_1.default.error(err);
        throw new hdbError_js_1.ClientError(AUTHENTICATION_ERROR_MSGS.NO_ENCRYPTION_KEYS, HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
    }
}
/**
 * Creates a new operation token and refresh token.
 * If there is no username and password, the hdb_user making the request is used in the token.
 * An optional role can be provided which will be saved in the token payload.
 * The token expires in the time specified in the expires_in field or the default time.
 * @param authObj
 */
async function createTokens(authObj) {
    const validation = (0, validationWrapper_js_1.validateBySchema)(authObj, joi_1.default.object({
        username: joi_1.default.string().optional(),
        password: joi_1.default.string().optional(),
        role: joi_1.default.string().optional(),
        expires_in: joi_1.default.alternatives(joi_1.default.string(), joi_1.default.number()).optional(),
    }));
    if (validation)
        throw new hdbError_js_1.ClientError(validation.message);
    let user;
    try {
        // bypassAuth will be set to true if this is called from a component
        let validatePassword = authObj.bypass_auth !== true;
        if (!authObj.username && !authObj.password) {
            // if the username and password are not provided, use the hdb_user making the request.
            authObj.username = authObj.hdb_user?.username;
            // the password would have been checked by authHandler before getting here
            validatePassword = false;
        }
        user = await (0, user_ts_1.findAndValidateUser)(authObj.username, authObj.password, validatePassword);
    }
    catch (err) {
        harper_logger_js_1.default.error(err);
        throw new hdbError_js_1.ClientError(AUTHENTICATION_ERROR_MSGS.INVALID_CREDENTIALS, HTTP_STATUS_CODES.UNAUTHORIZED);
    }
    if (!user)
        throw new hdbError_js_1.ClientError(AUTHENTICATION_ERROR_MSGS.INVALID_CREDENTIALS, HTTP_STATUS_CODES.UNAUTHORIZED);
    let superUser = false;
    if (user.role?.permission) {
        superUser = user.role.permission.super_user === true;
    }
    const payload = { username: authObj.username, super_user: superUser };
    if (authObj.role)
        payload.role = authObj.role;
    const keys = await getJWTRSAKeys();
    const operationToken = jsonwebtoken_1.default.sign(payload, { key: keys.privateKey, passphrase: keys.passphrase }, {
        expiresIn: (authObj.expires_in ?? OPERATION_TOKEN_TIMEOUT),
        algorithm: RSA_ALGORITHM,
        subject: TOKEN_TYPE.OPERATION,
    });
    const refreshToken = jsonwebtoken_1.default.sign(payload, { key: keys.privateKey, passphrase: keys.passphrase }, {
        expiresIn: REFRESH_TOKEN_TIMEOUT,
        algorithm: RSA_ALGORITHM,
        subject: TOKEN_TYPE.REFRESH,
    });
    // update the user refresh token
    const hashedToken = password.hash(refreshToken, password.HASH_FUNCTION.SHA256);
    const updateResult = await (0, insert_js_1.update)(new UpdateObject_js_1.default(hdbTerms_ts_1.SYSTEM_SCHEMA_NAME, hdbTerms_ts_1.SYSTEM_TABLE_NAMES.USER_TABLE_NAME, [
        { username: authObj.username, refresh_token: hashedToken },
    ]));
    if (updateResult.skipped_hashes.length > 0)
        throw new hdbError_js_1.ClientError(AUTHENTICATION_ERROR_MSGS.REFRESH_TOKEN_SAVE_FAILED, HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
    signalling_js_1.default.signalUserChange(new itc_js_1.UserEventMsg(process.pid));
    return {
        operation_token: operationToken,
        refresh_token: refreshToken,
    };
}
/**
 * Refreshes the operation token using the refresh token.
 * @param tokenObj
 */
async function refreshOperationToken(tokenObj) {
    const validation = (0, validationWrapper_js_1.validateBySchema)(tokenObj, joi_1.default.object({ refresh_token: joi_1.default.string().required() }).required());
    if (validation)
        throw new hdbError_js_1.ClientError(validation.message);
    const { refresh_token } = tokenObj;
    await validateRefreshToken(refresh_token);
    const keys = await getJWTRSAKeys();
    const decodedJWT = jsonwebtoken_1.default.decode(refresh_token, { json: true });
    const operationToken = jsonwebtoken_1.default.sign({ username: decodedJWT.username, super_user: decodedJWT.super_user }, { key: keys.privateKey, passphrase: keys.passphrase }, {
        expiresIn: OPERATION_TOKEN_TIMEOUT,
        algorithm: RSA_ALGORITHM,
        subject: TOKEN_TYPE.OPERATION,
    });
    return { operation_token: operationToken };
}
async function validateOperationToken(token) {
    return validateToken(token, TOKEN_TYPE.OPERATION);
}
async function validateRefreshToken(token) {
    return validateToken(token, TOKEN_TYPE.REFRESH);
}
async function validateToken(token, tokenType) {
    try {
        const keys = await getJWTRSAKeys();
        const tokenVerified = jsonwebtoken_1.default.verify(token, keys.publicKey, {
            algorithms: [RSA_ALGORITHM],
            subject: tokenType,
        });
        // If a role is present, it means the token is not an operation token. The validation of
        // the token will happen in the respective function/component that uses the token.
        if (tokenVerified.role) {
            throw new Error('Invalid token');
        }
        const user = await (0, user_ts_1.findAndValidateUser)(tokenVerified.username, undefined, false);
        if (tokenType === TOKEN_TYPE.REFRESH && !password.validate(user.refresh_token, token)) {
            throw new Error('Invalid token');
        }
        return user;
    }
    catch (err) {
        harper_logger_js_1.default.warn(err);
        if (err?.name === 'TokenExpiredError') {
            throw new hdbError_js_1.ClientError(AUTHENTICATION_ERROR_MSGS.TOKEN_EXPIRED, HTTP_STATUS_CODES.FORBIDDEN);
        }
        throw new hdbError_js_1.ClientError(AUTHENTICATION_ERROR_MSGS.INVALID_TOKEN, HTTP_STATUS_CODES.UNAUTHORIZED);
    }
}
//# sourceMappingURL=tokenAuthentication.js.map