export const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
export const PROTECTED_RESOURCE_METADATA_CACHE_CONTROL = "public, max-age=300";
function isBearerChallengeErrorCode(value) {
    return value === "invalid_token" || value === "insufficient_scope";
}
function isStringArray(value) {
    return Array.isArray(value) && value.every((item) => typeof item === "string");
}
export class TokenVerificationError extends Error {
    error;
    errorDescription;
    scope;
    constructor(input) {
        super(input.errorDescription ?? input.error);
        this.name = "TokenVerificationError";
        this.error = input.error;
        this.errorDescription = input.errorDescription;
        this.scope = input.scope;
    }
}
function toUrlString(value) {
    return value instanceof URL ? value.toString() : value;
}
function readSingleHeaderValue(value) {
    return Array.isArray(value) ? value[0] : value;
}
function getRequestProtocol(req, trustedProxy = false) {
    if (trustedProxy) {
        const forwardedProto = readSingleHeaderValue(req.headers["x-forwarded-proto"])
            ?.split(",")[0]
            ?.trim()
            .toLowerCase();
        if (forwardedProto === "https" || forwardedProto === "http") {
            return forwardedProto;
        }
    }
    return "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
}
function getRequestHost(req, trustedProxy = false) {
    if (trustedProxy) {
        const forwardedHost = readSingleHeaderValue(req.headers["x-forwarded-host"])
            ?.split(",")[0]
            ?.trim();
        if (forwardedHost !== undefined && forwardedHost.length > 0) {
            return forwardedHost;
        }
    }
    return readSingleHeaderValue(req.headers.host) ?? "127.0.0.1";
}
function escapeChallengeValue(value) {
    return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
}
function normalizeProtectedResourcePath(path) {
    if (path === undefined || path.length === 0 || path === "/") {
        return "";
    }
    const normalizedPath = path.startsWith("/") ? path : `/${path}`;
    if (normalizedPath.length > 1 && normalizedPath.endsWith("/")) {
        return normalizedPath.slice(0, -1);
    }
    return normalizedPath;
}
function formatScope(scope) {
    if (scope.length === 0) {
        return undefined;
    }
    return scope.join(" ");
}
function hasBearerTokenWhitespace(value) {
    for (const character of value) {
        if (character === " "
            || character === "\t"
            || character === "\n"
            || character === "\r"
            || character === "\f"
            || character === "\v") {
            return true;
        }
    }
    return false;
}
function readBearerToken(req) {
    const authorization = readSingleHeaderValue(req.headers.authorization);
    if (authorization === undefined || authorization.length === 0) {
        return { kind: "missing" };
    }
    const separatorIndex = authorization.indexOf(" ");
    if (separatorIndex <= 0) {
        return {
            kind: "malformed",
            errorDescription: "malformed bearer token",
        };
    }
    const scheme = authorization.slice(0, separatorIndex);
    const token = authorization.slice(separatorIndex + 1).trim();
    if (scheme.toLowerCase() !== "bearer" || token.length === 0 || hasBearerTokenWhitespace(token)) {
        return {
            kind: "malformed",
            errorDescription: "malformed bearer token",
        };
    }
    return { kind: "token", token };
}
function normalizeTokenVerificationError(error, requiredScopes) {
    if (typeof error === "object" && error !== null) {
        const challengeError = error;
        if (isBearerChallengeErrorCode(challengeError.error)
            && (challengeError.errorDescription === undefined
                || typeof challengeError.errorDescription === "string")
            && (challengeError.scope === undefined || isStringArray(challengeError.scope))) {
            const scope = challengeError.scope ?? [];
            return {
                error: challengeError.error,
                ...(challengeError.errorDescription === undefined
                    ? {}
                    : {
                        errorDescription: challengeError.errorDescription,
                    }),
                scope: scope.length > 0
                    ? [...scope]
                    : challengeError.error === "insufficient_scope"
                        ? requiredScopes
                        : undefined,
            };
        }
    }
    if (error instanceof TokenVerificationError) {
        return {
            error: error.error,
            errorDescription: error.errorDescription,
            scope: error.scope ?? (error.error === "insufficient_scope" ? requiredScopes : undefined),
        };
    }
    if (error instanceof Error) {
        return {
            error: "invalid_token",
            errorDescription: "token verification failed",
        };
    }
    return {
        error: "invalid_token",
        errorDescription: "token verification failed",
    };
}
function toRequestAuthInfo(verifiedToken, resource) {
    const audience = [...verifiedToken.audience];
    const scopes = [...verifiedToken.scopes];
    const claims = { ...verifiedToken.claims };
    return {
        ...verifiedToken,
        audience,
        clientId: verifiedToken.clientId ?? "",
        scopes,
        claims,
        resource: new URL(resource),
        extra: {
            issuer: verifiedToken.issuer,
            audience,
            claims,
            ...(verifiedToken.subject === undefined
                ? {}
                : {
                    subject: verifiedToken.subject,
                }),
        },
    };
}
export function getProtectedResourceMetadataUrl(req, protectedResourcePath, trustedProxy = false) {
    const path = `${PROTECTED_RESOURCE_METADATA_PATH}${normalizeProtectedResourcePath(protectedResourcePath)}`;
    const protocol = getRequestProtocol(req, trustedProxy);
    try {
        return new URL(path, `${protocol}://${getRequestHost(req, trustedProxy)}`).toString();
    }
    catch {
        return new URL(path, `${protocol}://127.0.0.1`).toString();
    }
}
export function createBearerChallenge(req, options = {}, protectedResourcePath, trustedProxy = false) {
    const parts = [
        'Bearer realm="mcp"',
        `resource_metadata="${escapeChallengeValue(getProtectedResourceMetadataUrl(req, protectedResourcePath, trustedProxy))}"`,
    ];
    if (options.error !== undefined) {
        parts.push(`error="${escapeChallengeValue(options.error)}"`);
    }
    if (options.errorDescription !== undefined) {
        parts.push(`error_description="${escapeChallengeValue(options.errorDescription)}"`);
    }
    const scope = options.scope === undefined ? undefined : formatScope(options.scope);
    if (scope !== undefined) {
        parts.push(`scope="${escapeChallengeValue(scope)}"`);
    }
    return parts.join(", ");
}
export async function authorizeBearerRequest(req, options) {
    const authorization = readBearerToken(req);
    if (authorization.kind === "missing") {
        return {
            ok: false,
            statusCode: 401,
            challenge: createBearerChallenge(req, {}, options.protectedResourcePath, options.trustedProxy),
        };
    }
    if (authorization.kind === "malformed") {
        return {
            ok: false,
            statusCode: 401,
            challenge: createBearerChallenge(req, {
                error: "invalid_token",
                errorDescription: authorization.errorDescription,
            }, options.protectedResourcePath, options.trustedProxy),
        };
    }
    const requiredScopes = options.requiredScopes ?? [];
    try {
        const verifiedToken = await options.verifier.verify({
            token: authorization.token,
            resource: toUrlString(options.resource),
            authorizationServers: options.authorizationServers.map(toUrlString),
            requiredScopes,
        });
        const auth = toRequestAuthInfo(verifiedToken, toUrlString(options.resource));
        req.auth = auth;
        return {
            ok: true,
            auth,
        };
    }
    catch (error) {
        const challengeOptions = normalizeTokenVerificationError(error, requiredScopes);
        return {
            ok: false,
            statusCode: challengeOptions.error === "insufficient_scope" ? 403 : 401,
            challenge: createBearerChallenge(req, challengeOptions, options.protectedResourcePath, options.trustedProxy),
        };
    }
}
