1
This commit is contained in:
@@ -41,13 +41,13 @@ use crate::{
|
||||
generate_character_visual, get_character_visual_job, publish_character_visual,
|
||||
},
|
||||
custom_world::{
|
||||
create_custom_world_agent_session, execute_custom_world_agent_action,
|
||||
get_custom_world_agent_card_detail, get_custom_world_agent_operation,
|
||||
get_custom_world_agent_session, get_custom_world_gallery_detail, get_custom_world_library,
|
||||
get_custom_world_library_detail, get_custom_world_works, list_custom_world_gallery,
|
||||
publish_custom_world_library_profile, put_custom_world_library_profile,
|
||||
stream_custom_world_agent_message, submit_custom_world_agent_message,
|
||||
unpublish_custom_world_library_profile,
|
||||
create_custom_world_agent_session, delete_custom_world_library_profile,
|
||||
execute_custom_world_agent_action, get_custom_world_agent_card_detail,
|
||||
get_custom_world_agent_operation, get_custom_world_agent_session,
|
||||
get_custom_world_gallery_detail, get_custom_world_library, get_custom_world_library_detail,
|
||||
get_custom_world_works, list_custom_world_gallery, publish_custom_world_library_profile,
|
||||
put_custom_world_library_profile, stream_custom_world_agent_message,
|
||||
submit_custom_world_agent_message, unpublish_custom_world_library_profile,
|
||||
},
|
||||
custom_world_ai::{
|
||||
generate_custom_world_cover_image, generate_custom_world_entity,
|
||||
@@ -359,6 +359,7 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/runtime/custom-world-library/{profile_id}",
|
||||
get(get_custom_world_library_detail)
|
||||
.put(put_custom_world_library_profile)
|
||||
.delete(delete_custom_world_library_profile)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
|
||||
@@ -59,7 +59,7 @@ pub async fn require_bearer_auth(
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AppError> {
|
||||
if request.uri().path().starts_with("/api/runtime/big-fish/")
|
||||
if allows_internal_forwarded_auth(request.uri().path())
|
||||
&& let Some(claims) = try_build_internal_forwarded_claims(&state, request.headers())
|
||||
{
|
||||
request
|
||||
@@ -187,6 +187,11 @@ fn extract_bearer_token(headers: &HeaderMap) -> Result<String, AppError> {
|
||||
Ok(token.to_string())
|
||||
}
|
||||
|
||||
fn allows_internal_forwarded_auth(path: &str) -> bool {
|
||||
// Node 代理已经完成平台账号 JWT 校验,Rust 运行时只信任这些明确的内部转发路径。
|
||||
path.starts_with("/api/runtime/big-fish/") || path.starts_with("/api/runtime/puzzle/")
|
||||
}
|
||||
|
||||
fn try_build_internal_forwarded_claims(
|
||||
state: &AppState,
|
||||
headers: &HeaderMap,
|
||||
@@ -234,7 +239,7 @@ fn try_build_internal_forwarded_claims(
|
||||
mod tests {
|
||||
use super::{
|
||||
INTERNAL_API_SECRET_HEADER, INTERNAL_AUTH_USER_ID_HEADER, RefreshSessionToken,
|
||||
extract_bearer_token, try_build_internal_forwarded_claims,
|
||||
allows_internal_forwarded_auth, extract_bearer_token, try_build_internal_forwarded_claims,
|
||||
};
|
||||
use crate::{config::AppConfig, state::AppState};
|
||||
use axum::{
|
||||
@@ -272,6 +277,15 @@ mod tests {
|
||||
assert_eq!(token.token(), "refresh-token-01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_forwarded_auth_allows_node_proxy_runtime_paths() {
|
||||
assert!(allows_internal_forwarded_auth(
|
||||
"/api/runtime/big-fish/sessions"
|
||||
));
|
||||
assert!(allows_internal_forwarded_auth("/api/runtime/puzzle/works"));
|
||||
assert!(!allows_internal_forwarded_auth("/api/auth/me"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_forwarded_claims_require_matching_secret() {
|
||||
let mut config = AppConfig::default();
|
||||
|
||||
@@ -178,6 +178,42 @@ pub async fn put_custom_world_library_profile(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn delete_custom_world_library_profile(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Path(profile_id): Path<String>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let owner_user_id = authenticated.claims().user_id().to_string();
|
||||
if profile_id.trim().is_empty() {
|
||||
return Err(custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-library",
|
||||
"message": "profileId is required",
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
let entries = state
|
||||
.spacetime_client()
|
||||
.delete_custom_world_profile(profile_id, owner_user_id, current_utc_micros())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
custom_world_error_response(&request_context, map_custom_world_client_error(error))
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
CustomWorldLibraryResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(map_custom_world_library_entry_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn publish_custom_world_library_profile(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
|
||||
Reference in New Issue
Block a user