tdm_server_rust/middleware/
auth.rs1use crate::{
14 app::AppState,
15 cache::get_auth_snapshot_cached,
16 common::result::permission_denied_response,
17 entity::member::Member,
18 error::AppError,
19 utils::jwt::{JwtClaims, JwtUtil},
20};
21use axum::{
22 body::Body,
23 extract::State,
24 http::{Method, Request},
25 middleware::Next,
26 response::Response,
27};
28
29#[derive(Debug, Clone)]
31pub struct AuthMember(pub Option<Member>);
32
33#[tracing::instrument(skip_all, level = "info")]
35pub async fn auth_middleware(
36 State(state): State<AppState>,
37 mut req: Request<Body>,
38 next: Next,
39) -> Result<Response, AppError> {
40 let uri = req.uri().path().to_string();
41 let method = req.method().clone();
42
43 if uri.contains("login") || uri.contains("reg") {
44 req.extensions_mut().insert(AuthMember(None));
45 return Ok(next.run(req).await);
46 }
47
48 if method == Method::OPTIONS {
49 req.extensions_mut().insert(AuthMember(None));
50 return Ok(next.run(req).await);
51 }
52
53 if uri.contains("undefined") {
54 return Err(AppError::login_expired("未登录哦 快加入提灯喵接坑吧喵!"));
55 }
56
57 let token = extract_token(req.headers());
58 if method != Method::GET && token.is_none() {
59 return Err(AppError::login_expired("请登录一下哦喵……"));
60 }
61
62 let mut member: Option<Member> = None;
63 if let Some(jwt) = token.as_deref() {
64 let claims = parse_token(&state, jwt)?;
65 member = Some(load_member(&state, claims.id).await?);
66 }
67
68 check_route_permission(&uri, token.as_deref())?;
69
70 if uri.starts_with("/api/evaluations") {
71 if let Some(ref m) = member {
72 let posts = &m.post_ids;
73 let has = posts.contains(&2) || posts.contains(&4);
74 if !has && !posts.contains(&4) {
75 return Ok(permission_denied_response());
76 }
77 } else {
78 return Ok(permission_denied_response());
79 }
80 }
81
82 req.extensions_mut().insert(AuthMember(member));
83 Ok(next.run(req).await)
84}
85
86#[tracing::instrument(name = "auth::parse_token", skip(state, token), level = "info")]
88fn parse_token(state: &AppState, token: &str) -> Result<JwtClaims, AppError> {
89 let util = JwtUtil::new(&state.config.jwt.sign_key, state.config.jwt.expire_ms);
90 util.parse(token).map_err(|e| {
91 if e.to_string().contains("ExpiredSignature") {
92 AppError::login_expired("登录数据过期,请重新登录喵!")
93 } else {
94 AppError::login_expired("Token解析失败喵!请重新登录喵!")
95 }
96 })
97}
98
99#[tracing::instrument(name = "auth::load_member", skip(state), level = "info")]
101async fn load_member(state: &AppState, member_id: i32) -> Result<Member, AppError> {
102 let m = get_auth_snapshot_cached(state, member_id).await?;
103 if crate::entity::enums::MemberInternEnum::is_left(m.intern) {
104 return Err(AppError::login_expired("你已经退出了提灯喵猫娘化计划喵……"));
105 }
106 Ok(m)
107}
108
109#[tracing::instrument(name = "auth::check_permission", skip(uri, token), level = "info")]
111fn check_route_permission(uri: &str, token: Option<&str>) -> Result<(), AppError> {
112 let needs_strict = uri.contains("takeEpisode")
113 || uri.contains("invitationCodes")
114 || uri.contains("download");
115 if needs_strict && token.is_none() {
116 return Err(AppError::login_expired("必须要登录才能访问这里喵!"));
117 }
118 Ok(())
119}
120
121fn extract_token(headers: &axum::http::HeaderMap) -> Option<String> {
123 headers
124 .get("token")
125 .or_else(|| headers.get("Authorization"))
126 .and_then(|v| v.to_str().ok())
127 .map(|s| s.to_string())
128}