//! `/api/auth` //! //! Routes dealing in authentication. Routes here integrate with `knowing`, //! which provides our authentication. use std::time::Duration; use axum::{Extension, body::Body, extract::State, http::StatusCode, response::Response}; use gny::web::cookie::{self, SetCookie}; use sessionhash::RequestContext; use somethings::auth::Session; use crate::{Query, RuntimeError, config::Settings, extractor::Wrap, state::SplormState}; /// POST /auth/register pub async fn register( State(state): State, Extension(settings): Extension, ctx: RequestContext, body: String, ) -> Result { tracing::trace!("{ctx} GET /api/register"); if !settings.registration_enabled() { return Err(RuntimeError::Generic403); } macro_rules! get { ($query:ident $field:literal) => { $query .get_first_value($field) .ok_or(RuntimeError::incomplete_form($field)) }; } let query: Query = body.parse()?; let name = get!(query "name")?; let plaintext_password = get!(query "password")?.to_owned(); let result = knowing::user_register(state.database(), name, plaintext_password).await; let _user = match result { Ok(user) => user, Err(knowing::Error::DatabaseError(dbe)) => return Err(dbe.into()), _ => unreachable!(), }; tracing::info!("registered user {name}"); Response::builder() .status(302) .header("Location", "/") .body(Body::from(format!("registered user {name}"))) .map_err(|_| RuntimeError::Unknown) } /// POST /auth/login pub async fn login( State(state): State, ctx: RequestContext, body: String, ) -> Result { tracing::trace!("{ctx} GET /api/login"); macro_rules! get { ($query:ident $field:literal) => { $query .get_first_value($field) .ok_or(RuntimeError::incomplete_form($field)) }; } let query: Query = body.parse()?; let name = get!(query "name")?; let plaintext_password = get!(query "password")?.to_owned(); let result = knowing::user_login(state.database(), name, plaintext_password).await; let session = match result { Ok(user) => user, Err(knowing::Error::DatabaseError(dbe)) => return Err(dbe.into()), Err(knowing::Error::AuthenticationFailed) => { return Err(RuntimeError::authentication_failed(name.to_owned())); } _ => unreachable!(), }; let session_cookie = SetCookie::new("sid", session.sessionid().as_str()) .max_age(Some(cookie::WEEK)) .secure(true) .httponly(true) .path(Some("/")); tracing::info!("user {name} logged in"); Response::builder() .status(302) .header("Location", "/") .header("Set-Cookie", session_cookie.as_string()) .body(Body::from(format!("registered user {name}"))) .map_err(|_| RuntimeError::Unknown) } /// POST /auth/logout pub async fn logout( State(state): State, Wrap(session): Wrap, ctx: RequestContext, ) -> Result { tracing::trace!("{ctx} GET /api/logout"); let result = knowing::remove_session(state.database(), session.sessionid().as_str()).await; match result { Ok(_) => (), Err(knowing::Error::DatabaseError(dbe)) => return Err(dbe.into()), _ => unreachable!(), } let session_cookie = SetCookie::new("sid", "") .max_age(Some(Duration::from_secs(0))) .secure(true) .httponly(true) .path(Some("/")); tracing::info!("user {} logged out", session.user().name()); Response::builder() .status(StatusCode::SEE_OTHER) .header("Location", "/") .header("Set-Cookie", session_cookie.as_string()) .body(Body::from(format!("goodbye {}", session.user().name()))) .map_err(|_| RuntimeError::Unknown) }