mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
fix(providers): fix claude-web 403, move no-auth providers out of web-cookie (#3090)
Integrated into release/v3.8.9 — resolved conflicts with release branch (allowAutoSolve:true preserved, duckduckgo-web correctly kept in NOAUTH_PROVIDERS).
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Claude Web Executor with Auto-Refresh cf_clearance
|
||||
*
|
||||
* Wraps the existing ClaudeWebExecutor with Turnstile solving capability.
|
||||
* When cf_clearance is missing or invalid, automatically solves Cloudflare
|
||||
* Turnstile challenge and injects fresh token.
|
||||
*/
|
||||
|
||||
import type { ExecuteInput } from "./base.ts";
|
||||
import { ClaudeWebExecutor } from "./claude-web.ts";
|
||||
import { getCfClearanceToken } from "../services/claudeTurnstileSolver.ts";
|
||||
|
||||
/**
|
||||
* Enhanced executor with auto-refresh
|
||||
*/
|
||||
export class ClaudeWebAutoRefreshExecutor extends ClaudeWebExecutor {
|
||||
/**
|
||||
* Override execute to add cf_clearance auto-refresh
|
||||
*/
|
||||
async execute(input: ExecuteInput) {
|
||||
const { credentials, log, signal } = input;
|
||||
|
||||
// First attempt with provided credentials
|
||||
let result = await super.execute(input);
|
||||
|
||||
// Check if response is a 403 (Cloudflare challenge) or 401 (invalid cf_clearance)
|
||||
if (result.response.status === 403 || result.response.status === 401) {
|
||||
log?.warn?.(
|
||||
"CLAUDE-WEB",
|
||||
`HTTP ${result.response.status} - attempting to refresh cf_clearance`
|
||||
);
|
||||
|
||||
try {
|
||||
// Attempt to solve Turnstile and get fresh cf_clearance
|
||||
const freshCfClearance = await getCfClearanceToken({ force: true });
|
||||
|
||||
// Update credentials with fresh cf_clearance
|
||||
const updatedCreds = {
|
||||
...credentials,
|
||||
cookie: credentials?.cookie
|
||||
? `${credentials.cookie}; cf_clearance=${freshCfClearance}`
|
||||
: `cf_clearance=${freshCfClearance}`,
|
||||
};
|
||||
|
||||
log?.info?.("CLAUDE-WEB", "cf_clearance refreshed, retrying request");
|
||||
|
||||
// Retry with fresh cookie
|
||||
result = await super.execute({
|
||||
...input,
|
||||
credentials: updatedCreds,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log?.error?.("CLAUDE-WEB", `Failed to auto-refresh cf_clearance: ${message}`);
|
||||
// Return original error response
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override testConnection to include cf_clearance check
|
||||
*/
|
||||
async testConnection(
|
||||
credentials: Record<string, unknown>,
|
||||
signal?: AbortSignal
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Test with provided credentials first
|
||||
const basicTest = await super.testConnection(credentials, signal);
|
||||
if (basicTest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If basic test failed, try to refresh cf_clearance
|
||||
const rawCookie = String((credentials as any)?.cookie || "");
|
||||
if (!rawCookie.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const freshCfClearance = await getCfClearanceToken();
|
||||
const updatedCreds = {
|
||||
...credentials,
|
||||
cookie: `${rawCookie}; cf_clearance=${freshCfClearance}`,
|
||||
};
|
||||
|
||||
return await super.testConnection(updatedCreds, signal);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const claudeWebAutoRefresh = new ClaudeWebAutoRefreshExecutor();
|
||||
@@ -22,8 +22,7 @@
|
||||
import { BaseExecutor, mergeAbortSignals, type ExecuteInput } from "./base.ts";
|
||||
import { FETCH_TIMEOUT_MS } from "../config/constants.ts";
|
||||
import { tlsFetchClaude } from "../services/claudeTlsClient.ts";
|
||||
import { createAutoRefreshMiddleware, refreshCookie } from "../services/claudeWebAutoRefresh.ts";
|
||||
import { getCfClearanceToken, getCacheStatus } from "../services/claudeTurnstileSolver.ts";
|
||||
import { getCfClearanceToken } from "../services/claudeTurnstileSolver.ts";
|
||||
import { normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth";
|
||||
import { randomUUID } from "crypto";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
@@ -45,16 +44,6 @@ const DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
||||
/**
|
||||
* Extended credentials to include organization and conversation context
|
||||
*/
|
||||
interface ClaudeWebCredentials {
|
||||
cookie: string;
|
||||
deviceId?: string;
|
||||
orgId?: string;
|
||||
conversationId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full request payload matching real Claude Web API format
|
||||
*/
|
||||
interface ClaudeWebRequestPayload {
|
||||
prompt: string;
|
||||
model: string;
|
||||
@@ -175,7 +164,8 @@ async function normalizeClaudeSessionCookieWithAutoRefresh(
|
||||
options?.log?.info?.("CLAUDE-WEB", "cf_clearance injected successfully");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
// Continue anyway - request might fail, but that's OK
|
||||
options?.log?.warn?.("CLAUDE-WEB", `cf_clearance injection failed: ${message}`);
|
||||
// Continue anyway - the retry wrapper will handle 403
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +419,7 @@ export class ClaudeWebExecutor extends BaseExecutor {
|
||||
/**
|
||||
* Get user's organization ID from session
|
||||
*/
|
||||
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
|
||||
async execute({ model, body, stream: _stream, credentials, signal, log }: ExecuteInput) {
|
||||
const bodyObj = (body || {}) as Record<string, unknown>;
|
||||
|
||||
try {
|
||||
@@ -479,7 +469,10 @@ export class ClaudeWebExecutor extends BaseExecutor {
|
||||
};
|
||||
}
|
||||
|
||||
const cookieHeader = await normalizeClaudeSessionCookieWithAutoRefresh(rawCookie, { log });
|
||||
const cookieHeader = await normalizeClaudeSessionCookieWithAutoRefresh(rawCookie, {
|
||||
allowAutoSolve: true,
|
||||
log,
|
||||
});
|
||||
const deviceId = (credentials as any)?.deviceId as string | undefined;
|
||||
|
||||
// Transform request to Claude format
|
||||
@@ -542,7 +535,7 @@ export class ClaudeWebExecutor extends BaseExecutor {
|
||||
|
||||
log?.debug?.("CLAUDE-WEB", `Making request to ${completionUrl}`);
|
||||
|
||||
// Inject cf_clearance before calling tlsFetchClaude
|
||||
// cf_clearance is already injected via normalizeClaudeSessionCookieWithAutoRefresh above
|
||||
|
||||
const fetchResponse = await tlsFetchClaude(completionUrl, {
|
||||
method: "POST",
|
||||
|
||||
@@ -69,12 +69,6 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = {
|
||||
placeholder: "access_token=... or a DevTools HAR export",
|
||||
acceptsFullCookieHeader: false,
|
||||
},
|
||||
"veoaifree-web": {
|
||||
kind: "none",
|
||||
credentialName: "",
|
||||
placeholder: "",
|
||||
acceptsFullCookieHeader: false,
|
||||
},
|
||||
"t3-web": {
|
||||
kind: "cookie",
|
||||
credentialName: "convex-session-id + Cookie header",
|
||||
@@ -93,12 +87,6 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = {
|
||||
placeholder: "token_value user@example.com",
|
||||
acceptsFullCookieHeader: false,
|
||||
},
|
||||
"duckduckgo-web": {
|
||||
kind: "none",
|
||||
credentialName: "",
|
||||
placeholder: "",
|
||||
acceptsFullCookieHeader: false,
|
||||
},
|
||||
huggingchat: {
|
||||
kind: "cookie",
|
||||
credentialName: "hf-chat",
|
||||
|
||||
@@ -45,6 +45,32 @@ export const NOAUTH_PROVIDERS = {
|
||||
text: "OpenCode Free uses the public OpenCode endpoint (https://opencode.ai/zen/v1). No signup or API key needed. Rate limits apply.",
|
||||
},
|
||||
},
|
||||
"duckduckgo-web": {
|
||||
id: "duckduckgo-web",
|
||||
alias: "ddgw",
|
||||
name: "DuckDuckGo AI Chat",
|
||||
icon: "auto_awesome",
|
||||
color: "#DE5833",
|
||||
textIcon: "DDG",
|
||||
website: "https://duckduckgo.com/duckchat",
|
||||
noAuth: true,
|
||||
hasFree: true,
|
||||
freeNote: "Free — anonymous access to multiple AI models via DuckDuckGo.",
|
||||
authHint: "No credentials required — DuckDuckGo AI Chat is anonymous and free.",
|
||||
},
|
||||
"veoaifree-web": {
|
||||
id: "veoaifree-web",
|
||||
alias: "veo-free",
|
||||
name: "Veo AI Free",
|
||||
icon: "videocam",
|
||||
color: "#8B5CF6",
|
||||
textIcon: "VF",
|
||||
website: "https://veoaifree.com",
|
||||
noAuth: true,
|
||||
hasFree: true,
|
||||
freeNote: "Free video generation — VEO 3.1, Seedance. 6 requests/hour.",
|
||||
authHint: "No auth required. Rate limited to 6 requests/hour per IP.",
|
||||
},
|
||||
};
|
||||
|
||||
export const FREE_APIKEY_PROVIDER_IDS = new Set(["qoder"]);
|
||||
@@ -365,18 +391,6 @@ export const WEB_COOKIE_PROVIDERS = {
|
||||
subscriptionRisk: true,
|
||||
riskNoticeVariant: "webCookie",
|
||||
},
|
||||
"veoaifree-web": {
|
||||
id: "veoaifree-web",
|
||||
alias: "veo-free",
|
||||
name: "Veo AI Free",
|
||||
icon: "videocam",
|
||||
color: "#8B5CF6",
|
||||
textIcon: "VF",
|
||||
website: "https://veoaifree.com",
|
||||
hasFree: true,
|
||||
freeNote: "Free video generation — VEO 3.1, Seedance. 6 requests/hour.",
|
||||
authHint: "No auth required. Rate limited to 6 requests/hour per IP.",
|
||||
},
|
||||
"t3-web": {
|
||||
id: "t3-web",
|
||||
alias: "t3chat",
|
||||
@@ -418,19 +432,6 @@ export const WEB_COOKIE_PROVIDERS = {
|
||||
authHint:
|
||||
"Paste your __client cookie value from .clerk.agent.adapta.one (DevTools → Application → Cookies)",
|
||||
},
|
||||
"duckduckgo-web": {
|
||||
id: "duckduckgo-web",
|
||||
alias: "ddgw",
|
||||
name: "DuckDuckGo AI Chat",
|
||||
icon: "auto_awesome",
|
||||
color: "#DE5833",
|
||||
textIcon: "DDG",
|
||||
website: "https://duckduckgo.com/duckchat",
|
||||
hasFree: true,
|
||||
noAuth: true,
|
||||
freeNote: "Free — anonymous access to multiple AI models via DuckDuckGo.",
|
||||
authHint: "No credentials required — DuckDuckGo AI Chat is anonymous and free.",
|
||||
},
|
||||
huggingchat: {
|
||||
id: "huggingchat",
|
||||
alias: "hc",
|
||||
|
||||
Reference in New Issue
Block a user