avl_console/
auth.rs

1//! Authentication module
2
3use crate::{
4    error::{ConsoleError, Result},
5    state::AppState,
6};
7use axum::{
8    extract::State,
9    http::StatusCode,
10    response::IntoResponse,
11    routing::{get, post},
12    Json, Router,
13};
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16
17pub fn routes(state: Arc<AppState>) -> Router {
18    Router::new()
19        .route("/login", post(login))
20        .route("/logout", post(logout))
21        .route("/me", get(current_user))
22        .with_state(state)
23}
24
25#[derive(Debug, Deserialize)]
26struct LoginRequest {
27    username: String,
28    password: String,
29}
30
31#[derive(Debug, Serialize)]
32struct LoginResponse {
33    session_id: String,
34    user: UserInfo,
35}
36
37#[derive(Debug, Serialize, Clone)]
38pub struct UserInfo {
39    pub id: String,
40    pub username: String,
41    pub email: String,
42    pub role: String,
43}
44
45async fn login(
46    State(state): State<Arc<AppState>>,
47    Json(req): Json<LoginRequest>,
48) -> Result<impl IntoResponse> {
49    // TODO: Call avl-auth service to validate credentials
50    // For now, mock authentication
51    if req.username == "admin" && req.password == "admin" {
52        let user = UserInfo {
53            id: "user_001".to_string(),
54            username: req.username.clone(),
55            email: format!("{}@avila.cloud", req.username),
56            role: "admin".to_string(),
57        };
58
59        let session_id = generate_session_id();
60        state.store_session(session_id.clone(), user.id.clone()).await;
61
62        let response = LoginResponse {
63            session_id: session_id.clone(),
64            user,
65        };
66
67        // Set session cookie
68        let cookie = format!(
69            "avl_session={}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=86400",
70            session_id
71        );
72
73        Ok((
74            StatusCode::OK,
75            [("Set-Cookie", cookie)],
76            Json(response),
77        ))
78    } else {
79        Err(ConsoleError::Authentication(
80            "Invalid credentials".to_string(),
81        ))
82    }
83}
84
85async fn logout(State(_state): State<Arc<AppState>>) -> impl IntoResponse {
86    // TODO: Extract session from cookie and remove it
87    let cookie = "avl_session=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0";
88
89    (StatusCode::OK, [("Set-Cookie", cookie)], "Logged out")
90}
91
92async fn current_user(State(_state): State<Arc<AppState>>) -> Result<Json<UserInfo>> {
93    // TODO: Extract user from session
94    Ok(Json(UserInfo {
95        id: "user_001".to_string(),
96        username: "admin".to_string(),
97        email: "admin@avila.cloud".to_string(),
98        role: "admin".to_string(),
99    }))
100}
101
102fn generate_session_id() -> String {
103    use std::time::{SystemTime, UNIX_EPOCH};
104    let timestamp = SystemTime::now()
105        .duration_since(UNIX_EPOCH)
106        .unwrap()
107        .as_nanos();
108    format!("sess_{:x}", timestamp)
109}