shaperail_runtime/auth/
tokens.rs1use actix_web::{web, HttpResponse};
2use serde::{Deserialize, Serialize};
3use shaperail_core::ShaperailError;
4use std::sync::Arc;
5
6use super::jwt::JwtConfig;
7
8#[derive(Debug, Deserialize)]
14pub struct TokenRequest {
15 pub user_id: String,
17 pub role: String,
19}
20
21#[derive(Debug, Deserialize)]
23pub struct RefreshRequest {
24 pub refresh_token: String,
26}
27
28#[derive(Debug, Serialize)]
30pub struct TokenPair {
31 pub access_token: String,
32 pub refresh_token: String,
33 pub token_type: String,
34 pub expires_in: i64,
35}
36
37pub async fn handle_issue_token(
42 jwt: web::Data<Arc<JwtConfig>>,
43 body: web::Json<TokenRequest>,
44) -> Result<HttpResponse, ShaperailError> {
45 let access = jwt
46 .encode_access(&body.user_id, &body.role)
47 .map_err(|e| ShaperailError::Internal(format!("Token encoding failed: {e}")))?;
48
49 let refresh = jwt
50 .encode_refresh(&body.user_id, &body.role)
51 .map_err(|e| ShaperailError::Internal(format!("Token encoding failed: {e}")))?;
52
53 let pair = TokenPair {
54 access_token: access,
55 refresh_token: refresh,
56 token_type: "Bearer".to_string(),
57 expires_in: jwt.access_ttl.num_seconds(),
58 };
59
60 Ok(HttpResponse::Ok().json(pair))
61}
62
63pub async fn handle_refresh_token(
67 jwt: web::Data<Arc<JwtConfig>>,
68 body: web::Json<RefreshRequest>,
69) -> Result<HttpResponse, ShaperailError> {
70 let claims = jwt
71 .decode(&body.refresh_token)
72 .map_err(|_| ShaperailError::Unauthorized)?;
73
74 if claims.token_type != "refresh" {
75 return Err(ShaperailError::Unauthorized);
76 }
77
78 let access = jwt
79 .encode_access(&claims.sub, &claims.role)
80 .map_err(|e| ShaperailError::Internal(format!("Token encoding failed: {e}")))?;
81
82 let refresh = jwt
83 .encode_refresh(&claims.sub, &claims.role)
84 .map_err(|e| ShaperailError::Internal(format!("Token encoding failed: {e}")))?;
85
86 let pair = TokenPair {
87 access_token: access,
88 refresh_token: refresh,
89 token_type: "Bearer".to_string(),
90 expires_in: jwt.access_ttl.num_seconds(),
91 };
92
93 Ok(HttpResponse::Ok().json(pair))
94}
95
96pub fn register_auth_routes(cfg: &mut web::ServiceConfig) {
98 cfg.route("/auth/token", web::post().to(handle_issue_token));
99 cfg.route("/auth/refresh", web::post().to(handle_refresh_token));
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn token_request_deserialize() {
108 let json = r#"{"user_id": "u1", "role": "admin"}"#;
109 let req: TokenRequest = serde_json::from_str(json).unwrap();
110 assert_eq!(req.user_id, "u1");
111 assert_eq!(req.role, "admin");
112 }
113
114 #[test]
115 fn refresh_request_deserialize() {
116 let json = r#"{"refresh_token": "some.token.here"}"#;
117 let req: RefreshRequest = serde_json::from_str(json).unwrap();
118 assert_eq!(req.refresh_token, "some.token.here");
119 }
120
121 #[test]
122 fn token_pair_serialize() {
123 let pair = TokenPair {
124 access_token: "at".to_string(),
125 refresh_token: "rt".to_string(),
126 token_type: "Bearer".to_string(),
127 expires_in: 3600,
128 };
129 let json = serde_json::to_value(&pair).unwrap();
130 assert_eq!(json["token_type"], "Bearer");
131 assert_eq!(json["expires_in"], 3600);
132 }
133}