|
/** |
|
* Config |
|
* API_KEYS[0] is Master Key |
|
*/ |
|
const API_KEYS = [ |
|
"TOKEN1", |
|
"TOKEN2", |
|
]; |
|
|
|
const TARGET_HOST = "generativelanguage.googleapis.com"; |
|
|
|
async function getHash(text) { |
|
const encoder = new TextEncoder(); |
|
const data = encoder.encode(text); |
|
return await crypto.subtle.digest('SHA-256', data); |
|
} |
|
|
|
function safeCompareBuffers(buf1, buf2) { |
|
if (buf1.byteLength !== buf2.byteLength) return false; |
|
return crypto.subtle.timingSafeEqual(buf1, buf2); |
|
} |
|
|
|
let MASTER_HASH_CACHE = null; |
|
|
|
export default { |
|
async fetch(request, env, ctx) { |
|
// CORS |
|
if (request.method === "OPTIONS") { |
|
return new Response(null, { |
|
headers: { |
|
"Access-Control-Allow-Origin": "*", |
|
"Access-Control-Allow-Methods": "POST, GET, OPTIONS", |
|
"Access-Control-Allow-Headers": "Content-Type, Authorization", |
|
}, |
|
}); |
|
} |
|
|
|
const errorResponse = (status, code, message, type = "api_error") => { |
|
return new Response(JSON.stringify({ |
|
error: { |
|
message: message, |
|
type: type, |
|
param: null, |
|
code: code |
|
} |
|
}), { |
|
status: status, |
|
headers: { "Content-Type": "application/json; charset=utf-8" } |
|
}); |
|
}; |
|
|
|
const authHeader = request.headers.get("Authorization"); |
|
|
|
if (!authHeader) { |
|
return errorResponse(401, "invalid_api_key", "Missing Authorization header."); |
|
} |
|
|
|
if (!authHeader.startsWith("Bearer ")) { |
|
return errorResponse(401, "invalid_api_key", "Invalid Authorization header format."); |
|
} |
|
|
|
const clientToken = authHeader.slice(7).trim(); |
|
const masterKey = API_KEYS[0]; |
|
|
|
if (!MASTER_HASH_CACHE) { |
|
MASTER_HASH_CACHE = await getHash(masterKey); |
|
} |
|
|
|
try { |
|
const clientHash = await getHash(clientToken); |
|
if (!safeCompareBuffers(clientHash, MASTER_HASH_CACHE)) { |
|
return errorResponse(401, "invalid_api_key", "Incorrect API key provided."); |
|
} |
|
} catch (e) { |
|
return errorResponse(500, "internal_error", "Authentication processing failed."); |
|
} |
|
|
|
const requestBody = await request.text(); |
|
const url = new URL(request.url); |
|
url.hostname = TARGET_HOST; |
|
url.protocol = "https:"; |
|
|
|
let keyPool = [...API_KEYS]; |
|
let lastResponse = null; |
|
|
|
while (keyPool.length > 0) { |
|
const randomIndex = Math.floor(Math.random() * keyPool.length); |
|
const currentKey = keyPool[randomIndex]; |
|
keyPool.splice(randomIndex, 1); |
|
|
|
const newHeaders = new Headers(request.headers); |
|
newHeaders.set("Authorization", `Bearer ${currentKey}`); |
|
newHeaders.set("Host", TARGET_HOST); |
|
|
|
try { |
|
const response = await fetch(url.toString(), { |
|
method: request.method, |
|
headers: newHeaders, |
|
body: requestBody, |
|
}); |
|
|
|
if (response.status === 429 && keyPool.length > 0) { |
|
lastResponse = response; |
|
continue; |
|
} |
|
|
|
const newResponse = new Response(response.body, response); |
|
newResponse.headers.set("Access-Control-Allow-Origin", "*"); |
|
return newResponse; |
|
|
|
} catch (e) { |
|
if (keyPool.length === 0) { |
|
return errorResponse(502, "bad_gateway", `Upstream error: ${e.message}`); |
|
} |
|
} |
|
} |
|
|
|
if (lastResponse) { |
|
const newResponse = new Response(lastResponse.body, lastResponse); |
|
newResponse.headers.set("Access-Control-Allow-Origin", "*"); |
|
return newResponse; |
|
} |
|
|
|
return errorResponse(503, "service_unavailable", "All API keys exhausted."); |
|
}, |
|
}; |