Files
Aether/crates/aether-provider-transport/src/antigravity/auth.rs
fawney19 763ff03a7b refactor: 拆分 gateway 单体为独立 crate,新增 systemd 部署方案
将 gateway 内部的 model-fetch、provider-transport、scheduler-core、
usage-runtime、video-tasks-core 模块提取为独立 crate;重构 gateway
内部模块结构(state/router/cache/data/query 等);移除大量遗留模块
文件;新增 systemd 二进制部署骨架及相关文档;更新前端 usage 相关
API 和组件。
2026-04-05 20:23:16 +08:00

214 lines
5.9 KiB
Rust

use std::collections::BTreeMap;
use serde_json::Value;
use super::super::snapshot::GatewayProviderTransportSnapshot;
pub const ANTIGRAVITY_PROVIDER_TYPE: &str = "antigravity";
pub const ANTIGRAVITY_REQUEST_USER_AGENT: &str = "antigravity";
const ANTIGRAVITY_CLIENT_NAME: &str = "antigravity";
const ANTIGRAVITY_GOOG_API_CLIENT: &str = "gl-node/18.18.2 fire/0.8.6 grpc/1.10.x";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AntigravityRequestAuth {
pub project_id: String,
pub client_version: Option<String>,
pub session_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AntigravityRequestAuthSupport {
Supported(AntigravityRequestAuth),
Unsupported(AntigravityRequestAuthUnsupportedReason),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AntigravityRequestAuthUnsupportedReason {
WrongProviderType,
MissingAuthConfig,
InvalidAuthConfigJson,
ComplexDynamicAuthConfig,
MissingProjectId,
}
pub fn resolve_local_antigravity_request_auth(
transport: &GatewayProviderTransportSnapshot,
) -> AntigravityRequestAuthSupport {
if !transport
.provider
.provider_type
.trim()
.eq_ignore_ascii_case(ANTIGRAVITY_PROVIDER_TYPE)
{
return AntigravityRequestAuthSupport::Unsupported(
AntigravityRequestAuthUnsupportedReason::WrongProviderType,
);
}
let Some(raw_auth_config) = transport
.key
.decrypted_auth_config
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
else {
return AntigravityRequestAuthSupport::Unsupported(
AntigravityRequestAuthUnsupportedReason::MissingAuthConfig,
);
};
let Ok(auth_config) = serde_json::from_str::<Value>(raw_auth_config) else {
return AntigravityRequestAuthSupport::Unsupported(
AntigravityRequestAuthUnsupportedReason::InvalidAuthConfigJson,
);
};
if contains_blocked_auth_fields(&auth_config) {
return AntigravityRequestAuthSupport::Unsupported(
AntigravityRequestAuthUnsupportedReason::ComplexDynamicAuthConfig,
);
}
let Some(project_id) = find_string_by_paths(
&auth_config,
&[
&["project_id"],
&["projectId"],
&["project", "id"],
&["project", "project_id"],
&["project", "projectId"],
&["antigravity", "project_id"],
&["antigravity", "projectId"],
&["metadata", "project_id"],
&["metadata", "projectId"],
],
) else {
return AntigravityRequestAuthSupport::Unsupported(
AntigravityRequestAuthUnsupportedReason::MissingProjectId,
);
};
let client_version = find_string_by_paths(
&auth_config,
&[
&["client_version"],
&["clientVersion"],
&["antigravity", "client_version"],
&["antigravity", "clientVersion"],
&["metadata", "client_version"],
&["metadata", "clientVersion"],
],
);
let session_id = find_string_by_paths(
&auth_config,
&[
&["session_id"],
&["sessionId"],
&["antigravity", "session_id"],
&["antigravity", "sessionId"],
&["metadata", "session_id"],
&["metadata", "sessionId"],
],
);
AntigravityRequestAuthSupport::Supported(AntigravityRequestAuth {
project_id,
client_version,
session_id,
})
}
pub fn build_antigravity_static_identity_headers(
auth: &AntigravityRequestAuth,
) -> BTreeMap<String, String> {
let mut headers = BTreeMap::from([
(
String::from("x-client-name"),
String::from(ANTIGRAVITY_CLIENT_NAME),
),
(
String::from("x-goog-api-client"),
String::from(ANTIGRAVITY_GOOG_API_CLIENT),
),
]);
if let Some(client_version) = auth
.client_version
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
headers.insert(String::from("x-client-version"), client_version.to_string());
}
if let Some(session_id) = auth
.session_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
headers.insert(String::from("x-vscode-sessionid"), session_id.to_string());
}
headers
}
fn find_string_by_paths(value: &Value, paths: &[&[&str]]) -> Option<String> {
for path in paths {
let mut current = value;
let mut matched = true;
for segment in *path {
let Some(next) = current.get(*segment) else {
matched = false;
break;
};
current = next;
}
if !matched {
continue;
}
if let Some(string) = current
.as_str()
.map(str::trim)
.filter(|item| !item.is_empty())
{
return Some(string.to_string());
}
}
None
}
fn contains_blocked_auth_fields(value: &Value) -> bool {
match value {
Value::Object(map) => map.iter().any(|(key, inner)| {
is_blocked_auth_key(key.as_str()) || contains_blocked_auth_fields(inner)
}),
Value::Array(items) => items.iter().any(contains_blocked_auth_fields),
_ => false,
}
}
fn is_blocked_auth_key(key: &str) -> bool {
matches!(
key.trim().to_ascii_lowercase().as_str(),
"private_key"
| "privateKey"
| "private_key_id"
| "privateKeyId"
| "service_account"
| "serviceAccount"
| "service_account_json"
| "serviceAccountJson"
| "service_account_key"
| "serviceAccountKey"
| "credential_source"
| "credentialSource"
| "token_url"
| "tokenUrl"
| "auth_uri"
| "authUri"
| "subject"
| "audience"
)
}