1
This commit is contained in:
@@ -8,8 +8,12 @@ use axum::{
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use platform_auth::{AccessTokenClaims, read_refresh_session_token, verify_access_token};
|
||||
use platform_auth::{
|
||||
AccessTokenClaims, AuthProvider, BindingStatus, read_refresh_session_token,
|
||||
verify_access_token,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
@@ -17,6 +21,9 @@ use crate::{
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
const INTERNAL_AUTH_USER_ID_HEADER: &str = "x-genarrative-authenticated-user-id";
|
||||
const INTERNAL_API_SECRET_HEADER: &str = "x-genarrative-internal-api-secret";
|
||||
|
||||
// 统一把已校验的 claims 写入 request extensions,避免后续 handler 再次重复解析 Bearer token。
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AuthenticatedAccessToken {
|
||||
@@ -53,6 +60,15 @@ pub async fn require_bearer_auth(
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AppError> {
|
||||
if request.uri().path().starts_with("/api/runtime/big-fish/")
|
||||
&& let Some(claims) = try_build_internal_forwarded_claims(&state, request.headers())
|
||||
{
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(AuthenticatedAccessToken::new(claims));
|
||||
return Ok(next.run(request).await);
|
||||
}
|
||||
|
||||
let bearer_token = extract_bearer_token(request.headers())?;
|
||||
let request_id = request
|
||||
.extensions()
|
||||
@@ -172,13 +188,60 @@ fn extract_bearer_token(headers: &HeaderMap) -> Result<String, AppError> {
|
||||
Ok(token.to_string())
|
||||
}
|
||||
|
||||
fn try_build_internal_forwarded_claims(
|
||||
state: &AppState,
|
||||
headers: &HeaderMap,
|
||||
) -> Option<AccessTokenClaims> {
|
||||
let expected_secret = state.config.internal_api_secret.as_ref()?.trim();
|
||||
if expected_secret.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let provided_secret = headers
|
||||
.get(INTERNAL_API_SECRET_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?;
|
||||
if provided_secret != expected_secret {
|
||||
return None;
|
||||
}
|
||||
|
||||
let user_id = headers
|
||||
.get(INTERNAL_AUTH_USER_ID_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?
|
||||
.to_string();
|
||||
|
||||
// 这里的 claims 只服务于经 Node 已鉴权后的本地内部转发链路,避免在开发态复制整套账号仓储。
|
||||
AccessTokenClaims::from_input(
|
||||
platform_auth::AccessTokenClaimsInput {
|
||||
user_id: user_id.clone(),
|
||||
session_id: format!("internal-forwarded-{user_id}"),
|
||||
provider: AuthProvider::Password,
|
||||
roles: vec!["user".to_string()],
|
||||
token_version: 0,
|
||||
phone_verified: false,
|
||||
binding_status: BindingStatus::Active,
|
||||
display_name: None,
|
||||
},
|
||||
state.auth_jwt_config(),
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{RefreshSessionToken, extract_bearer_token};
|
||||
use super::{
|
||||
INTERNAL_API_SECRET_HEADER, INTERNAL_AUTH_USER_ID_HEADER, RefreshSessionToken,
|
||||
extract_bearer_token, try_build_internal_forwarded_claims,
|
||||
};
|
||||
use axum::{
|
||||
http::{HeaderMap, HeaderValue, StatusCode, header::AUTHORIZATION},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use crate::{config::AppConfig, state::AppState};
|
||||
|
||||
#[test]
|
||||
fn extract_bearer_token_accepts_standard_header() {
|
||||
@@ -209,4 +272,26 @@ mod tests {
|
||||
|
||||
assert_eq!(token.token(), "refresh-token-01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_forwarded_claims_require_matching_secret() {
|
||||
let mut config = AppConfig::default();
|
||||
config.internal_api_secret = Some("bridge-secret".to_string());
|
||||
let state = AppState::new(config).expect("state should build");
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
INTERNAL_AUTH_USER_ID_HEADER,
|
||||
HeaderValue::from_static("user_forwarded_01"),
|
||||
);
|
||||
headers.insert(
|
||||
INTERNAL_API_SECRET_HEADER,
|
||||
HeaderValue::from_static("bridge-secret"),
|
||||
);
|
||||
|
||||
let claims =
|
||||
try_build_internal_forwarded_claims(&state, &headers).expect("claims should resolve");
|
||||
|
||||
assert_eq!(claims.user_id(), "user_forwarded_01");
|
||||
assert_eq!(claims.token_version(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user