1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use axum::http::StatusCode;
use axum::{extract::State, Json};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;
use tracing::error;
use utoipa::ToSchema;

use crate::db::User;
use crate::web::utils::validate_api_token;
use crate::web::GoatState;

#[derive(Debug, Deserialize, Clone, ToSchema)]
pub struct AuthPayload {
    pub tokenkey: String,
    pub token: String,
}

#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
pub struct AuthResponse {
    pub message: String,
}

impl From<String> for AuthResponse {
    fn from(message: String) -> Self {
        AuthResponse { message }
    }
}

#[utoipa::path(
    post,
    path = "/login",
    operation_id = "login",
    request_body = AuthPayload,
    responses(
        (status = 200, description = "Login Successful"),
        (status = 403, description = "Auth failed"),
        (status = 500, description = "Something broke!"),
    ),
    tag = "Authentication",
)]
pub async fn login(
    State(state): State<GoatState>,
    session: Session,
    payload: Json<AuthPayload>,
) -> Result<(StatusCode, Json<AuthResponse>), (StatusCode, Json<AuthResponse>)> {
    #[cfg(test)]
    println!("Got login payload: {payload:?}");
    #[cfg(not(test))]
    log::debug!("Got login payload: {payload:?}");
    let mut pool = state.read().await.connpool.clone();
    let token = match User::get_token(&mut pool, &payload.tokenkey).await {
        Ok(val) => val,
        Err(err) => {
            #[cfg(test)]
            println!(
                "action=api_login tokenkey={} result=failure reason=\"no token found\"",
                payload.tokenkey
            );
            log::info!(
                "action=api_login tokenkey={} result=failure reason=\"no token found\"",
                payload.tokenkey
            );
            #[cfg(test)]
            println!("Error: {err:?}");
            log::debug!("Error: {err:?}");
            session.flush().await.map_err(|err| {
                error!("Failed to flush session: {err:?}");
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(AuthResponse::from("Failed to flush session!".to_string())),
                )
            })?;
            let resp = AuthResponse {
                message: "token not found".to_string(),
            };
            return Err((StatusCode::UNAUTHORIZED, Json(resp)));
        }
    };
    match validate_api_token(&token, &payload.token) {
        Ok(_) => {
            println!("Successfully validated token on login");
            let session_user = session.insert("user", &token.user).await;
            let session_authref = session.insert("authref", token.user.authref).await;
            let session_signin = session.insert("signed_in", true).await;

            if session_authref.is_err() | session_user.is_err() | session_signin.is_err() {
                session.flush().await.map_err(|err| {
                    error!("Failed to flush session: {err:?}");
                    (
                        StatusCode::INTERNAL_SERVER_ERROR,
                        Json(AuthResponse::from("Failed to flush session!".to_string())),
                    )
                })?;
                log::info!("action=api_login tokenkey={} result=failure reason=\"failed to store session for user\"", payload.tokenkey);
                return Err((
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(AuthResponse {
                        message: "system failure, please contact an admin".to_string(),
                    }),
                ));
            };
            #[cfg(test)]
            println!("action=api_login user={} result=success", payload.tokenkey);
            #[cfg(not(test))]
            log::info!("action=api_login user={} result=success", payload.tokenkey);
            Ok((
                StatusCode::OK,
                Json(AuthResponse {
                    message: "success".to_string(),
                }),
            ))
        }
        Err(err) => {
            session.flush().await.map_err(|err| {
                error!("Failed to flush session: {err:?}");
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(AuthResponse::from("Failed to flush session!".to_string())),
                )
            })?;
            #[cfg(test)]
            println!("Failed to validate token! {err:?}");
            log::error!(
        "action=api_login username={} userid={} tokenkey=\"{:?}\" result=failure reason=\"failed to match token: {err:?}\"",
        token.user.username,
        token.user.id.unwrap(),
        payload.tokenkey,
        );
            Err((
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(AuthResponse {
                    message: "system failure, please contact an admin".to_string(),
                }),
            ))
        }
    }
}