"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.Auth = void 0;
exports.createBasicAuthMiddleware = createBasicAuthMiddleware;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-reflection/type-metadata-runtime"));
const node_crypto_1 = require("node:crypto");
const node_util_1 = require("node:util");
const bcrypt_1 = tslib_1.__importDefault(require("bcrypt"));
const app_1 = require("../app");
const helpers_1 = require("../helpers");
const errors_1 = require("../http/errors");
const headers_1 = require("../http/headers");
const scrypt = (0, node_util_1.promisify)(node_crypto_1.scrypt);
const ResetTokenRandomBytes = 16;
let _AuthMetadata_1;
class Auth {
    static async hashPassword(password) {
        const salt = (0, node_crypto_1.randomBytes)(16).toString('base64url');
        const hash = (await scrypt(password, salt, 32));
        return `scrypt$${salt}$${hash.toString('base64url')}`;
    }
    static async verifyHash(password, encodedHash) {
        if (isBcryptHash(encodedHash))
            return verifyBcryptHash(password, encodedHash);
        return verifyScryptHash(password, encodedHash);
    }
    static async generateResetToken(data) {
        const generationTime = Math.floor(Date.now() / 1000) * 1000;
        const random = await (0, helpers_1.randomBytes)(ResetTokenRandomBytes);
        const payload = Buffer.from(JSON.stringify(data), 'utf8');
        const token = [Math.floor(generationTime / 1000).toString(36), random.toString('base64url'), payload.toString('base64url')].join('.');
        return {
            token,
            data,
            generationTime,
            verifier: (0, node_crypto_1.createHash)('sha256').update(random).digest('base64url')
        };
    }
    static async decodeResetToken(token) {
        return decodeResetTokenParts(token);
    }
    static __tsfType = _AuthMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"Auth\",\"typeName\":\"Auth\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[],\"hasConstructor\":false,\"constructorParameters\":[]}]", [() => Auth]);
}
exports.Auth = Auth;
_AuthMetadata_1.classType = Auth;
Auth.__tsfType = _AuthMetadata_1;
async function verifyScryptHash(password, encodedHash) {
    const parts = encodedHash.split('$');
    if (parts.length !== 3)
        return false;
    const [algorithm, salt, expectedHash] = parts;
    if (algorithm !== 'scrypt' || !isBase64Url(salt) || !isBase64Url(expectedHash))
        return false;
    const actual = (await scrypt(password, salt, 32));
    const expected = Buffer.from(expectedHash, 'base64url');
    return actual.length === expected.length && (0, node_crypto_1.timingSafeEqual)(actual, expected);
}
async function verifyBcryptHash(password, hash) {
    try {
        const normalizedHash = hash.startsWith('$2y$') ? `$2b$${hash.slice(4)}` : hash;
        return await bcrypt_1.default.compare(password, normalizedHash);
    }
    catch {
        return false;
    }
}
function isBcryptHash(value) {
    return /^\$2[abxy]\$(0[4-9]|[12][0-9]|3[01])\$[./A-Za-z0-9]{53}$/.test(value);
}
function createBasicAuthMiddleware(expectedUsername) {
    return class BasicAuthMiddleware {
        config = (0, app_1.getAppConfig)();
        async handle(request) {
            const authHeader = (0, headers_1.getRequestHeader)(request, 'authorization');
            if (typeof authHeader !== 'string')
                throw new errors_1.HttpUnauthorizedError();
            const match = /^basic\s+(\S+)$/i.exec(authHeader.trim());
            if (!match)
                throw new errors_1.HttpUnauthorizedError('Invalid authorization scheme');
            const credentials = decodeBasicCredentials(match[1]);
            if (!credentials)
                throw new errors_1.HttpUnauthorizedError('Invalid credentials');
            const expectedSecret = this.config.AUTH_BASIC_SECRET;
            const usernameInvalid = expectedUsername !== undefined && !timingSafeStringEqual(credentials.username, expectedUsername);
            const passwordInvalid = !expectedSecret || !timingSafeStringEqual(credentials.password, expectedSecret);
            if (usernameInvalid || passwordInvalid)
                throw new errors_1.HttpUnauthorizedError('Invalid credentials');
        }
    };
}
function decodeResetTokenParts(token) {
    try {
        const parts = token.split('.');
        if (parts.length !== 3 || !parts[0] || !isBase64Url(parts[1]) || !isBase64Url(parts[2]))
            throw new Error('Invalid reset token');
        const generationTime = parseGenerationTime(parts[0]);
        const random = Buffer.from(parts[1], 'base64url');
        if (random.length !== ResetTokenRandomBytes)
            throw new Error('Invalid reset token');
        const data = JSON.parse(Buffer.from(parts[2], 'base64url').toString('utf8'));
        return formatResetToken(token, data, generationTime, random);
    }
    catch (error) {
        if (error instanceof Error && error.message === 'Invalid reset token')
            throw error;
        throw new Error('Invalid reset token');
    }
}
function formatResetToken(token, data, generationTime, random) {
    return {
        token,
        data,
        generationTime,
        verifier: (0, node_crypto_1.createHash)('sha256').update(random).digest('base64url')
    };
}
function parseGenerationTime(value) {
    if (!/^[0-9a-z]+$/i.test(value))
        throw new Error('Invalid reset token');
    const generationTime = parseInt(value, 36) * 1000;
    if (!Number.isSafeInteger(generationTime) || generationTime < 0)
        throw new Error('Invalid reset token');
    return generationTime;
}
function decodeBasicCredentials(value) {
    if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value) || value.length % 4 === 1)
        return undefined;
    const decoded = Buffer.from(value, 'base64').toString('utf8');
    const separator = decoded.indexOf(':');
    if (separator < 0)
        return undefined;
    return {
        username: decoded.slice(0, separator),
        password: decoded.slice(separator + 1)
    };
}
function timingSafeStringEqual(actual, expected) {
    const actualHash = (0, node_crypto_1.createHash)('sha256').update(actual, 'utf8').digest();
    const expectedHash = (0, node_crypto_1.createHash)('sha256').update(expected, 'utf8').digest();
    return (0, node_crypto_1.timingSafeEqual)(actualHash, expectedHash);
}
function isBase64Url(value) {
    return /^[A-Za-z0-9_-]+={0,2}$/.test(value) && value.length % 4 !== 1;
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"ResetToken\":{\"kind\":18,\"typeName\":\"ResetToken\",\"types\":[{\"kind\":20,\"name\":\"token\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"data\",\"type\":{\"kind\":2,\"typeName\":\"T\"},\"optional\":false},{\"kind\":20,\"name\":\"generationTime\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"verifier\",\"type\":{\"kind\":6},\"optional\":false}],\"typeParameters\":[\"T\"]}}]")[1];
//# sourceMappingURL=provider.js.map