1use 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 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 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 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 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}