ipfrs_interface/
auth_handlers.rs

1//! Authentication API Handlers
2//!
3//! Provides HTTP endpoints for user authentication and management.
4
5use crate::auth::{ApiKey, AuthError, AuthState, Permission, Role, User};
6use crate::gateway::GatewayState;
7use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
8use serde::{Deserialize, Serialize};
9use std::collections::HashSet;
10use uuid::Uuid;
11
12// ============================================================================
13// Request/Response Types
14// ============================================================================
15
16/// Login request
17#[derive(Debug, Deserialize)]
18pub struct LoginRequest {
19    pub username: String,
20    pub password: String,
21}
22
23/// Login response
24#[derive(Debug, Serialize)]
25pub struct LoginResponse {
26    pub token: String,
27    pub expires_in: u64,
28    pub user: UserInfo,
29}
30
31/// User registration request
32#[derive(Debug, Deserialize)]
33pub struct RegisterRequest {
34    pub username: String,
35    pub password: String,
36    pub role: Option<Role>,
37}
38
39/// User registration response
40#[derive(Debug, Serialize)]
41pub struct RegisterResponse {
42    pub success: bool,
43    pub user: UserInfo,
44}
45
46/// User information (without sensitive data)
47#[derive(Debug, Serialize)]
48pub struct UserInfo {
49    pub id: String,
50    pub username: String,
51    pub role: Role,
52    pub permissions: Vec<Permission>,
53    pub active: bool,
54    pub created_at: u64,
55}
56
57impl From<&User> for UserInfo {
58    fn from(user: &User) -> Self {
59        Self {
60            id: user.id.to_string(),
61            username: user.username.clone(),
62            role: user.role,
63            permissions: user.permissions().into_iter().collect(),
64            active: user.active,
65            created_at: user.created_at,
66        }
67    }
68}
69
70/// Token refresh response
71#[derive(Debug, Serialize)]
72pub struct RefreshResponse {
73    pub token: String,
74    pub expires_in: u64,
75}
76
77/// Update permissions request
78#[derive(Debug, Deserialize)]
79pub struct UpdatePermissionsRequest {
80    pub username: String,
81    pub permissions: HashSet<Permission>,
82}
83
84/// Generic success response
85#[derive(Debug, Serialize)]
86pub struct SuccessResponse {
87    pub success: bool,
88    pub message: String,
89}
90
91// ============================================================================
92// Helper Functions
93// ============================================================================
94
95/// Get auth state from gateway state, returning error if not enabled
96fn get_auth_state(state: &GatewayState) -> Result<&AuthState, AuthHandlerError> {
97    state.auth.as_ref().ok_or_else(|| {
98        AuthHandlerError::from(AuthError::InvalidToken(
99            "Authentication not enabled".to_string(),
100        ))
101    })
102}
103
104// ============================================================================
105// Handler Functions
106// ============================================================================
107
108/// Login endpoint
109///
110/// Authenticates user with username and password, returns JWT token.
111///
112/// POST /api/v0/auth/login
113pub async fn login_handler(
114    State(gateway_state): State<GatewayState>,
115    Json(req): Json<LoginRequest>,
116) -> Result<Json<LoginResponse>, AuthHandlerError> {
117    let auth_state = get_auth_state(&gateway_state)?;
118
119    // Authenticate user
120    let user = auth_state
121        .user_store
122        .authenticate(&req.username, &req.password)?;
123
124    // Generate JWT token (24 hour expiration)
125    let token = auth_state.jwt_manager.generate_token(&user, 24)?;
126
127    Ok(Json(LoginResponse {
128        token,
129        expires_in: 24 * 3600, // 24 hours in seconds
130        user: UserInfo::from(&user),
131    }))
132}
133
134/// Register endpoint
135///
136/// Creates a new user account. Only admins can specify role, otherwise defaults to User.
137///
138/// POST /api/v0/auth/register
139pub async fn register_handler(
140    State(gateway_state): State<GatewayState>,
141    Json(req): Json<RegisterRequest>,
142) -> Result<Json<RegisterResponse>, AuthHandlerError> {
143    let auth_state = get_auth_state(&gateway_state)?;
144
145    // For now, default to User role if not specified
146    // In production, you'd check if the requester is admin before allowing role specification
147    let role = req.role.unwrap_or(Role::User);
148
149    // Create new user
150    let user = User::new(req.username, &req.password, role)?;
151
152    // Add to store
153    auth_state.user_store.add_user(user.clone())?;
154
155    Ok(Json(RegisterResponse {
156        success: true,
157        user: UserInfo::from(&user),
158    }))
159}
160
161/// Get current user info
162///
163/// Returns information about the authenticated user.
164///
165/// GET /api/v0/auth/me
166pub async fn me_handler(
167    State(gateway_state): State<GatewayState>,
168    headers: axum::http::HeaderMap,
169) -> Result<Json<UserInfo>, AuthHandlerError> {
170    let auth_state = get_auth_state(&gateway_state)?;
171
172    // Extract and validate token
173    let token = extract_token_from_headers(&headers)?;
174    let claims = auth_state.jwt_manager.validate_token(token)?;
175
176    let user = auth_state.user_store.get_user(&claims.username)?;
177
178    Ok(Json(UserInfo::from(&user)))
179}
180
181/// Extract JWT token from headers
182fn extract_token_from_headers(headers: &axum::http::HeaderMap) -> Result<&str, AuthError> {
183    let auth_header = headers
184        .get(axum::http::header::AUTHORIZATION)
185        .and_then(|h| h.to_str().ok())
186        .ok_or(AuthError::InvalidToken(
187            "Missing Authorization header".to_string(),
188        ))?;
189
190    auth_header
191        .strip_prefix("Bearer ")
192        .ok_or_else(|| AuthError::InvalidToken("Invalid Authorization format".to_string()))
193}
194
195/// Update user permissions (admin only)
196///
197/// Updates custom permissions for a user.
198///
199/// POST /api/v0/auth/permissions
200pub async fn update_permissions_handler(
201    State(gateway_state): State<GatewayState>,
202    headers: axum::http::HeaderMap,
203    Json(req): Json<UpdatePermissionsRequest>,
204) -> Result<Json<SuccessResponse>, AuthHandlerError> {
205    let auth_state = get_auth_state(&gateway_state)?;
206
207    // Extract and validate token
208    let token = extract_token_from_headers(&headers)?;
209    let claims = auth_state.jwt_manager.validate_token(token)?;
210
211    // Check if requester is admin
212    let requester = auth_state.user_store.get_user(&claims.username)?;
213
214    if !requester.has_permission(Permission::SystemAdmin) {
215        return Err(AuthHandlerError::from(AuthError::InsufficientPermissions));
216    }
217
218    // Update permissions
219    auth_state
220        .user_store
221        .update_permissions(&req.username, req.permissions)?;
222
223    Ok(Json(SuccessResponse {
224        success: true,
225        message: format!("Permissions updated for user: {}", req.username),
226    }))
227}
228
229/// Deactivate user (admin only)
230///
231/// Deactivates a user account.
232///
233/// POST /api/v0/auth/deactivate/:username
234pub async fn deactivate_user_handler(
235    State(gateway_state): State<GatewayState>,
236    headers: axum::http::HeaderMap,
237    axum::extract::Path(username): axum::extract::Path<String>,
238) -> Result<Json<SuccessResponse>, AuthHandlerError> {
239    let auth_state = get_auth_state(&gateway_state)?;
240
241    // Extract and validate token
242    let token = extract_token_from_headers(&headers)?;
243    let claims = auth_state.jwt_manager.validate_token(token)?;
244
245    // Check if requester is admin
246    let requester = auth_state.user_store.get_user(&claims.username)?;
247
248    if !requester.has_permission(Permission::SystemAdmin) {
249        return Err(AuthHandlerError::from(AuthError::InsufficientPermissions));
250    }
251
252    // Deactivate user
253    auth_state.user_store.deactivate_user(&username)?;
254
255    Ok(Json(SuccessResponse {
256        success: true,
257        message: format!("User deactivated: {}", username),
258    }))
259}
260
261// ============================================================================
262// API Key Management Handlers
263// ============================================================================
264
265/// Create API key request
266#[derive(Debug, Deserialize)]
267pub struct CreateApiKeyRequest {
268    pub name: String,
269}
270
271/// Create API key response
272#[derive(Debug, Serialize)]
273pub struct CreateApiKeyResponse {
274    pub key: String,
275    pub key_info: ApiKeyInfo,
276}
277
278/// API key information (sanitized, no key_hash)
279#[derive(Debug, Serialize)]
280pub struct ApiKeyInfo {
281    pub id: String,
282    pub prefix: String,
283    pub name: String,
284    pub created_at: u64,
285    pub last_used_at: Option<u64>,
286    pub active: bool,
287}
288
289impl From<&ApiKey> for ApiKeyInfo {
290    fn from(key: &ApiKey) -> Self {
291        Self {
292            id: key.id.to_string(),
293            prefix: key.prefix.clone(),
294            name: key.name.clone(),
295            created_at: key.created_at,
296            last_used_at: key.last_used_at,
297            active: key.active,
298        }
299    }
300}
301
302/// Create new API key
303///
304/// POST /api/v0/auth/keys
305pub async fn create_api_key_handler(
306    State(gateway_state): State<GatewayState>,
307    headers: axum::http::HeaderMap,
308    Json(req): Json<CreateApiKeyRequest>,
309) -> Result<Json<CreateApiKeyResponse>, AuthHandlerError> {
310    let auth_state = get_auth_state(&gateway_state)?;
311
312    // Extract and validate token
313    let token = extract_token_from_headers(&headers)?;
314    let claims = auth_state.jwt_manager.validate_token(token)?;
315
316    // Get user
317    let user = auth_state.user_store.get_user(&claims.username)?;
318
319    // Create API key
320    let (api_key, raw_key) = ApiKey::new(user.id, req.name)?;
321
322    // Store key
323    auth_state.api_key_store.add_key(api_key.clone())?;
324
325    Ok(Json(CreateApiKeyResponse {
326        key: raw_key,
327        key_info: ApiKeyInfo::from(&api_key),
328    }))
329}
330
331/// List user's API keys
332///
333/// GET /api/v0/auth/keys
334pub async fn list_api_keys_handler(
335    State(gateway_state): State<GatewayState>,
336    headers: axum::http::HeaderMap,
337) -> Result<Json<Vec<ApiKeyInfo>>, AuthHandlerError> {
338    let auth_state = get_auth_state(&gateway_state)?;
339
340    // Extract and validate token
341    let token = extract_token_from_headers(&headers)?;
342    let claims = auth_state.jwt_manager.validate_token(token)?;
343
344    // Get user
345    let user = auth_state.user_store.get_user(&claims.username)?;
346
347    // List keys
348    let keys = auth_state.api_key_store.list_user_keys(&user.id);
349    let key_infos: Vec<ApiKeyInfo> = keys.iter().map(ApiKeyInfo::from).collect();
350
351    Ok(Json(key_infos))
352}
353
354/// Revoke API key
355///
356/// POST /api/v0/auth/keys/:key_id/revoke
357pub async fn revoke_api_key_handler(
358    State(gateway_state): State<GatewayState>,
359    headers: axum::http::HeaderMap,
360    axum::extract::Path(key_id_str): axum::extract::Path<String>,
361) -> Result<Json<SuccessResponse>, AuthHandlerError> {
362    let auth_state = get_auth_state(&gateway_state)?;
363
364    // Extract and validate token
365    let token = extract_token_from_headers(&headers)?;
366    let claims = auth_state.jwt_manager.validate_token(token)?;
367
368    // Get user
369    let user = auth_state.user_store.get_user(&claims.username)?;
370
371    // Parse key ID
372    let key_id = Uuid::parse_str(&key_id_str)
373        .map_err(|_| AuthHandlerError::from(AuthError::InvalidCredentials))?;
374
375    // Get key and verify ownership
376    let key = auth_state.api_key_store.get_key(&key_id)?;
377    if key.user_id != user.id && !user.has_permission(Permission::SystemAdmin) {
378        return Err(AuthHandlerError::from(AuthError::InsufficientPermissions));
379    }
380
381    // Revoke key
382    auth_state.api_key_store.revoke_key(&key_id)?;
383
384    Ok(Json(SuccessResponse {
385        success: true,
386        message: format!("API key revoked: {}", key_id),
387    }))
388}
389
390/// Delete API key
391///
392/// DELETE /api/v0/auth/keys/:key_id
393pub async fn delete_api_key_handler(
394    State(gateway_state): State<GatewayState>,
395    headers: axum::http::HeaderMap,
396    axum::extract::Path(key_id_str): axum::extract::Path<String>,
397) -> Result<Json<SuccessResponse>, AuthHandlerError> {
398    let auth_state = get_auth_state(&gateway_state)?;
399
400    // Extract and validate token
401    let token = extract_token_from_headers(&headers)?;
402    let claims = auth_state.jwt_manager.validate_token(token)?;
403
404    // Get user
405    let user = auth_state.user_store.get_user(&claims.username)?;
406
407    // Parse key ID
408    let key_id = Uuid::parse_str(&key_id_str)
409        .map_err(|_| AuthHandlerError::from(AuthError::InvalidCredentials))?;
410
411    // Get key and verify ownership
412    let key = auth_state.api_key_store.get_key(&key_id)?;
413    if key.user_id != user.id && !user.has_permission(Permission::SystemAdmin) {
414        return Err(AuthHandlerError::from(AuthError::InsufficientPermissions));
415    }
416
417    // Delete key
418    auth_state.api_key_store.delete_key(&key_id)?;
419
420    Ok(Json(SuccessResponse {
421        success: true,
422        message: format!("API key deleted: {}", key_id),
423    }))
424}
425
426// ============================================================================
427// Error Handling
428// ============================================================================
429
430/// Authentication handler error
431#[derive(Debug)]
432pub struct AuthHandlerError {
433    error: AuthError,
434}
435
436impl From<AuthError> for AuthHandlerError {
437    fn from(error: AuthError) -> Self {
438        Self { error }
439    }
440}
441
442impl IntoResponse for AuthHandlerError {
443    fn into_response(self) -> axum::response::Response {
444        let (status, message) = match self.error {
445            AuthError::InvalidCredentials => {
446                (StatusCode::UNAUTHORIZED, "Invalid credentials".to_string())
447            }
448            AuthError::InvalidToken(msg) => (StatusCode::UNAUTHORIZED, msg),
449            AuthError::TokenExpired => (StatusCode::UNAUTHORIZED, "Token expired".to_string()),
450            AuthError::InsufficientPermissions => (
451                StatusCode::FORBIDDEN,
452                "Insufficient permissions".to_string(),
453            ),
454            AuthError::UserNotFound => (StatusCode::NOT_FOUND, "User not found".to_string()),
455            AuthError::HashError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
456            AuthError::JwtError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
457        };
458
459        (status, message).into_response()
460    }
461}