import { URL } from "url";
import httpSignature from "@peertube/http-signature";
import config from "../../config/index.js";
import { fetchMeta } from "../../misc/fetch-meta.js";
import { toPuny } from "../../misc/convert-host.js";
import DbResolver from "./db-resolver.js";
import { getApId } from "./type.js";
import { shouldBlockInstance } from "../../misc/should-block-instance.js";
import { verify } from "node:crypto";
import { toSingle } from "../../prelude/array.js";
import { createHash } from "node:crypto";
import { tickFetch } from "../../metrics.js";
export async function hasSignature(req) {
    const meta = await fetchMeta();
    const required = meta.secureMode || meta.privateMode;
    try {
        httpSignature.parseRequest(req, {
            headers: []
        });
    } catch (e) {
        if (e instanceof Error && e.name === "MissingHeaderError") {
            return required ? "missing" : "optional";
        }
        return "invalid";
    }
    return required ? "supplied" : "unneeded";
}
export async function checkFetch(req) {
    const meta = await fetchMeta();
    if (meta.secureMode || meta.privateMode) {
        if (req.headers.host !== config.host) return 400;
        let signature;
        try {
            signature = httpSignature.parseRequest(req, {
                headers: [
                    "(request-target)",
                    "host",
                    "date"
                ],
                authorizationHeaderName: 'signature'
            });
        } catch (e) {
            return 401;
        }
        const keyId = new URL(signature.keyId);
        const host = toPuny(keyId.hostname);
        if (await shouldBlockInstance(host, meta)) {
            return 403;
        }
        if (meta.privateMode && host !== config.host && host !== config.domain && !meta.allowedHosts.includes(host)) {
            return 403;
        }
        const keyIdLower = signature.keyId.toLowerCase();
        if (keyIdLower.startsWith("acct:")) {
            // Old keyId is no longer supported.
            return 401;
        }
        const dbResolver = new DbResolver();
        // HTTP-Signature keyIdを元にDBから取得
        let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
        // keyIdでわからなければ、resolveしてみる
        if (authUser == null) {
            try {
                keyId.hash = "";
                authUser = await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
            } catch (e) {
                // できなければ駄目
                return 403;
            }
        }
        // publicKey がなくても終了
        if (authUser?.key == null) {
            return 403;
        }
        // Cannot authenticate against local user
        if (authUser.user.uri === null || authUser.user.host === null) {
            return 400;
        }
        // Check if keyId hostname matches actor hostname
        if (toPuny(new URL(authUser.user.uri).hostname) !== host) {
            return 403;
        }
        // HTTP-Signatureの検証
        let httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
        // If signature validation failed, try refetching the actor
        if (!httpSignatureValidated) {
            authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
            if (authUser.key == null) {
                return 403;
            }
            httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
        }
        if (!httpSignatureValidated) {
            return 403;
        }
        if (!verifySignature(signature, authUser.key)) {
            return 401;
        }
        tickFetch();
        return 200;
    }
    return 200;
}
export async function getSignatureUser(req) {
    const signature = httpSignature.parseRequest(req, {
        headers: []
    });
    const keyId = new URL(signature.keyId);
    const dbResolver = new DbResolver();
    // Retrieve from DB by HTTP-Signature keyId
    const authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
    if (authUser) {
        return authUser;
    }
    // Resolve if failed to retrieve by keyId
    keyId.hash = "";
    return await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
}
export function verifySignature(sig, key) {
    if (![
        'hs2019',
        'rsa-sha256'
    ].includes(sig.algorithm.toLowerCase())) return false;
    try {
        return verify('rsa-sha256', Buffer.from(sig.signingString, 'utf8'), key.keyPem, Buffer.from(sig.params.signature, 'base64'));
    } catch  {
        // Algo not supported
        return false;
    }
}
export function verifyDigest(body, digest) {
    digest = toSingle(digest);
    if (body == null || digest == null || !digest.toLowerCase().startsWith('sha-256=')) return false;
    return createHash('sha256').update(body).digest('base64') === digest.substring(8);
}
