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, state: Extension>, 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, state: Extension>) -> 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")) } #[instrument] pub async fn get_user_or_403(jar: PrivateCookieJar, conn: &mut PoolConnection) -> Result { 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) } }