mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix: proxy direct APNs HTTP2 sessions
This commit is contained in:
@@ -467,7 +467,31 @@ describe("proxy validation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts APNs 403 reachability even when apns-id is unavailable", async () => {
|
||||
it("accepts APNs 403 reachability with InvalidProviderToken when apns-id is unavailable", async () => {
|
||||
const result = await runProxyValidation({
|
||||
config: {
|
||||
enabled: true,
|
||||
proxyUrl: "http://127.0.0.1:3128",
|
||||
},
|
||||
env: {},
|
||||
allowedUrls: [],
|
||||
deniedUrls: [],
|
||||
apnsReachability: true,
|
||||
apnsCheck: vi.fn().mockResolvedValue({ status: 403, apnsReason: "InvalidProviderToken" }),
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.checks).toEqual([
|
||||
{
|
||||
kind: "apns",
|
||||
url: "https://api.sandbox.push.apple.com",
|
||||
ok: true,
|
||||
status: 403,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("fails APNs reachability when bare 403 has no APNs proof", async () => {
|
||||
const result = await runProxyValidation({
|
||||
config: {
|
||||
enabled: true,
|
||||
@@ -480,13 +504,13 @@ describe("proxy validation", () => {
|
||||
apnsCheck: vi.fn().mockResolvedValue({ status: 403 }),
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.checks).toEqual([
|
||||
{
|
||||
kind: "apns",
|
||||
url: "https://api.sandbox.push.apple.com",
|
||||
ok: true,
|
||||
status: 403,
|
||||
ok: false,
|
||||
error: expect.stringContaining("InvalidProviderToken"),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ export const DEFAULT_PROXY_VALIDATION_APNS_AUTHORITY = "https://api.sandbox.push
|
||||
|
||||
const DEFAULT_PROXY_VALIDATION_TIMEOUT_MS = 5000;
|
||||
const DENIED_CANARY_HEADER = "x-openclaw-proxy-validation-canary";
|
||||
const APNS_REACHABILITY_REASON = "InvalidProviderToken";
|
||||
|
||||
export type ProxyValidationConfigSource = "override" | "config" | "env" | "missing" | "disabled";
|
||||
|
||||
@@ -62,6 +63,8 @@ export type ProxyValidationApnsCheckResult = {
|
||||
status: number;
|
||||
/** Present when the response originated from a real APNs server (Apple always returns this UUID). */
|
||||
apnsId?: string;
|
||||
/** APNs JSON error reason. InvalidProviderToken proves the invalid-token probe reached APNs. */
|
||||
apnsReason?: string;
|
||||
};
|
||||
|
||||
export type ProxyValidationApnsCheck = (
|
||||
@@ -203,7 +206,31 @@ async function defaultProxyValidationApnsCheck({
|
||||
timeoutMs,
|
||||
}: ProxyValidationApnsCheckParams): Promise<ProxyValidationApnsCheckResult> {
|
||||
const result = await probeApnsHttp2ReachabilityViaProxy({ proxyUrl, authority, timeoutMs });
|
||||
return { status: result.status, apnsId: result.responseHeaders?.["apns-id"] };
|
||||
return {
|
||||
status: result.status,
|
||||
apnsId: result.responseHeaders?.["apns-id"],
|
||||
apnsReason: parseApnsErrorReason(result.body),
|
||||
};
|
||||
}
|
||||
|
||||
function parseApnsErrorReason(body: string): string | undefined {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(body);
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const reason = (parsed as { reason?: unknown }).reason;
|
||||
return typeof reason === "string" && reason.trim() ? reason : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function hasApnsReachabilityProof(result: ProxyValidationApnsCheckResult): boolean {
|
||||
if (result.apnsId) {
|
||||
return true;
|
||||
}
|
||||
return result.status === 403 && result.apnsReason === APNS_REACHABILITY_REASON;
|
||||
}
|
||||
|
||||
function normalizeTimeoutMs(value: number | undefined): number {
|
||||
@@ -422,13 +449,13 @@ async function runApnsReachabilityCheck(params: {
|
||||
authority: params.authority,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
if (!result.apnsId && result.status !== 403) {
|
||||
if (!hasApnsReachabilityProof(result)) {
|
||||
return {
|
||||
kind: "apns",
|
||||
url: params.authority,
|
||||
ok: false,
|
||||
error:
|
||||
"APNs reachability check failed: response was not a 403 and did not include an apns-id header. " +
|
||||
"APNs reachability check failed: response did not include an apns-id header or APNs InvalidProviderToken body. " +
|
||||
"The proxy may be intercepting the connection instead of tunneling it.",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user