mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 20:19:51 +08:00
refactor: 大规模模块拆分与重组,新增 aether-admin crate
- 新建独立 aether-admin crate 承载 admin 相关共享契约与纯辅助函数 - 拆分 ai_pipeline 下 kiro/private_envelope/conversion/planner 等大文件为子模块目录 - 重组 admin handlers 各业务域(billing/oauth/provider/system/users 等)为目录结构,移除 shared.rs/builders.rs 等反模式 - 移除 ai_pipeline runtime adapters 旧实现(claude/openai/gemini/kiro/vertex/antigravity 等),改由 provider transport 统一承载 - 移除 control_facade/execution_facade/auth_snapshot_facade 等冗余 facade 层 - 拆分 query/billing 与 query/monitoring 模块、state/runtime/payments 与 security 模块 - 扩展架构测试覆盖 admin_billing/admin_model/admin_users 等新模块 - 删除 docs/architecture/refactor-execution-plan.md 已完成的执行计划文档
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
pub(crate) use aether_admin::provider::state::{
|
||||
decode_jwt_claims, enrich_admin_provider_oauth_auth_config, json_non_empty_string,
|
||||
json_u64_value,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::enrich_admin_provider_oauth_auth_config;
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||
use serde_json::json;
|
||||
|
||||
fn sample_unsigned_jwt(payload: serde_json::Value) -> String {
|
||||
let header = URL_SAFE_NO_PAD.encode(r#"{"alg":"none","typ":"JWT"}"#);
|
||||
let payload = URL_SAFE_NO_PAD.encode(payload.to_string());
|
||||
format!("{header}.{payload}.sig")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_enrichment_extracts_identity_from_nested_auth_claims() {
|
||||
let access_token = sample_unsigned_jwt(json!({
|
||||
"email": "[email protected]",
|
||||
"https://api.openai.com/auth": {
|
||||
"chatgpt_account_id": "acc-1",
|
||||
"chatgpt_account_user_id": "user-1__acc-1",
|
||||
"chatgpt_plan_type": "team",
|
||||
"chatgpt_user_id": "user-1",
|
||||
"organizations": [
|
||||
{"id": "org-1", "title": "Personal", "is_default": true}
|
||||
],
|
||||
}
|
||||
}));
|
||||
let token_payload = json!({
|
||||
"access_token": access_token,
|
||||
});
|
||||
let mut auth_config = serde_json::Map::new();
|
||||
|
||||
enrich_admin_provider_oauth_auth_config("codex", &mut auth_config, &token_payload);
|
||||
|
||||
assert_eq!(auth_config.get("email"), Some(&json!("[email protected]")));
|
||||
assert_eq!(auth_config.get("account_id"), Some(&json!("acc-1")));
|
||||
assert_eq!(
|
||||
auth_config.get("account_user_id"),
|
||||
Some(&json!("user-1__acc-1"))
|
||||
);
|
||||
assert_eq!(auth_config.get("plan_type"), Some(&json!("team")));
|
||||
assert_eq!(auth_config.get("user_id"), Some(&json!("user-1")));
|
||||
assert_eq!(
|
||||
auth_config.get("organizations"),
|
||||
Some(&json!([
|
||||
{"id": "org-1", "title": "Personal", "is_default": true}
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_enrichment_normalizes_direct_chatgpt_alias_fields() {
|
||||
let token_payload = json!({
|
||||
"email": "[email protected]",
|
||||
"chatgpt_account_id": "acc-2",
|
||||
"chatgpt_account_user_id": "user-2__acc-2",
|
||||
"chatgpt_plan_type": "plus",
|
||||
"chatgpt_user_id": "user-2",
|
||||
});
|
||||
let mut auth_config = serde_json::Map::new();
|
||||
|
||||
enrich_admin_provider_oauth_auth_config("codex", &mut auth_config, &token_payload);
|
||||
|
||||
assert_eq!(auth_config.get("email"), Some(&json!("[email protected]")));
|
||||
assert_eq!(auth_config.get("account_id"), Some(&json!("acc-2")));
|
||||
assert_eq!(
|
||||
auth_config.get("account_user_id"),
|
||||
Some(&json!("user-2__acc-2"))
|
||||
);
|
||||
assert_eq!(auth_config.get("plan_type"), Some(&json!("plus")));
|
||||
assert_eq!(auth_config.get("user_id"), Some(&json!("user-2")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
use super::super::errors::{
|
||||
build_internal_control_error_response, normalize_provider_oauth_refresh_error_message,
|
||||
};
|
||||
use super::json_non_empty_string;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminProviderOAuthTemplate};
|
||||
use axum::{body::Body, http, response::Response};
|
||||
|
||||
pub(crate) async fn exchange_admin_provider_oauth_code(
|
||||
state: &AdminAppState<'_>,
|
||||
template: AdminProviderOAuthTemplate,
|
||||
code: &str,
|
||||
state_nonce: &str,
|
||||
pkce_verifier: Option<&str>,
|
||||
) -> Result<serde_json::Value, Response<Body>> {
|
||||
let token_url = state.provider_oauth_token_url(template.provider_type, template.token_url);
|
||||
let request = state.http_client().post(token_url);
|
||||
let response = if template.provider_type == "claude_code" {
|
||||
let mut body = serde_json::Map::from_iter([
|
||||
(
|
||||
"grant_type".to_string(),
|
||||
serde_json::Value::String("authorization_code".to_string()),
|
||||
),
|
||||
(
|
||||
"client_id".to_string(),
|
||||
serde_json::Value::String(template.client_id.to_string()),
|
||||
),
|
||||
(
|
||||
"redirect_uri".to_string(),
|
||||
serde_json::Value::String(template.redirect_uri.to_string()),
|
||||
),
|
||||
(
|
||||
"code".to_string(),
|
||||
serde_json::Value::String(code.to_string()),
|
||||
),
|
||||
(
|
||||
"state".to_string(),
|
||||
serde_json::Value::String(state_nonce.to_string()),
|
||||
),
|
||||
]);
|
||||
if let Some(verifier) = pkce_verifier {
|
||||
body.insert(
|
||||
"code_verifier".to_string(),
|
||||
serde_json::Value::String(verifier.to_string()),
|
||||
);
|
||||
}
|
||||
request
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.json(&serde_json::Value::Object(body))
|
||||
.send()
|
||||
.await
|
||||
} else {
|
||||
let mut form = vec![
|
||||
("grant_type", "authorization_code".to_string()),
|
||||
("client_id", template.client_id.to_string()),
|
||||
("redirect_uri", template.redirect_uri.to_string()),
|
||||
("code", code.to_string()),
|
||||
];
|
||||
if !template.client_secret.trim().is_empty() {
|
||||
form.push(("client_secret", template.client_secret.to_string()));
|
||||
}
|
||||
if let Some(verifier) = pkce_verifier {
|
||||
form.push(("code_verifier", verifier.to_string()));
|
||||
}
|
||||
request
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Accept", "application/json")
|
||||
.form(&form)
|
||||
.send()
|
||||
.await
|
||||
}
|
||||
.map_err(|_| {
|
||||
build_internal_control_error_response(http::StatusCode::BAD_REQUEST, "token exchange 失败")
|
||||
})?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"token exchange 失败",
|
||||
));
|
||||
}
|
||||
|
||||
let payload = response.json::<serde_json::Value>().await.map_err(|_| {
|
||||
build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"token exchange 返回缺少 access_token",
|
||||
)
|
||||
})?;
|
||||
if json_non_empty_string(payload.get("access_token")).is_none() {
|
||||
return Err(build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"token exchange 返回缺少 access_token",
|
||||
));
|
||||
}
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
pub(crate) async fn exchange_admin_provider_oauth_refresh_token(
|
||||
state: &AdminAppState<'_>,
|
||||
template: AdminProviderOAuthTemplate,
|
||||
refresh_token: &str,
|
||||
) -> Result<serde_json::Value, Response<Body>> {
|
||||
let token_url = state.provider_oauth_token_url(template.provider_type, template.token_url);
|
||||
let request = state.http_client().post(token_url);
|
||||
let scope = template.scopes.join(" ");
|
||||
let response = if template.provider_type == "claude_code" {
|
||||
let mut body = serde_json::Map::from_iter([
|
||||
(
|
||||
"grant_type".to_string(),
|
||||
serde_json::Value::String("refresh_token".to_string()),
|
||||
),
|
||||
(
|
||||
"client_id".to_string(),
|
||||
serde_json::Value::String(template.client_id.to_string()),
|
||||
),
|
||||
(
|
||||
"refresh_token".to_string(),
|
||||
serde_json::Value::String(refresh_token.to_string()),
|
||||
),
|
||||
]);
|
||||
if !scope.trim().is_empty() {
|
||||
body.insert("scope".to_string(), serde_json::Value::String(scope));
|
||||
}
|
||||
request
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.json(&serde_json::Value::Object(body))
|
||||
.send()
|
||||
.await
|
||||
} else {
|
||||
let mut form = vec![
|
||||
("grant_type", "refresh_token".to_string()),
|
||||
("client_id", template.client_id.to_string()),
|
||||
("refresh_token", refresh_token.to_string()),
|
||||
];
|
||||
if !scope.trim().is_empty() {
|
||||
form.push(("scope", scope));
|
||||
}
|
||||
if !template.client_secret.trim().is_empty() {
|
||||
form.push(("client_secret", template.client_secret.to_string()));
|
||||
}
|
||||
request
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Accept", "application/json")
|
||||
.form(&form)
|
||||
.send()
|
||||
.await
|
||||
}
|
||||
.map_err(|_| {
|
||||
build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"Refresh Token 验证失败: token exchange 失败",
|
||||
)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
let body = response.text().await.map_err(|_| {
|
||||
build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"Refresh Token 验证失败: token exchange 失败",
|
||||
)
|
||||
})?;
|
||||
if !status.is_success() {
|
||||
let reason =
|
||||
normalize_provider_oauth_refresh_error_message(Some(status.as_u16()), Some(&body));
|
||||
return Err(build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
format!("Refresh Token 验证失败: {reason}"),
|
||||
));
|
||||
}
|
||||
|
||||
let payload = serde_json::from_str::<serde_json::Value>(&body).map_err(|_| {
|
||||
build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"token refresh 返回缺少 access_token",
|
||||
)
|
||||
})?;
|
||||
if json_non_empty_string(payload.get("access_token")).is_none() {
|
||||
return Err(build_internal_control_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"token refresh 返回缺少 access_token",
|
||||
));
|
||||
}
|
||||
Ok(payload)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
mod auth_config;
|
||||
mod exchange;
|
||||
mod storage;
|
||||
mod template;
|
||||
|
||||
pub(crate) use self::auth_config::enrich_admin_provider_oauth_auth_config;
|
||||
pub(crate) use self::exchange::{
|
||||
exchange_admin_provider_oauth_code, exchange_admin_provider_oauth_refresh_token,
|
||||
};
|
||||
pub(crate) use self::storage::build_provider_oauth_start_response;
|
||||
pub(crate) use self::template::{
|
||||
admin_provider_oauth_template, build_admin_provider_oauth_backend_unavailable_response,
|
||||
build_admin_provider_oauth_supported_types_payload, is_fixed_provider_type_for_provider_oauth,
|
||||
};
|
||||
pub(crate) use aether_admin::provider::state::{
|
||||
build_kiro_device_key_name, current_unix_secs, decode_jwt_claims, default_kiro_device_region,
|
||||
default_kiro_device_start_url, generate_provider_oauth_nonce,
|
||||
generate_provider_oauth_pkce_verifier, json_non_empty_string, json_u64_value,
|
||||
normalize_kiro_device_region, parse_provider_oauth_callback_params, provider_oauth_pkce_s256,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::handlers::admin::request::AdminProviderOAuthTemplate;
|
||||
use serde_json::json;
|
||||
use url::form_urlencoded;
|
||||
|
||||
pub(crate) fn build_provider_oauth_start_response(
|
||||
template: AdminProviderOAuthTemplate,
|
||||
nonce: &str,
|
||||
code_challenge: Option<&str>,
|
||||
) -> serde_json::Value {
|
||||
let mut serializer = form_urlencoded::Serializer::new(String::new());
|
||||
serializer.append_pair("client_id", template.client_id);
|
||||
serializer.append_pair("response_type", "code");
|
||||
serializer.append_pair("redirect_uri", template.redirect_uri);
|
||||
serializer.append_pair("scope", &template.scopes.join(" "));
|
||||
serializer.append_pair("state", nonce);
|
||||
if template.provider_type == "codex" {
|
||||
serializer.append_pair("prompt", "login");
|
||||
serializer.append_pair("id_token_add_organizations", "true");
|
||||
serializer.append_pair("codex_cli_simplified_flow", "true");
|
||||
}
|
||||
if template.use_pkce {
|
||||
if let Some(code_challenge) = code_challenge {
|
||||
serializer.append_pair("code_challenge", code_challenge);
|
||||
serializer.append_pair("code_challenge_method", "S256");
|
||||
}
|
||||
}
|
||||
|
||||
json!({
|
||||
"authorization_url": format!("{}?{}", template.authorize_url, serializer.finish()),
|
||||
"redirect_uri": template.redirect_uri,
|
||||
"provider_type": template.provider_type,
|
||||
"instructions": "1) 打开 authorization_url 完成授权\n2) 授权后会跳转到 redirect_uri(localhost)\n3) 复制浏览器地址栏完整 URL,调用 complete 接口粘贴 callback_url",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use super::super::errors::build_internal_control_error_response;
|
||||
use crate::handlers::admin::provider::shared::support::ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL;
|
||||
use crate::handlers::admin::request::{
|
||||
admin_provider_oauth_template as request_admin_provider_oauth_template,
|
||||
admin_provider_oauth_template_types,
|
||||
is_fixed_provider_type_for_admin_oauth as request_is_fixed_provider_type_for_admin_oauth,
|
||||
AdminProviderOAuthTemplate,
|
||||
};
|
||||
use axum::{body::Body, http, response::Response};
|
||||
use serde_json::json;
|
||||
|
||||
pub(crate) fn is_fixed_provider_type_for_provider_oauth(provider_type: &str) -> bool {
|
||||
request_is_fixed_provider_type_for_admin_oauth(provider_type)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_template(
|
||||
provider_type: &str,
|
||||
) -> Option<AdminProviderOAuthTemplate> {
|
||||
request_admin_provider_oauth_template(provider_type)
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_provider_oauth_supported_types_payload() -> Vec<serde_json::Value> {
|
||||
admin_provider_oauth_template_types()
|
||||
.filter_map(|provider_type| admin_provider_oauth_template(provider_type))
|
||||
.map(|template| {
|
||||
json!({
|
||||
"provider_type": template.provider_type,
|
||||
"display_name": template.display_name,
|
||||
"scopes": template.scopes,
|
||||
"redirect_uri": template.redirect_uri,
|
||||
"authorize_url": template.authorize_url,
|
||||
"token_url": template.token_url,
|
||||
"use_pkce": template.use_pkce,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_provider_oauth_backend_unavailable_response() -> Response<Body> {
|
||||
build_internal_control_error_response(
|
||||
http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user