mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
Merge remote-tracking branch 'origin/pr/399' into aether-rust-pioneer
# Conflicts: # crates/aether-data/src/lifecycle/bootstrap/postgres.rs # crates/aether-data/src/lifecycle/migrate/tests.rs
This commit is contained in:
@@ -72,6 +72,30 @@ impl<'a> AdminAppState<'a> {
|
||||
self.app.delete_management_token(token_id).await
|
||||
}
|
||||
|
||||
pub(crate) async fn create_management_token(
|
||||
&self,
|
||||
record: &aether_data::repository::management_tokens::CreateManagementTokenRecord,
|
||||
) -> Result<
|
||||
crate::LocalMutationOutcome<
|
||||
aether_data::repository::management_tokens::StoredManagementToken,
|
||||
>,
|
||||
GatewayError,
|
||||
> {
|
||||
self.app.create_management_token(record).await
|
||||
}
|
||||
|
||||
pub(crate) async fn update_management_token(
|
||||
&self,
|
||||
record: &aether_data::repository::management_tokens::UpdateManagementTokenRecord,
|
||||
) -> Result<
|
||||
crate::LocalMutationOutcome<
|
||||
aether_data::repository::management_tokens::StoredManagementToken,
|
||||
>,
|
||||
GatewayError,
|
||||
> {
|
||||
self.app.update_management_token(record).await
|
||||
}
|
||||
|
||||
pub(crate) async fn list_management_tokens(
|
||||
&self,
|
||||
query: &aether_data::repository::management_tokens::ManagementTokenListQuery,
|
||||
@@ -95,6 +119,18 @@ impl<'a> AdminAppState<'a> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn regenerate_management_token_secret(
|
||||
&self,
|
||||
mutation: &aether_data::repository::management_tokens::RegenerateManagementTokenSecret,
|
||||
) -> Result<
|
||||
crate::LocalMutationOutcome<
|
||||
aether_data::repository::management_tokens::StoredManagementToken,
|
||||
>,
|
||||
GatewayError,
|
||||
> {
|
||||
self.app.regenerate_management_token_secret(mutation).await
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_admin_security_blacklist(
|
||||
&self,
|
||||
ip_address: &str,
|
||||
|
||||
@@ -47,6 +47,7 @@ pub(crate) async fn maybe_build_local_admin_core_response(
|
||||
super::management_tokens::maybe_build_local_admin_management_tokens_response(
|
||||
state,
|
||||
request_context,
|
||||
request_body,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
|
||||
@@ -1,23 +1,314 @@
|
||||
use crate::control::{
|
||||
management_token_permission_catalog_payload, normalize_assignable_management_token_permissions,
|
||||
};
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::handlers::admin::shared::{query_param_optional_bool, query_param_value};
|
||||
use crate::handlers::admin::system::shared::paths::{
|
||||
admin_management_token_id_from_path, admin_management_token_status_id_from_path,
|
||||
is_admin_management_tokens_root,
|
||||
admin_management_token_id_from_path, admin_management_token_regenerate_id_from_path,
|
||||
admin_management_token_status_id_from_path, is_admin_management_tokens_root,
|
||||
};
|
||||
use crate::handlers::internal::build_management_token_payload;
|
||||
use crate::GatewayError;
|
||||
use aether_data::repository::management_tokens::ManagementTokenListQuery;
|
||||
use crate::handlers::shared::generate_gateway_secret_plaintext;
|
||||
use crate::{GatewayError, LocalMutationOutcome};
|
||||
use aether_data::repository::management_tokens::{
|
||||
CreateManagementTokenRecord, ManagementTokenListQuery, RegenerateManagementTokenSecret,
|
||||
StoredManagementTokenUserSummary, UpdateManagementTokenRecord,
|
||||
};
|
||||
use axum::{
|
||||
body::Body,
|
||||
body::{Body, Bytes},
|
||||
http,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use serde_json::json;
|
||||
use sha2::{Digest, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
const ADMIN_MANAGEMENT_TOKEN_PREFIX: &str = "ae";
|
||||
const ADMIN_MANAGEMENT_TOKEN_SEPARATOR: &str = "-";
|
||||
const ADMIN_MANAGEMENT_TOKEN_DISPLAY_PREFIX_LEN: usize = 10;
|
||||
|
||||
fn admin_management_token_bad_request_response(detail: impl Into<String>) -> Response<Body> {
|
||||
(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "detail": detail.into() })),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn admin_management_token_not_found_response() -> Response<Body> {
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn admin_management_token_read_only_response() -> Response<Body> {
|
||||
(
|
||||
http::StatusCode::CONFLICT,
|
||||
Json(json!({
|
||||
"detail": "Management Token 本地写入仓库不可用",
|
||||
"error_code": "read_only_mode",
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AdminManagementTokenCreateInput {
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
allowed_ips: Option<serde_json::Value>,
|
||||
permissions: serde_json::Value,
|
||||
expires_at_unix_secs: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct AdminManagementTokenUpdateInput {
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
clear_description: bool,
|
||||
allowed_ips: Option<serde_json::Value>,
|
||||
clear_allowed_ips: bool,
|
||||
permissions: Option<serde_json::Value>,
|
||||
expires_at_unix_secs: Option<u64>,
|
||||
clear_expires_at: bool,
|
||||
}
|
||||
|
||||
fn generate_admin_management_token_plaintext() -> String {
|
||||
generate_gateway_secret_plaintext(
|
||||
ADMIN_MANAGEMENT_TOKEN_PREFIX,
|
||||
ADMIN_MANAGEMENT_TOKEN_SEPARATOR,
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_admin_management_token(value: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(value.as_bytes());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
fn admin_management_token_prefix(value: &str) -> Option<String> {
|
||||
(!value.is_empty())
|
||||
.then(|| value[..value.len().min(ADMIN_MANAGEMENT_TOKEN_DISPLAY_PREFIX_LEN)].to_string())
|
||||
}
|
||||
|
||||
fn admin_validate_ip_or_cidr(value: &str) -> bool {
|
||||
let value = value.trim();
|
||||
if value.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if value.parse::<std::net::IpAddr>().is_ok() {
|
||||
return true;
|
||||
}
|
||||
let Some((host, prefix)) = value.split_once('/') else {
|
||||
return false;
|
||||
};
|
||||
let Ok(ip) = host.trim().parse::<std::net::IpAddr>() else {
|
||||
return false;
|
||||
};
|
||||
let Ok(prefix) = prefix.trim().parse::<u8>() else {
|
||||
return false;
|
||||
};
|
||||
match ip {
|
||||
std::net::IpAddr::V4(_) => prefix <= 32,
|
||||
std::net::IpAddr::V6(_) => prefix <= 128,
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_parse_management_token_allowed_ips(
|
||||
value: Option<&serde_json::Value>,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
match value {
|
||||
serde_json::Value::Null => Ok(None),
|
||||
serde_json::Value::Array(items) => {
|
||||
if items.is_empty() {
|
||||
return Err("IP 白名单不能为空列表,如需取消限制请不提供此字段".to_string());
|
||||
}
|
||||
let mut normalized = Vec::with_capacity(items.len());
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
let Some(raw) = item.as_str() else {
|
||||
return Err("IP 白名单必须是字符串数组".to_string());
|
||||
};
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err(format!("IP 白名单第 {} 项为空", index + 1));
|
||||
}
|
||||
if !admin_validate_ip_or_cidr(trimmed) {
|
||||
return Err(format!("无效的 IP 地址或 CIDR: {raw}"));
|
||||
}
|
||||
normalized.push(trimmed.to_string());
|
||||
}
|
||||
Ok(Some(json!(normalized)))
|
||||
}
|
||||
_ => Err("IP 白名单必须是字符串数组".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_parse_management_token_expires_at(
|
||||
value: &serde_json::Value,
|
||||
) -> Result<Option<u64>, String> {
|
||||
match value {
|
||||
serde_json::Value::Null => Ok(None),
|
||||
serde_json::Value::String(raw) => {
|
||||
let raw = raw.trim();
|
||||
if raw.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let parsed = chrono::DateTime::parse_from_rfc3339(raw)
|
||||
.map(|value| value.with_timezone(&Utc))
|
||||
.or_else(|_| {
|
||||
chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f")
|
||||
.or_else(|_| {
|
||||
chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S")
|
||||
})
|
||||
.or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M"))
|
||||
.map(|value| value.and_utc())
|
||||
})
|
||||
.map_err(|_| format!("无效的时间格式: {raw}"))?;
|
||||
let unix_secs = parsed.timestamp();
|
||||
if unix_secs <= Utc::now().timestamp() {
|
||||
return Err("过期时间必须在未来".to_string());
|
||||
}
|
||||
u64::try_from(unix_secs)
|
||||
.map(Some)
|
||||
.map_err(|_| format!("无效的时间格式: {raw}"))
|
||||
}
|
||||
_ => Err("expires_at 必须是字符串或 null".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_parse_management_token_create_input(
|
||||
request_body: &[u8],
|
||||
) -> Result<AdminManagementTokenCreateInput, String> {
|
||||
let payload =
|
||||
serde_json::from_slice::<serde_json::Map<String, serde_json::Value>>(request_body)
|
||||
.map_err(|_| "输入验证失败".to_string())?;
|
||||
let name = match payload.get("name") {
|
||||
Some(serde_json::Value::String(value)) if (1..=100).contains(&value.chars().count()) => {
|
||||
value.clone()
|
||||
}
|
||||
_ => return Err("输入验证失败".to_string()),
|
||||
};
|
||||
let description = match payload.get("description") {
|
||||
None | Some(serde_json::Value::Null) => None,
|
||||
Some(serde_json::Value::String(value)) if value.chars().count() <= 500 => {
|
||||
Some(value.clone())
|
||||
}
|
||||
_ => return Err("输入验证失败".to_string()),
|
||||
};
|
||||
let allowed_ips = admin_parse_management_token_allowed_ips(payload.get("allowed_ips"))?;
|
||||
let permissions =
|
||||
normalize_assignable_management_token_permissions(payload.get("permissions"))?;
|
||||
let expires_at_unix_secs = match payload.get("expires_at") {
|
||||
Some(value) => admin_parse_management_token_expires_at(value)?,
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(AdminManagementTokenCreateInput {
|
||||
name,
|
||||
description,
|
||||
allowed_ips,
|
||||
permissions,
|
||||
expires_at_unix_secs,
|
||||
})
|
||||
}
|
||||
|
||||
fn admin_parse_management_token_update_input(
|
||||
request_body: &[u8],
|
||||
) -> Result<AdminManagementTokenUpdateInput, String> {
|
||||
let payload =
|
||||
serde_json::from_slice::<serde_json::Map<String, serde_json::Value>>(request_body)
|
||||
.map_err(|_| "输入验证失败".to_string())?;
|
||||
let mut input = AdminManagementTokenUpdateInput::default();
|
||||
|
||||
if let Some(value) = payload.get("name") {
|
||||
match value {
|
||||
serde_json::Value::Null => {}
|
||||
serde_json::Value::String(value) if (1..=100).contains(&value.chars().count()) => {
|
||||
input.name = Some(value.clone());
|
||||
}
|
||||
_ => return Err("输入验证失败".to_string()),
|
||||
}
|
||||
}
|
||||
if let Some(value) = payload.get("description") {
|
||||
match value {
|
||||
serde_json::Value::Null => input.clear_description = true,
|
||||
serde_json::Value::String(value) if value.chars().count() <= 500 => {
|
||||
if value.is_empty() {
|
||||
input.clear_description = true;
|
||||
} else {
|
||||
input.description = Some(value.clone());
|
||||
}
|
||||
}
|
||||
_ => return Err("输入验证失败".to_string()),
|
||||
}
|
||||
}
|
||||
if let Some(value) = payload.get("allowed_ips") {
|
||||
if value.is_null() {
|
||||
input.clear_allowed_ips = true;
|
||||
} else {
|
||||
input.allowed_ips = admin_parse_management_token_allowed_ips(Some(value))?;
|
||||
}
|
||||
}
|
||||
if let Some(value) = payload.get("permissions") {
|
||||
input.permissions = Some(normalize_assignable_management_token_permissions(Some(
|
||||
value,
|
||||
))?);
|
||||
}
|
||||
if let Some(value) = payload.get("expires_at") {
|
||||
if value.is_null() || value.as_str().is_some_and(|value| value.trim().is_empty()) {
|
||||
input.clear_expires_at = true;
|
||||
} else {
|
||||
input.expires_at_unix_secs = admin_parse_management_token_expires_at(value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
async fn admin_management_token_user_summary(
|
||||
state: &AdminAppState<'_>,
|
||||
user_id: &str,
|
||||
) -> Result<StoredManagementTokenUserSummary, Response<Body>> {
|
||||
let Some(user) = state.find_user_auth_by_id(user_id).await.map_err(|err| {
|
||||
(
|
||||
http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "detail": format!("management token user lookup failed: {err:?}") })),
|
||||
)
|
||||
.into_response()
|
||||
})?
|
||||
else {
|
||||
return Err((
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "管理员用户不存在" })),
|
||||
)
|
||||
.into_response());
|
||||
};
|
||||
StoredManagementTokenUserSummary::new(
|
||||
user.id,
|
||||
user.email,
|
||||
user.username,
|
||||
user.role,
|
||||
)
|
||||
.map_err(|err| {
|
||||
(
|
||||
http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "detail": format!("management token user summary build failed: {err:?}") })),
|
||||
)
|
||||
.into_response()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
state: &AdminAppState<'_>,
|
||||
request_context: &AdminRequestContext<'_>,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = request_context.decision() else {
|
||||
return Ok(None);
|
||||
@@ -43,6 +334,14 @@ pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("permissions_catalog")
|
||||
&& request_context.method() == http::Method::GET
|
||||
{
|
||||
return Ok(Some(
|
||||
Json(management_token_permission_catalog_payload()).into_response(),
|
||||
));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("list_tokens")
|
||||
&& request_context.method() == http::Method::GET
|
||||
&& is_admin_management_tokens_root(request_context.path())
|
||||
@@ -83,6 +382,63 @@ pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("create_token")
|
||||
&& request_context.method() == http::Method::POST
|
||||
&& is_admin_management_tokens_root(request_context.path())
|
||||
{
|
||||
if !state.has_management_token_writer() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(request_body) = request_body else {
|
||||
return Ok(Some(admin_management_token_bad_request_response(
|
||||
"缺少请求体",
|
||||
)));
|
||||
};
|
||||
let input = match admin_parse_management_token_create_input(request_body) {
|
||||
Ok(value) => value,
|
||||
Err(detail) => return Ok(Some(admin_management_token_bad_request_response(detail))),
|
||||
};
|
||||
let Some(admin_principal) = decision.admin_principal.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let user = match admin_management_token_user_summary(state, &admin_principal.user_id).await
|
||||
{
|
||||
Ok(value) => value,
|
||||
Err(response) => return Ok(Some(response)),
|
||||
};
|
||||
let raw_token = generate_admin_management_token_plaintext();
|
||||
let record = CreateManagementTokenRecord {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
user_id: user.id.clone(),
|
||||
user,
|
||||
token_hash: hash_admin_management_token(&raw_token),
|
||||
token_prefix: admin_management_token_prefix(&raw_token),
|
||||
name: input.name.clone(),
|
||||
description: input.description,
|
||||
allowed_ips: input.allowed_ips,
|
||||
permissions: Some(input.permissions),
|
||||
expires_at_unix_secs: input.expires_at_unix_secs,
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
return Ok(Some(match state.create_management_token(&record).await? {
|
||||
LocalMutationOutcome::Applied(token) => (
|
||||
http::StatusCode::CREATED,
|
||||
Json(json!({
|
||||
"message": "Management Token 创建成功",
|
||||
"token": raw_token,
|
||||
"data": build_management_token_payload(&token, Some(&record.user)),
|
||||
})),
|
||||
)
|
||||
.into_response(),
|
||||
LocalMutationOutcome::Invalid(detail) => {
|
||||
admin_management_token_bad_request_response(detail)
|
||||
}
|
||||
LocalMutationOutcome::Unavailable => admin_management_token_read_only_response(),
|
||||
LocalMutationOutcome::NotFound => admin_management_token_not_found_response(),
|
||||
}));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("get_token")
|
||||
&& request_context.method() == http::Method::GET
|
||||
{
|
||||
@@ -105,15 +461,60 @@ pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
Some(&token.user),
|
||||
))
|
||||
.into_response(),
|
||||
None => (
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response(),
|
||||
None => admin_management_token_not_found_response(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("update_token")
|
||||
&& request_context.method() == http::Method::PUT
|
||||
{
|
||||
if !state.has_management_token_writer() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(token_id) = admin_management_token_id_from_path(request_context.path()) else {
|
||||
return Ok(Some(admin_management_token_not_found_response()));
|
||||
};
|
||||
let existing = match state.get_management_token_with_user(&token_id).await? {
|
||||
Some(token) => token,
|
||||
None => return Ok(Some(admin_management_token_not_found_response())),
|
||||
};
|
||||
let Some(request_body) = request_body else {
|
||||
return Ok(Some(admin_management_token_bad_request_response(
|
||||
"缺少请求体",
|
||||
)));
|
||||
};
|
||||
let input = match admin_parse_management_token_update_input(request_body) {
|
||||
Ok(value) => value,
|
||||
Err(detail) => return Ok(Some(admin_management_token_bad_request_response(detail))),
|
||||
};
|
||||
let record = UpdateManagementTokenRecord {
|
||||
token_id: existing.token.id.clone(),
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
clear_description: input.clear_description,
|
||||
allowed_ips: input.allowed_ips,
|
||||
clear_allowed_ips: input.clear_allowed_ips,
|
||||
permissions: input.permissions,
|
||||
expires_at_unix_secs: input.expires_at_unix_secs,
|
||||
clear_expires_at: input.clear_expires_at,
|
||||
is_active: None,
|
||||
};
|
||||
|
||||
return Ok(Some(match state.update_management_token(&record).await? {
|
||||
LocalMutationOutcome::Applied(token) => Json(json!({
|
||||
"message": "更新成功",
|
||||
"data": build_management_token_payload(&token, Some(&existing.user)),
|
||||
}))
|
||||
.into_response(),
|
||||
LocalMutationOutcome::NotFound => admin_management_token_not_found_response(),
|
||||
LocalMutationOutcome::Invalid(detail) => {
|
||||
admin_management_token_bad_request_response(detail)
|
||||
}
|
||||
LocalMutationOutcome::Unavailable => admin_management_token_read_only_response(),
|
||||
}));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("delete_token")
|
||||
&& request_context.method() == http::Method::DELETE
|
||||
{
|
||||
@@ -121,35 +522,17 @@ pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(token_id) = admin_management_token_id_from_path(request_context.path()) else {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
return Ok(Some(admin_management_token_not_found_response()));
|
||||
};
|
||||
let existing = match state.get_management_token_with_user(&token_id).await? {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
None => return Ok(Some(admin_management_token_not_found_response())),
|
||||
};
|
||||
let deleted = state.delete_management_token(&existing.token.id).await?;
|
||||
return Ok(Some(if deleted {
|
||||
Json(json!({ "message": "删除成功" })).into_response()
|
||||
} else {
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response()
|
||||
admin_management_token_not_found_response()
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -161,37 +544,17 @@ pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
}
|
||||
let Some(token_id) = admin_management_token_status_id_from_path(request_context.path())
|
||||
else {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
return Ok(Some(admin_management_token_not_found_response()));
|
||||
};
|
||||
let existing = match state.get_management_token_with_user(&token_id).await? {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
None => return Ok(Some(admin_management_token_not_found_response())),
|
||||
};
|
||||
let Some(updated) = state
|
||||
.set_management_token_active(&existing.token.id, !existing.token.is_active)
|
||||
.await?
|
||||
else {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Management Token 不存在" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
return Ok(Some(admin_management_token_not_found_response()));
|
||||
};
|
||||
return Ok(Some(
|
||||
Json(json!({
|
||||
@@ -202,5 +565,43 @@ pub(crate) async fn maybe_build_local_admin_management_tokens_response(
|
||||
));
|
||||
}
|
||||
|
||||
if decision.route_kind.as_deref() == Some("regenerate_token")
|
||||
&& request_context.method() == http::Method::POST
|
||||
{
|
||||
if !state.has_management_token_writer() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(token_id) = admin_management_token_regenerate_id_from_path(request_context.path())
|
||||
else {
|
||||
return Ok(Some(admin_management_token_not_found_response()));
|
||||
};
|
||||
let existing = match state.get_management_token_with_user(&token_id).await? {
|
||||
Some(token) => token,
|
||||
None => return Ok(Some(admin_management_token_not_found_response())),
|
||||
};
|
||||
let raw_token = generate_admin_management_token_plaintext();
|
||||
let mutation = RegenerateManagementTokenSecret {
|
||||
token_id: existing.token.id.clone(),
|
||||
token_hash: hash_admin_management_token(&raw_token),
|
||||
token_prefix: admin_management_token_prefix(&raw_token),
|
||||
};
|
||||
|
||||
return Ok(Some(
|
||||
match state.regenerate_management_token_secret(&mutation).await? {
|
||||
LocalMutationOutcome::Applied(token) => Json(json!({
|
||||
"message": "Token 已重新生成",
|
||||
"token": raw_token,
|
||||
"data": build_management_token_payload(&token, Some(&existing.user)),
|
||||
}))
|
||||
.into_response(),
|
||||
LocalMutationOutcome::NotFound => admin_management_token_not_found_response(),
|
||||
LocalMutationOutcome::Invalid(detail) => {
|
||||
admin_management_token_bad_request_response(detail)
|
||||
}
|
||||
LocalMutationOutcome::Unavailable => admin_management_token_read_only_response(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -37,3 +37,7 @@ pub(crate) fn admin_management_token_id_from_path(request_path: &str) -> Option<
|
||||
pub(crate) fn admin_management_token_status_id_from_path(request_path: &str) -> Option<String> {
|
||||
aether_admin::system::admin_management_token_status_id_from_path(request_path)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_management_token_regenerate_id_from_path(request_path: &str) -> Option<String> {
|
||||
aether_admin::system::admin_management_token_regenerate_id_from_path(request_path)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user