mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +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:
@@ -4,13 +4,12 @@ use super::super::users::{
|
||||
normalize_admin_optional_api_key_name, normalize_admin_user_api_formats,
|
||||
normalize_admin_user_string_list,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks, query_param_bool,
|
||||
query_param_optional_bool, query_param_value,
|
||||
};
|
||||
use crate::handlers::admin::system::shared::configs::serialize_admin_system_users_export_wallet;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -19,8 +18,6 @@ use axum::{
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
const ADMIN_API_KEYS_DATA_UNAVAILABLE_DETAIL: &str = "Admin standalone API key data unavailable";
|
||||
|
||||
mod mutation_routes;
|
||||
mod read_routes;
|
||||
mod routes;
|
||||
@@ -41,8 +38,8 @@ use self::shared::{
|
||||
};
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_api_keys_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&axum::body::Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
routes::maybe_build_local_admin_api_keys_routes_response(state, request_context, request_body)
|
||||
|
||||
@@ -5,15 +5,15 @@ use super::shared::{
|
||||
AdminStandaloneApiKeyCreateRequest, AdminStandaloneApiKeyFieldPresence,
|
||||
AdminStandaloneApiKeyToggleRequest, AdminStandaloneApiKeyUpdateRequest,
|
||||
};
|
||||
use super::{
|
||||
default_admin_user_api_key_name, encrypt_catalog_secret_with_fallbacks,
|
||||
format_optional_unix_secs_iso8601, generate_admin_user_api_key_plaintext,
|
||||
hash_admin_user_api_key, masked_user_api_key_display, normalize_admin_optional_api_key_name,
|
||||
normalize_admin_user_api_formats, normalize_admin_user_string_list,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::handlers::admin::users::{
|
||||
default_admin_user_api_key_name, format_optional_unix_secs_iso8601,
|
||||
generate_admin_user_api_key_plaintext, hash_admin_user_api_key, masked_user_api_key_display,
|
||||
normalize_admin_optional_api_key_name, normalize_admin_user_api_formats,
|
||||
normalize_admin_user_string_list,
|
||||
};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -23,11 +23,11 @@ use axum::{
|
||||
use serde_json::json;
|
||||
|
||||
pub(super) async fn build_admin_create_api_key_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&axum::body::Bytes>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
if !state.data.has_auth_api_key_writer() {
|
||||
if !state.has_auth_api_key_writer() {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ pub(super) async fn build_admin_create_api_key_response(
|
||||
Err(_) => {
|
||||
return Ok(build_admin_api_keys_bad_request_response(
|
||||
"请求数据验证失败",
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
if payload.initial_balance_usd.is_some()
|
||||
@@ -85,7 +85,7 @@ pub(super) async fn build_admin_create_api_key_response(
|
||||
}
|
||||
|
||||
let plaintext_key = generate_admin_user_api_key_plaintext();
|
||||
let Some(key_encrypted) = encrypt_catalog_secret_with_fallbacks(state, &plaintext_key) else {
|
||||
let Some(key_encrypted) = state.encrypt_catalog_secret_with_fallbacks(&plaintext_key) else {
|
||||
return Ok((
|
||||
http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "detail": "API密钥加密失败" })),
|
||||
@@ -138,15 +138,15 @@ pub(super) async fn build_admin_create_api_key_response(
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_update_api_key_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&axum::body::Bytes>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
if !state.data.has_auth_api_key_writer() {
|
||||
if !state.has_auth_api_key_writer() {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
}
|
||||
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(&request_context.request_path) else {
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(request_context.path()) else {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
};
|
||||
let Some(request_body) = request_body else {
|
||||
@@ -159,7 +159,7 @@ pub(super) async fn build_admin_update_api_key_response(
|
||||
_ => {
|
||||
return Ok(build_admin_api_keys_bad_request_response(
|
||||
"请求数据验证失败",
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
let field_presence = AdminStandaloneApiKeyFieldPresence {
|
||||
@@ -174,7 +174,7 @@ pub(super) async fn build_admin_update_api_key_response(
|
||||
Err(_) => {
|
||||
return Ok(build_admin_api_keys_bad_request_response(
|
||||
"请求数据验证失败",
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
if payload.initial_balance_usd.is_some()
|
||||
@@ -262,15 +262,15 @@ pub(super) async fn build_admin_update_api_key_response(
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_toggle_api_key_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&axum::body::Bytes>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
if !state.data.has_auth_api_key_writer() {
|
||||
if !state.has_auth_api_key_writer() {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
}
|
||||
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(&request_context.request_path) else {
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(request_context.path()) else {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
};
|
||||
|
||||
@@ -283,17 +283,15 @@ pub(super) async fn build_admin_toggle_api_key_response(
|
||||
Err(_) => {
|
||||
return Ok(build_admin_api_keys_bad_request_response(
|
||||
"请求数据验证失败",
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let Some(snapshot) = state
|
||||
.data
|
||||
.list_auth_api_key_snapshots_by_ids(std::slice::from_ref(&api_key_id))
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.await?
|
||||
.into_iter()
|
||||
.find(|snapshot| snapshot.api_key_id == api_key_id)
|
||||
else {
|
||||
@@ -326,14 +324,14 @@ pub(super) async fn build_admin_toggle_api_key_response(
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_delete_api_key_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
if !state.data.has_auth_api_key_writer() {
|
||||
if !state.has_auth_api_key_writer() {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
}
|
||||
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(&request_context.request_path) else {
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(request_context.path()) else {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ use super::shared::{
|
||||
build_admin_api_keys_data_unavailable_response, build_admin_api_keys_not_found_response,
|
||||
};
|
||||
use super::{decrypt_catalog_secret_with_fallbacks, query_param_bool, query_param_optional_bool};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
@@ -19,11 +19,11 @@ use std::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
pub(super) async fn build_admin_list_api_keys_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let handler_started_at = Instant::now();
|
||||
let query = request_context.request_query_string.as_deref();
|
||||
let query = request_context.query_string();
|
||||
let skip = match admin_api_keys_parse_skip(query) {
|
||||
Ok(value) => value,
|
||||
Err(detail) => return Ok(build_admin_api_keys_bad_request_response(detail)),
|
||||
@@ -109,18 +109,16 @@ pub(super) async fn build_admin_list_api_keys_response(
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_api_key_detail_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(&request_context.request_path) else {
|
||||
let Some(api_key_id) = admin_api_keys_id_from_path(request_context.path()) else {
|
||||
return Ok(build_admin_api_keys_data_unavailable_response());
|
||||
};
|
||||
|
||||
if state
|
||||
.data
|
||||
.list_auth_api_key_snapshots_by_ids(std::slice::from_ref(&api_key_id))
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.await?
|
||||
.into_iter()
|
||||
.any(|snapshot| snapshot.api_key_id == api_key_id && !snapshot.api_key_is_standalone)
|
||||
{
|
||||
@@ -136,11 +134,7 @@ pub(super) async fn build_admin_api_key_detail_response(
|
||||
return Ok(build_admin_api_keys_not_found_response());
|
||||
};
|
||||
|
||||
if query_param_bool(
|
||||
request_context.request_query_string.as_deref(),
|
||||
"include_key",
|
||||
false,
|
||||
) {
|
||||
if query_param_bool(request_context.query_string(), "include_key", false) {
|
||||
let Some(ciphertext) = record
|
||||
.key_encrypted
|
||||
.as_deref()
|
||||
|
||||
@@ -4,16 +4,16 @@ use super::mutation_routes::{
|
||||
};
|
||||
use super::read_routes::{build_admin_api_key_detail_response, build_admin_list_api_keys_response};
|
||||
use super::shared::build_admin_api_keys_data_unavailable_response;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::GatewayError;
|
||||
use axum::{body::Body, http, response::Response};
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&axum::body::Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = request_context.control_decision.as_ref() else {
|
||||
let Some(decision) = request_context.decision() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let path = request_context.request_path.as_str();
|
||||
let path = request_context.path();
|
||||
let is_api_keys_route = matches!(path, "/api/admin/api-keys" | "/api/admin/api-keys/")
|
||||
|| (path.starts_with("/api/admin/api-keys/") && path.matches('/').count() == 4);
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
|
||||
match decision.route_kind.as_deref() {
|
||||
Some("list_api_keys")
|
||||
if request_context.request_method == http::Method::GET
|
||||
if request_context.method() == http::Method::GET
|
||||
&& matches!(path, "/api/admin/api-keys" | "/api/admin/api-keys/") =>
|
||||
{
|
||||
Ok(Some(
|
||||
@@ -39,7 +39,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
))
|
||||
}
|
||||
Some("api_key_detail")
|
||||
if request_context.request_method == http::Method::GET
|
||||
if request_context.method() == http::Method::GET
|
||||
&& path.starts_with("/api/admin/api-keys/") =>
|
||||
{
|
||||
Ok(Some(
|
||||
@@ -47,7 +47,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
))
|
||||
}
|
||||
Some("create_api_key")
|
||||
if request_context.request_method == http::Method::POST
|
||||
if request_context.method() == http::Method::POST
|
||||
&& matches!(path, "/api/admin/api-keys" | "/api/admin/api-keys/") =>
|
||||
{
|
||||
Ok(Some(
|
||||
@@ -55,7 +55,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
))
|
||||
}
|
||||
Some("update_api_key")
|
||||
if request_context.request_method == http::Method::PUT
|
||||
if request_context.method() == http::Method::PUT
|
||||
&& path.starts_with("/api/admin/api-keys/") =>
|
||||
{
|
||||
Ok(Some(
|
||||
@@ -63,7 +63,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
))
|
||||
}
|
||||
Some("toggle_api_key")
|
||||
if request_context.request_method == http::Method::PATCH
|
||||
if request_context.method() == http::Method::PATCH
|
||||
&& path.starts_with("/api/admin/api-keys/") =>
|
||||
{
|
||||
Ok(Some(
|
||||
@@ -71,7 +71,7 @@ pub(super) async fn maybe_build_local_admin_api_keys_routes_response(
|
||||
))
|
||||
}
|
||||
Some("delete_api_key")
|
||||
if request_context.request_method == http::Method::DELETE
|
||||
if request_context.method() == http::Method::DELETE
|
||||
&& path.starts_with("/api/admin/api-keys/") =>
|
||||
{
|
||||
Ok(Some(
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
use super::ADMIN_API_KEYS_DATA_UNAVAILABLE_DETAIL;
|
||||
use super::{
|
||||
format_optional_unix_secs_iso8601, http, json, masked_user_api_key_display, query_param_value,
|
||||
serialize_admin_system_users_export_wallet, AppState, Body, GatewayError,
|
||||
GatewayPublicRequestContext, IntoResponse, Json, Response,
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::query_param_value;
|
||||
use crate::handlers::admin::users::{
|
||||
format_optional_unix_secs_iso8601, masked_user_api_key_display,
|
||||
};
|
||||
use crate::GatewayError;
|
||||
use aether_admin::system::serialize_admin_system_users_export_wallet;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
const ADMIN_API_KEYS_DATA_UNAVAILABLE_DETAIL: &str = "Admin standalone API key data unavailable";
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
pub(super) struct AdminStandaloneApiKeyCreateRequest {
|
||||
@@ -85,11 +95,10 @@ pub(super) fn admin_api_keys_id_from_path(request_path: &str) -> Option<String>
|
||||
}
|
||||
|
||||
pub(super) fn admin_api_keys_operator_id(
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
) -> Option<String> {
|
||||
request_context
|
||||
.control_decision
|
||||
.as_ref()
|
||||
.decision()
|
||||
.and_then(|decision| decision.admin_principal.as_ref())
|
||||
.map(|principal| principal.user_id.clone())
|
||||
}
|
||||
@@ -118,8 +127,12 @@ pub(super) fn admin_api_keys_parse_limit(query: Option<&str>) -> Result<usize, S
|
||||
}
|
||||
}
|
||||
|
||||
fn masked_admin_api_key_display(state: &AdminAppState<'_>, ciphertext: Option<&str>) -> String {
|
||||
masked_user_api_key_display(state, ciphertext)
|
||||
}
|
||||
|
||||
pub(super) fn build_admin_api_key_list_item_payload(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
record: &aether_data::repository::auth::StoredAuthApiKeyExportRecord,
|
||||
total_tokens: Option<u64>,
|
||||
wallet: Option<&aether_data::repository::wallet::StoredWalletSnapshot>,
|
||||
@@ -128,7 +141,7 @@ pub(super) fn build_admin_api_key_list_item_payload(
|
||||
"id": record.api_key_id,
|
||||
"user_id": record.user_id,
|
||||
"name": record.name,
|
||||
"key_display": masked_user_api_key_display(state, record.key_encrypted.as_deref()),
|
||||
"key_display": masked_admin_api_key_display(state, record.key_encrypted.as_deref()),
|
||||
"is_active": record.is_active,
|
||||
"is_standalone": true,
|
||||
"total_requests": record.total_requests,
|
||||
@@ -148,7 +161,7 @@ pub(super) fn build_admin_api_key_list_item_payload(
|
||||
}
|
||||
|
||||
pub(super) fn build_admin_api_key_detail_payload(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
record: &aether_data::repository::auth::StoredAuthApiKeyExportRecord,
|
||||
total_tokens: u64,
|
||||
wallet: Option<&aether_data::repository::wallet::StoredWalletSnapshot>,
|
||||
@@ -157,7 +170,7 @@ pub(super) fn build_admin_api_key_detail_payload(
|
||||
"id": record.api_key_id,
|
||||
"user_id": record.user_id,
|
||||
"name": record.name,
|
||||
"key_display": masked_user_api_key_display(state, record.key_encrypted.as_deref()),
|
||||
"key_display": masked_admin_api_key_display(state, record.key_encrypted.as_deref()),
|
||||
"is_active": record.is_active,
|
||||
"is_standalone": true,
|
||||
"total_requests": record.total_requests,
|
||||
@@ -176,7 +189,7 @@ pub(super) fn build_admin_api_key_detail_payload(
|
||||
}
|
||||
|
||||
pub(super) async fn admin_api_key_total_tokens_by_ids(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
api_key_ids: &[String],
|
||||
) -> Result<std::collections::BTreeMap<String, u64>, GatewayError> {
|
||||
if api_key_ids.is_empty() || !state.has_usage_data_reader() {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use super::shared::*;
|
||||
use crate::handlers::admin::shared::{
|
||||
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
use crate::GatewayError;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -69,7 +67,7 @@ pub(super) struct AdminLdapConnectionTestConfig {
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_ldap_update_config(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
payload: AdminLdapConfigUpdateRequest,
|
||||
) -> Result<aether_data::repository::auth_modules::StoredLdapModuleConfig, String> {
|
||||
let server_url = admin_ldap_trim_required(payload.server_url, "LDAP 服务器地址不能为空")?;
|
||||
@@ -139,7 +137,7 @@ pub(super) async fn build_admin_ldap_update_config(
|
||||
|
||||
let bind_password_encrypted = match bind_password {
|
||||
Some(value) if value.is_empty() => None,
|
||||
Some(value) => encrypt_catalog_secret_with_fallbacks(state, &value),
|
||||
Some(value) => state.encrypt_catalog_secret_with_fallbacks(&value),
|
||||
None => existing.and_then(|config| config.bind_password_encrypted),
|
||||
};
|
||||
if bind_password_update_requested && bind_password_encrypted.is_none() {
|
||||
@@ -165,7 +163,7 @@ pub(super) async fn build_admin_ldap_update_config(
|
||||
}
|
||||
|
||||
pub(super) async fn build_admin_ldap_test_config(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
payload: AdminLdapConfigTestRequest,
|
||||
) -> Result<Option<AdminLdapConnectionTestConfig>, String> {
|
||||
if let Some(value) = payload.user_search_filter.as_deref() {
|
||||
@@ -337,7 +335,7 @@ fn admin_ldap_validate_search_filter(value: &str) -> Result<(), String> {
|
||||
}
|
||||
|
||||
fn admin_ldap_read_saved_bind_password(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
config: &aether_data::repository::auth_modules::StoredLdapModuleConfig,
|
||||
) -> Option<String> {
|
||||
config
|
||||
@@ -346,7 +344,8 @@ fn admin_ldap_read_saved_bind_password(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.and_then(|value| {
|
||||
decrypt_catalog_secret_with_fallbacks(state.encryption_key(), value)
|
||||
state
|
||||
.decrypt_catalog_secret_with_fallbacks(value)
|
||||
.or_else(|| Some(value.to_string()))
|
||||
})
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
response::Response,
|
||||
@@ -10,8 +10,8 @@ mod routes;
|
||||
mod shared;
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_ldap_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
routes::maybe_build_local_admin_ldap_response(state, request_context, request_body).await
|
||||
|
||||
@@ -3,9 +3,9 @@ use super::builders::{
|
||||
AdminLdapConfigTestRequest, AdminLdapConfigUpdateRequest,
|
||||
};
|
||||
use super::shared::*;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -15,11 +15,11 @@ use axum::{
|
||||
use serde_json::json;
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_ldap_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = request_context.control_decision.as_ref() else {
|
||||
let Some(decision) = request_context.decision() else {
|
||||
return Ok(None);
|
||||
};
|
||||
if decision.route_family.as_deref() != Some("ldap_manage") {
|
||||
@@ -28,8 +28,8 @@ pub(super) async fn maybe_build_local_admin_ldap_response(
|
||||
|
||||
match decision.route_kind.as_deref() {
|
||||
Some("get_config")
|
||||
if request_context.request_method == http::Method::GET
|
||||
&& is_admin_ldap_config_root(&request_context.request_path) =>
|
||||
if request_context.method() == http::Method::GET
|
||||
&& is_admin_ldap_config_root(request_context.path()) =>
|
||||
{
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(build_admin_ldap_config_payload(
|
||||
@@ -43,8 +43,8 @@ pub(super) async fn maybe_build_local_admin_ldap_response(
|
||||
)));
|
||||
}
|
||||
Some("set_config")
|
||||
if request_context.request_method == http::Method::PUT
|
||||
&& is_admin_ldap_config_root(&request_context.request_path) =>
|
||||
if request_context.method() == http::Method::PUT
|
||||
&& is_admin_ldap_config_root(request_context.path()) =>
|
||||
{
|
||||
if !state.has_auth_module_writer() {
|
||||
return Ok(Some(admin_ldap_unavailable_response()));
|
||||
@@ -70,8 +70,8 @@ pub(super) async fn maybe_build_local_admin_ldap_response(
|
||||
));
|
||||
}
|
||||
Some("test_connection")
|
||||
if request_context.request_method == http::Method::POST
|
||||
&& is_admin_ldap_test_root(&request_context.request_path) =>
|
||||
if request_context.method() == http::Method::POST
|
||||
&& is_admin_ldap_test_root(request_context.path()) =>
|
||||
{
|
||||
let payload = match request_body {
|
||||
Some(body) if !body.is_empty() => match serde_json::from_slice::<
|
||||
|
||||
@@ -2,13 +2,11 @@ mod api_keys;
|
||||
mod ldap;
|
||||
mod oauth_config;
|
||||
mod oauth_routes;
|
||||
mod routes;
|
||||
mod security;
|
||||
|
||||
pub(crate) use self::api_keys::maybe_build_local_admin_api_keys_response;
|
||||
pub(crate) use self::ldap::maybe_build_local_admin_ldap_response;
|
||||
pub(crate) use self::oauth_config::{
|
||||
build_admin_oauth_provider_payload, build_admin_oauth_supported_types_payload,
|
||||
build_admin_oauth_upsert_record,
|
||||
};
|
||||
pub(crate) use self::oauth_routes::maybe_build_local_admin_oauth_response;
|
||||
pub(super) use self::api_keys::maybe_build_local_admin_api_keys_response;
|
||||
pub(super) use self::ldap::maybe_build_local_admin_ldap_response;
|
||||
pub(super) use self::oauth_routes::maybe_build_local_admin_oauth_response;
|
||||
pub(super) use self::routes::maybe_build_local_admin_auth_response;
|
||||
pub(crate) use self::security::maybe_build_local_admin_security_response;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::handlers::admin::shared::encrypt_catalog_secret_with_fallbacks;
|
||||
use crate::AppState;
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
use aether_data::repository::oauth_providers::{
|
||||
EncryptedSecretUpdate, UpsertOAuthProviderConfigRecord,
|
||||
};
|
||||
@@ -10,31 +9,31 @@ use url::Url;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminOAuthProviderUpsertRequest {
|
||||
pub(crate) display_name: String,
|
||||
pub(crate) client_id: String,
|
||||
pub(super) display_name: String,
|
||||
pub(super) client_id: String,
|
||||
#[serde(default)]
|
||||
pub(crate) client_secret: Option<String>,
|
||||
pub(super) client_secret: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) authorization_url_override: Option<String>,
|
||||
pub(super) authorization_url_override: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) token_url_override: Option<String>,
|
||||
pub(super) token_url_override: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) userinfo_url_override: Option<String>,
|
||||
pub(super) userinfo_url_override: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) scopes: Option<Vec<String>>,
|
||||
pub(crate) redirect_uri: String,
|
||||
pub(crate) frontend_callback_url: String,
|
||||
pub(super) scopes: Option<Vec<String>>,
|
||||
pub(super) redirect_uri: String,
|
||||
pub(super) frontend_callback_url: String,
|
||||
#[serde(default)]
|
||||
pub(crate) attribute_mapping: Option<serde_json::Value>,
|
||||
pub(super) attribute_mapping: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) extra_config: Option<serde_json::Value>,
|
||||
pub(super) extra_config: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_enabled: bool,
|
||||
pub(super) is_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub(crate) force: bool,
|
||||
pub(super) force: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_oauth_supported_types_payload() -> Vec<serde_json::Value> {
|
||||
pub(super) fn build_admin_oauth_supported_types_payload() -> Vec<serde_json::Value> {
|
||||
vec![json!({
|
||||
"provider_type": "linuxdo",
|
||||
"display_name": "Linux Do",
|
||||
@@ -45,7 +44,7 @@ pub(crate) fn build_admin_oauth_supported_types_payload() -> Vec<serde_json::Val
|
||||
})]
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_oauth_provider_payload(
|
||||
pub(super) fn build_admin_oauth_provider_payload(
|
||||
provider: &aether_data::repository::oauth_providers::StoredOAuthProviderConfig,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
@@ -135,8 +134,8 @@ fn validate_admin_oauth_url_override(url: &str, allowed_domains: &[&str]) -> Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_oauth_upsert_record(
|
||||
state: &AppState,
|
||||
pub(super) fn build_admin_oauth_upsert_record(
|
||||
state: &AdminAppState<'_>,
|
||||
provider_type: &str,
|
||||
payload: AdminOAuthProviderUpsertRequest,
|
||||
) -> Result<UpsertOAuthProviderConfigRecord, String> {
|
||||
@@ -213,7 +212,8 @@ pub(crate) fn build_admin_oauth_upsert_record(
|
||||
} else if secret.is_empty() {
|
||||
EncryptedSecretUpdate::Preserve
|
||||
} else {
|
||||
let encrypted = encrypt_catalog_secret_with_fallbacks(state, secret)
|
||||
let encrypted = state
|
||||
.encrypt_catalog_secret_with_fallbacks(secret)
|
||||
.ok_or_else(|| "gateway 未配置 OAuth provider 加密密钥".to_string())?;
|
||||
EncryptedSecretUpdate::Set(encrypted)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use super::oauth_config::{
|
||||
build_admin_oauth_provider_payload, build_admin_oauth_supported_types_payload,
|
||||
build_admin_oauth_upsert_record, AdminOAuthProviderUpsertRequest,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::{attach_admin_audit_response, build_proxy_error_response};
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -15,11 +15,11 @@ use axum::{
|
||||
use serde_json::json;
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = request_context.control_decision.as_ref() else {
|
||||
let Some(decision) = request_context.decision() else {
|
||||
return Ok(None);
|
||||
};
|
||||
if decision.route_family.as_deref() != Some("oauth_manage") {
|
||||
@@ -27,8 +27,8 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("supported_types")
|
||||
&& request_context.request_method == http::Method::GET
|
||||
&& request_context.request_path == "/api/admin/oauth/supported-types"
|
||||
&& request_context.method() == http::Method::GET
|
||||
&& request_context.path() == "/api/admin/oauth/supported-types"
|
||||
{
|
||||
return Ok(Some(
|
||||
Json(build_admin_oauth_supported_types_payload()).into_response(),
|
||||
@@ -36,9 +36,9 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("list_providers")
|
||||
&& request_context.request_method == http::Method::GET
|
||||
&& request_context.method() == http::Method::GET
|
||||
&& matches!(
|
||||
request_context.request_path.as_str(),
|
||||
request_context.path(),
|
||||
"/api/admin/oauth/providers" | "/api/admin/oauth/providers/"
|
||||
)
|
||||
{
|
||||
@@ -59,10 +59,9 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("get_provider")
|
||||
&& request_context.request_method == http::Method::GET
|
||||
&& request_context.method() == http::Method::GET
|
||||
{
|
||||
let Some(provider_type) =
|
||||
admin_oauth_provider_type_from_path(&request_context.request_path)
|
||||
let Some(provider_type) = admin_oauth_provider_type_from_path(request_context.path())
|
||||
else {
|
||||
return Ok(Some(
|
||||
(
|
||||
@@ -91,10 +90,9 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("upsert_provider")
|
||||
&& request_context.request_method == http::Method::PUT
|
||||
&& request_context.method() == http::Method::PUT
|
||||
{
|
||||
let Some(provider_type) =
|
||||
admin_oauth_provider_type_from_path(&request_context.request_path)
|
||||
let Some(provider_type) = admin_oauth_provider_type_from_path(request_context.path())
|
||||
else {
|
||||
return Ok(Some(build_proxy_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
@@ -172,10 +170,9 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("delete_provider")
|
||||
&& request_context.request_method == http::Method::DELETE
|
||||
&& request_context.method() == http::Method::DELETE
|
||||
{
|
||||
let Some(provider_type) =
|
||||
admin_oauth_provider_type_from_path(&request_context.request_path)
|
||||
let Some(provider_type) = admin_oauth_provider_type_from_path(request_context.path())
|
||||
else {
|
||||
return Ok(Some(build_proxy_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
@@ -229,10 +226,9 @@ pub(crate) async fn maybe_build_local_admin_oauth_response(
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("test_provider")
|
||||
&& request_context.request_method == http::Method::POST
|
||||
&& request_context.method() == http::Method::POST
|
||||
{
|
||||
let Some(provider_type) =
|
||||
admin_oauth_test_provider_type_from_path(&request_context.request_path)
|
||||
let Some(provider_type) = admin_oauth_test_provider_type_from_path(request_context.path())
|
||||
else {
|
||||
return Ok(Some(
|
||||
(
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use super::{api_keys, ldap, oauth_routes, security};
|
||||
use crate::handlers::admin::request::{AdminRouteRequest, AdminRouteResult};
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_auth_response(
|
||||
request: AdminRouteRequest<'_>,
|
||||
) -> AdminRouteResult {
|
||||
if let Some(response) = security::maybe_build_local_admin_security_response(
|
||||
&request.state(),
|
||||
&request.request_context(),
|
||||
request.request_body(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = api_keys::maybe_build_local_admin_api_keys_response(
|
||||
&request.state(),
|
||||
&request.request_context(),
|
||||
request.request_body(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = ldap::maybe_build_local_admin_ldap_response(
|
||||
&request.state(),
|
||||
&request.request_context(),
|
||||
request.request_body(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = oauth_routes::maybe_build_local_admin_oauth_response(
|
||||
&request.state(),
|
||||
&request.request_context(),
|
||||
request.request_body(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::attach_admin_audit_response;
|
||||
use crate::{AppState, GatewayError};
|
||||
use crate::GatewayError;
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
@@ -98,7 +98,7 @@ fn admin_security_validate_ip_or_cidr(value: &str) -> bool {
|
||||
}
|
||||
|
||||
async fn build_admin_security_blacklist_add_response(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let Some(request_body) = request_body else {
|
||||
@@ -113,7 +113,7 @@ async fn build_admin_security_blacklist_add_response(
|
||||
_ => {
|
||||
return Ok(build_admin_security_bad_request_response(
|
||||
"请求数据验证失败",
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,11 +148,10 @@ async fn build_admin_security_blacklist_add_response(
|
||||
}
|
||||
|
||||
async fn build_admin_security_blacklist_remove_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let Some(ip_address) = admin_security_blacklist_ip_from_path(&request_context.request_path)
|
||||
else {
|
||||
let Some(ip_address) = admin_security_blacklist_ip_from_path(request_context.path()) else {
|
||||
return Ok(build_admin_security_bad_request_response("缺少 ip_address"));
|
||||
};
|
||||
|
||||
@@ -176,7 +175,7 @@ async fn build_admin_security_blacklist_remove_response(
|
||||
}
|
||||
|
||||
async fn build_admin_security_blacklist_stats_response(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let (available, total, error) = state.admin_security_blacklist_stats().await?;
|
||||
let mut payload = json!({
|
||||
@@ -196,7 +195,7 @@ async fn build_admin_security_blacklist_stats_response(
|
||||
}
|
||||
|
||||
async fn build_admin_security_blacklist_list_response(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let entries = state.list_admin_security_blacklist().await?;
|
||||
let total = entries.len();
|
||||
@@ -210,7 +209,7 @@ async fn build_admin_security_blacklist_list_response(
|
||||
}
|
||||
|
||||
async fn build_admin_security_whitelist_add_response(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let Some(request_body) = request_body else {
|
||||
@@ -223,7 +222,7 @@ async fn build_admin_security_whitelist_add_response(
|
||||
Err(_) => {
|
||||
return Ok(build_admin_security_bad_request_response(
|
||||
"请求数据验证失败",
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
let ip_address = payload.ip_address.trim();
|
||||
@@ -249,11 +248,10 @@ async fn build_admin_security_whitelist_add_response(
|
||||
}
|
||||
|
||||
async fn build_admin_security_whitelist_remove_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let Some(ip_address) = admin_security_whitelist_ip_from_path(&request_context.request_path)
|
||||
else {
|
||||
let Some(ip_address) = admin_security_whitelist_ip_from_path(request_context.path()) else {
|
||||
return Ok(build_admin_security_bad_request_response("缺少 ip_address"));
|
||||
};
|
||||
|
||||
@@ -277,7 +275,7 @@ async fn build_admin_security_whitelist_remove_response(
|
||||
}
|
||||
|
||||
async fn build_admin_security_whitelist_list_response(
|
||||
state: &AppState,
|
||||
state: &AdminAppState<'_>,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let whitelist = state.list_admin_security_whitelist().await?;
|
||||
let total = whitelist.len();
|
||||
@@ -295,11 +293,11 @@ async fn build_admin_security_whitelist_list_response(
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_security_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = request_context.control_decision.as_ref() else {
|
||||
let Some(decision) = request_context.decision() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user