dd4650a
use crate::session::{RequestId, Session};
use actix_web::{HttpRequest, web};
use crate::auth::AuthSvc;
use crate::auth::errors::AuthError;
use crate::auth::me::types::MeResponse;
use crate::envelope::Envelope;
use crate::envelope::NoData;
/// 鉴权失败一律映射成 401/503:`JwtError::Config`/`Internal` 是服务端/配置故障,
/// 直接报 401 会让运维把可用性故障当成用户凭证问题。
fn authenticate_error(error: crate::jwt::JwtError) -> AuthError {
match error {
crate::jwt::JwtError::Config(message) => AuthError::Internal(message),
crate::jwt::JwtError::Internal(message) => AuthError::Unavailable(message),
_ => AuthError::Unauthenticated,
}
}
#[utoipa::path(
get,
path = "/api/v1/oauth/me",
tag = "auth",
operation_id = "auth_me",
summary = "当前登录用户",
description = "通过会话 Cookie 或 Bearer 访问令牌返回用户自述信息,含头像地址。Bearer 令牌需具备 profile、email、avatar 权限,并校验账号与会话状态。结果在服务端缓存 300 秒,每第 5 次访问回源刷新;登出与改密会主动失效该缓存。",
responses(
(status = 200, description = "当前登录用户", body = Envelope<MeResponse>, example = json!({"code":200,"message":"success","data":{"user_id":1024,"username":"ada","displayname":"Ada","email":"ada@example.com","is_admin":false,"avatar_url":"https://cdn.example.com/avatar/ada.png","email_verified_at":"2026-01-05T08:30:00Z","created_at":"2025-11-20T02:15:00Z","version":3}})),
(status = 401, description = "会话未绑定用户,或账号已被删除(`unauthenticated`)", body = Envelope<NoData>, example = json!({"code": "unauthenticated", "message": "authentication required"})),
(status = 403, description = "访问令牌权限不足(`scope_insufficient`)", body = Envelope<NoData>),
(status = 500, description = "服务端故障(`internal`、`database`、`cache`、`session`、`serialization`)", body = Envelope<NoData>, example = json!({"code": "internal", "message": "internal server error"}))
)
)]
pub async fn me(
svc: web::Data<AuthSvc>,
session: Session,
request_id: RequestId,
request: HttpRequest,
) -> Result<Envelope<MeResponse>, AuthError> {
let response = if let Some(header) = request.headers().get("authorization") {
let (_, token) = header
.to_str()
.ok()
.and_then(|value| value.split_once(' '))
.filter(|(scheme, token)| {
scheme.eq_ignore_ascii_case("bearer") && !token.trim().is_empty()
})
.ok_or(AuthError::Unauthenticated)?;
let claims = crate::jwt::authority::authenticate(token.trim())
.await
.map_err(authenticate_error)?;
for scope in ["profile", "email", "avatar"] {
crate::jwt::scope::require(&claims, scope).map_err(|_| {
AuthError::ScopeInsufficient {
required: scope.into(),
}
})?;
}
svc.me_for_user(claims.user_id).await?
} else {
svc.me(session, request_id.0).await?
};
Ok(Envelope::ok(response))
}