1use crate::api::{ApiResponse, ApiState, extract_bearer_token};
6use axum::{Json, extract::State, http::HeaderMap};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Deserialize)]
11pub struct LoginRequest {
12 pub username: String,
13 pub password: String,
14 #[serde(default)]
15 pub mfa_code: Option<String>,
16 #[serde(default)]
17 pub remember_me: bool,
18}
19
20#[derive(Debug, Serialize)]
22pub struct LoginResponse {
23 pub access_token: String,
24 pub refresh_token: String,
25 pub token_type: String,
26 pub expires_in: u64,
27 pub user: UserInfo,
28}
29
30#[derive(Debug, Serialize)]
32pub struct UserInfo {
33 pub id: String,
34 pub username: String,
35 pub roles: Vec<String>,
36 pub permissions: Vec<String>,
37}
38
39#[derive(Debug, Deserialize)]
41pub struct RefreshRequest {
42 pub refresh_token: String,
43}
44
45#[derive(Debug, Serialize)]
47pub struct RefreshResponse {
48 pub access_token: String,
49 pub token_type: String,
50 pub expires_in: u64,
51}
52
53#[derive(Debug, Deserialize)]
55pub struct LogoutRequest {
56 #[serde(default)]
57 pub refresh_token: Option<String>,
58}
59
60pub async fn login(
62 State(state): State<ApiState>,
63 Json(req): Json<LoginRequest>,
64) -> ApiResponse<LoginResponse> {
65 if req.username.is_empty() || req.password.is_empty() {
67 return ApiResponse::validation_error_typed("Username and password are required");
68 }
69
70 let credential = crate::authentication::credentials::Credential::Password {
72 username: req.username.clone(),
73 password: req.password.clone(),
74 };
75
76 match state
78 .auth_framework
79 .authenticate("password", credential)
80 .await
81 {
82 Ok(auth_result) => match auth_result {
83 crate::auth::AuthResult::Success(token) => {
84 let user_info = UserInfo {
86 id: token.user_id.clone(),
87 username: req.username,
88 roles: token.roles.clone(),
89 permissions: token.permissions.clone(),
90 };
91
92 let token_lifetime = std::time::Duration::from_secs(3600); let access_token = match state.auth_framework.token_manager().create_jwt_token(
95 &token.user_id,
96 token.permissions.clone(),
97 Some(token_lifetime),
98 ) {
99 Ok(jwt) => jwt,
100 Err(e) => {
101 tracing::error!("Failed to create JWT token: {}", e);
102 return ApiResponse::error_typed(
103 "TOKEN_CREATION_FAILED",
104 "Failed to create access token",
105 );
106 }
107 };
108
109 let refresh_token_lifetime = std::time::Duration::from_secs(86400 * 7); let refresh_token = match state.auth_framework.token_manager().create_jwt_token(
112 &token.user_id,
113 vec!["refresh".to_string()],
114 Some(refresh_token_lifetime),
115 ) {
116 Ok(jwt) => jwt,
117 Err(e) => {
118 tracing::error!("Failed to create refresh token: {}", e);
119 return ApiResponse::error_typed(
120 "TOKEN_CREATION_FAILED",
121 "Failed to create refresh token",
122 );
123 }
124 };
125
126 let response = LoginResponse {
127 access_token,
128 refresh_token,
129 token_type: "Bearer".to_string(),
130 expires_in: 3600, user: user_info,
132 };
133
134 ApiResponse::success(response)
135 }
136 crate::auth::AuthResult::MfaRequired(_challenge) => {
137 ApiResponse::error_typed("MFA_REQUIRED", "Multi-factor authentication required")
139 }
140 crate::auth::AuthResult::Failure(reason) => {
141 ApiResponse::error_typed("AUTHENTICATION_FAILED", reason)
142 }
143 },
144 Err(e) => {
145 if matches!(e, crate::errors::AuthError::AuthMethod { .. }) {
147 ApiResponse::error_typed("INVALID_CREDENTIALS", "Invalid username or password")
148 } else {
149 ApiResponse::error_typed("AUTH_ERROR", "Authentication failed")
150 }
151 }
152 }
153}
154
155pub async fn refresh_token(
157 State(state): State<ApiState>,
158 Json(req): Json<RefreshRequest>,
159) -> ApiResponse<RefreshResponse> {
160 if req.refresh_token.is_empty() {
161 return ApiResponse::validation_error_typed("Refresh token is required");
162 }
163
164 match state
166 .auth_framework
167 .token_manager()
168 .validate_jwt_token(&req.refresh_token)
169 {
170 Ok(claims) => {
171 if !claims.scope.contains("refresh") {
173 return ApiResponse::error_typed("INVALID_TOKEN", "Token is not a refresh token");
174 }
175
176 let token_lifetime = std::time::Duration::from_secs(3600); let new_access_token = match state.auth_framework.token_manager().create_jwt_token(
179 &claims.sub,
180 vec!["read".to_string(), "write".to_string()], Some(token_lifetime),
182 ) {
183 Ok(jwt) => jwt,
184 Err(e) => {
185 tracing::error!("Failed to create new access token: {}", e);
186 return ApiResponse::error_typed(
187 "TOKEN_CREATION_FAILED",
188 "Failed to create new access token",
189 );
190 }
191 };
192
193 let response = RefreshResponse {
194 access_token: new_access_token,
195 token_type: "Bearer".to_string(),
196 expires_in: 3600,
197 };
198
199 ApiResponse::success(response)
200 }
201 Err(e) => {
202 tracing::warn!("Invalid refresh token: {}", e);
203 ApiResponse::error_typed("INVALID_TOKEN", "Invalid or expired refresh token")
204 }
205 }
206}
207
208pub async fn logout(
210 State(_state): State<ApiState>,
211 headers: HeaderMap,
212 Json(req): Json<LogoutRequest>,
213) -> ApiResponse<()> {
214 if let Some(token) = extract_bearer_token(&headers) {
216 tracing::info!("Logging out user with token: {}", &token[..10]);
218 }
219
220 if let Some(ref refresh_token) = req.refresh_token {
222 tracing::info!("Invalidating refresh token: {}", &refresh_token[..10]);
223 }
224
225 ApiResponse::<()>::ok_with_message("Successfully logged out")
226}
227
228pub async fn validate_token(
231 State(state): State<ApiState>,
232 headers: HeaderMap,
233) -> ApiResponse<UserInfo> {
234 match extract_bearer_token(&headers) {
235 Some(token) => {
236 match crate::api::validate_api_token(&state.auth_framework, &token).await {
237 Ok(auth_token) => {
238 let username = match state
240 .auth_framework
241 .get_user_profile(&auth_token.user_id)
242 .await
243 {
244 Ok(profile) => profile
245 .username
246 .unwrap_or_else(|| format!("user_{}", auth_token.user_id)),
247 Err(_) => format!("user_{}", auth_token.user_id), };
249
250 let user_info = UserInfo {
251 id: auth_token.user_id,
252 username,
253 roles: auth_token.roles,
254 permissions: auth_token.permissions,
255 };
256 ApiResponse::success(user_info)
257 }
258 Err(_e) => ApiResponse::error_typed("AUTH_ERROR", "Token validation failed"),
259 }
260 }
261 None => ApiResponse::unauthorized_typed(),
262 }
263}
264
265pub async fn list_providers(State(_state): State<ApiState>) -> ApiResponse<Vec<ProviderInfo>> {
268 let providers = vec![
269 ProviderInfo {
270 name: "google".to_string(),
271 display_name: "Google".to_string(),
272 auth_url: "/oauth/google".to_string(),
273 },
274 ProviderInfo {
275 name: "github".to_string(),
276 display_name: "GitHub".to_string(),
277 auth_url: "/oauth/github".to_string(),
278 },
279 ProviderInfo {
280 name: "microsoft".to_string(),
281 display_name: "Microsoft".to_string(),
282 auth_url: "/oauth/microsoft".to_string(),
283 },
284 ];
285
286 ApiResponse::success(providers)
287}
288
289#[derive(Debug, Serialize)]
291pub struct ProviderInfo {
292 pub name: String,
293 pub display_name: String,
294 pub auth_url: String,
295}
296
297