aboutsummaryrefslogblamecommitdiff
path: root/src/handlers/auth.rs
blob: ab72bc8b8e35f149535d570eb18b16c3e0a086cf (plain) (tree)
1
2
3
4
5
6
7




                                                                      

                                                            

































                                                                                                                                                                  
                                                                                                  


                               


                                                                           
 



                                                           
     




























                                                                                                                                                                                                        
 











                                                                                                       




















                                                                                                                          
use std::sync::Arc;

use axum::{response::{IntoResponse, Html, Redirect}, Form, Extension};
use axum_extra::extract::{PrivateCookieJar, cookie::Cookie};
use serde::Deserialize;
use sqlx::{query, query_as, pool::PoolConnection, Postgres};
use tracing::{debug, instrument};
use crate::{errors::ServiceError, State, models::DbUser};
use chrono::prelude::*;

#[derive(Deserialize, Debug)]
pub struct RegisterForm {
    pub email: String,
    pub prefname: String,
    pub password: String,
    #[serde(rename="password-confirm")]
    pub password_confirm: String
}

#[derive(Deserialize)]
pub struct LoginForm {
    pub email: String,
    pub password: String,
}

pub async fn login() -> impl IntoResponse {
    let mut buf = Vec::new();
    crate::templates::login_html(&mut buf).unwrap();

    Html(buf)
}

pub async fn login_post(Form(login): Form<LoginForm>, state: Extension<Arc<State>>, jar: PrivateCookieJar) -> Result<(PrivateCookieJar, Redirect), ServiceError> {
    let mut conn = state.conn.acquire().await?;

    let user: DbUser = query_as("SELECT * FROM users WHERE email=$1").bind(login.email)
        .fetch_one(&mut conn)
        .await?;

    if bcrypt::verify(login.password, &user.pw_hash)? {
        debug!("Logged in ID {} (email {})", user.id, user.email);
        query("UPDATE users SET last_login=$1 WHERE id=$2").bind(Utc::now()).bind(user.id.clone())
            .execute(&mut conn)
            .await?;

        let updated_jar = jar.add(Cookie::build("user-id", user.id.clone())
                                  .path("/")
                                  .finish());

        Ok((updated_jar, Redirect::to("/")))
    } else {
        let updated_jar = jar;
        Ok((updated_jar, Redirect::to("/dash/auth/login")))
    }
}

pub async fn register() -> impl IntoResponse {
    let mut buf = Vec::new();
    crate::templates::register_html(&mut buf).unwrap();

    Html(buf)
}

pub async fn register_post(Form(reg): Form<RegisterForm>, state: Extension<Arc<State>>) -> impl IntoResponse {
    if reg.password_confirm == reg.password {
        let hash = match bcrypt::hash(reg.password, bcrypt::DEFAULT_COST) {
            Ok(h) => h,
            Err(e) => {
                return Err(ServiceError::NotAuthorized);
            }
        };
        
        let mut conn = state.conn.acquire().await?;
        let ulid = ulid::Ulid::new();

        query("INSERT INTO users (id, email, pref_name, pw_hash, last_login) VALUES ($1, $2, $3, $4, $5)").bind(ulid.to_string()).bind(reg.email.clone()).bind(reg.prefname).bind(hash).bind(Utc::now())
            .execute(&mut conn)
            .await?;
    
    }

    Ok(Redirect::to("/dash/auth/login"))
}

pub async fn logout_post(jar: PrivateCookieJar) -> Result<(PrivateCookieJar, Redirect), ServiceError> {
    if let Some(id) = jar.get("user-id") {
        debug!("Found user {}", id);

        let updated_jar = jar.remove(id);

        Ok((updated_jar, Redirect::to("/dash/auth/login")))
    } else {
        Ok((jar, Redirect::to("/")))
    }
}

#[instrument]
pub async fn get_user_or_403(jar: PrivateCookieJar, conn: &mut PoolConnection<Postgres>) -> Result<DbUser, ServiceError> {
    debug!("Starting middleware get_user_or_403");
    debug!("Displaying all cookies");
    for c in jar.iter() {
        debug!("{}={}", c.name(), c.value());
    }
    if let Some(id) = jar.get("user-id") {
        debug!("Found user {}", id);

        let user: DbUser = query_as("SELECT * FROM users WHERE id=$1").bind(id.value())
            .fetch_one(conn)
            .await?;

        Ok(user)

    } else {
        debug!("No user found");
        Err(ServiceError::NotAuthorized)
    }
}