sysmonk/routes/
auth.rs

1use std::sync::Arc;
2
3use actix_web::cookie::time::{Duration, OffsetDateTime};
4use actix_web::cookie::{Cookie, SameSite};
5use actix_web::http::StatusCode;
6use actix_web::{web, HttpRequest, HttpResponse};
7use fernet::Fernet;
8use minijinja;
9use serde::Serialize;
10
11use crate::{constant, squire};
12
13/// Struct for representing a JSON Response with a redirect URL.
14#[derive(Serialize)]
15struct RedirectResponse {
16    redirect_url: String,
17}
18
19/// Struct for representing detailed errors in JSON format.
20#[derive(Serialize)]
21pub struct DetailError {
22    pub detail: String,
23}
24
25/// Handles the login endpoint, verifying credentials and creating session tokens.
26///
27/// # Arguments
28///
29/// * `request` - A reference to the Actix web `HttpRequest` object.
30/// * `config` - Configuration data for the application.
31/// * `fernet` - Fernet object to encrypt the auth payload that will be set as `session_token` cookie.
32/// * `session` - Session struct that holds the `session_mapping` to handle sessions.
33///
34/// # Returns
35///
36/// * `200` - HttpResponse with a `session_token` and redirect URL to the `/monitor` entrypoint.
37/// * `401` - HttpResponse with an error message for failed authentication.
38#[post("/login")]
39pub async fn login(request: HttpRequest,
40                   config: web::Data<Arc<squire::settings::Config>>,
41                   fernet: web::Data<Arc<Fernet>>,
42                   session: web::Data<Arc<constant::Session>>) -> HttpResponse {
43    let verified = squire::authenticator::verify_login(&request, &config, &session);
44    if let Err(err) = verified {
45        let err_message = err.to_string();
46        log::warn!("Error response::{}", err_message);
47        return HttpResponse::Unauthorized().json(DetailError {
48            detail: err_message
49        });
50    }
51
52    let mapped = verified.unwrap();
53    let payload = serde_json::to_string(&mapped).unwrap();
54    let encrypted_payload = fernet.encrypt(payload.as_bytes());
55
56    let cookie_duration = Duration::seconds(config.session_duration);
57    let expiration = OffsetDateTime::now_utc() + cookie_duration;
58    let cookie = Cookie::build("session_token", encrypted_payload)
59        .http_only(true)
60        .same_site(SameSite::Strict)
61        .max_age(cookie_duration)
62        .expires(expiration)
63        .finish();
64    log::info!("Session for '{}' will be valid until {}", mapped.get("username").unwrap(), expiration);
65
66    let mut response = HttpResponse::Ok().json(RedirectResponse {
67        redirect_url: "/monitor".to_string(),
68    });
69    response.add_cookie(&cookie).unwrap();
70    response
71}
72
73/// Handles the logout endpoint, logging out the user and rendering the appropriate HTML page.
74///
75/// # Arguments
76///
77/// * `request` - A reference to the Actix web `HttpRequest` object.
78/// * `fernet` - Fernet object to encrypt the auth payload that will be set as `session_token` cookie.
79/// * `session` - Session struct that holds the `session_mapping` to handle sessions.
80/// * `metadata` - Struct containing metadata of the application.
81/// * `config` - Configuration data for the application.
82/// * `template` - Configuration container for the loaded templates.
83///
84/// # Returns
85///
86/// Returns an `HTTPResponse` with the cookie for `session_token` reset if available.
87#[get("/logout")]
88pub async fn logout(request: HttpRequest,
89                    fernet: web::Data<Arc<Fernet>>,
90                    session: web::Data<Arc<constant::Session>>,
91                    metadata: web::Data<Arc<constant::MetaData>>,
92                    config: web::Data<Arc<squire::settings::Config>>,
93                    template: web::Data<Arc<minijinja::Environment<'static>>>) -> HttpResponse {
94    let host = request.connection_info().host().to_owned();
95    let logout_template = template.get_template("logout").unwrap();
96    let mut response = HttpResponse::build(StatusCode::OK);
97    response.content_type("text/html; charset=utf-8");
98
99    let rendered;
100    let auth_response = squire::authenticator::verify_token(&request, &config, &fernet, &session);
101    log::debug!("Session Validation Response: {}", auth_response.detail);
102
103    if auth_response.username != "NA" {
104        log::info!("{} from {} attempted to log out", auth_response.username, host)
105    }
106
107    if auth_response.ok {
108        rendered = logout_template.render(minijinja::context!(
109            version => metadata.pkg_version,
110            detail => "You have been logged out successfully."
111        )).unwrap();
112
113        let mut cookie = Cookie::new("session_token", "");
114        cookie.set_same_site(SameSite::Strict);
115        cookie.make_removal();
116        response.cookie(cookie);
117    } else {
118        log::debug!("{} - {}", auth_response.detail, host);
119        rendered = logout_template.render(minijinja::context!(
120                version => metadata.pkg_version,
121                detail => "You are not logged in. Please click the button below to proceed.",
122                show_login => true
123            )).unwrap();
124    }
125    response.body(rendered)
126}
127
128/// Handles the error endpoint, rendering the appropriate HTML page based on session issues.
129///
130/// # Arguments
131///
132/// * `request` - A reference to the Actix web `HttpRequest` object.
133/// * `metadata` - Struct containing metadata of the application.
134/// * `template` - Configuration container for the loaded templates.
135///
136/// # Returns
137///
138/// HttpResponse with either a session expiry or unauthorized message.
139#[get("/error")]
140pub async fn error(request: HttpRequest,
141                   metadata: web::Data<Arc<constant::MetaData>>,
142                   template: web::Data<Arc<minijinja::Environment<'static>>>) -> HttpResponse {
143    if let Some(detail) = request.cookie("detail") {
144        log::info!("Error response for /error: {}", detail.value());
145        let session = template.get_template("session").unwrap();
146        return HttpResponse::build(StatusCode::UNAUTHORIZED)
147            .content_type("text/html; charset=utf-8")
148            .body(session.render(minijinja::context!(
149                version => metadata.pkg_version,
150                reason => detail.value()
151            )).unwrap());
152    }
153
154    log::info!("Sending unauthorized response for /error");
155    let error = template.get_template("error").unwrap();
156    HttpResponse::build(StatusCode::UNAUTHORIZED)
157        .content_type("text/html; charset=utf-8")
158        .body(error.render(minijinja::context!(
159            version => metadata.pkg_version,
160            title => "LOGIN FAILED",
161            description => "USER ERROR - REPLACE USER",
162            help => r"Forgot Password?\n\nRelax and try to remember your password.",
163            button_text => "LOGIN", button_link => "/",
164            block_navigation => true
165        )).unwrap())
166}
167
168/// Constructs an `HttpResponse` for failed `session_token` verification.
169///
170/// # Arguments
171///
172/// * `auth_response` - The authentication response containing details of the failure.
173/// * `config` - Configuration data for the application.
174///
175/// # Returns
176///
177/// Returns an `HttpResponse` with a redirect, setting a cookie with the failure detail.
178pub fn failed_auth(auth_response: squire::authenticator::AuthToken) -> HttpResponse {
179    let mut response = HttpResponse::build(StatusCode::FOUND);
180    let detail = auth_response.detail;
181    let age = Duration::new(3, 0);
182    let cookie = Cookie::build("detail", detail)
183        .path("/error")
184        .http_only(true)
185        .same_site(SameSite::Strict)
186        .max_age(age)
187        .finish();
188    response.cookie(cookie);
189    response.append_header(("Location", "/error"));
190    response.finish()
191}