From b6a211bbcf35b3f40f2bb8476df05ec98266b5c1 Mon Sep 17 00:00:00 2001 From: jakubmanczak Date: Fri, 27 Feb 2026 23:46:37 +0100 Subject: [PATCH] login & logout --- src/api/auth.rs | 7 +- src/users/auth/implementation.rs | 108 ++++++++++++++++++++----------- src/users/auth/mod.rs | 11 +++- 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index 87ed78d..1b3f6bc 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -8,7 +8,7 @@ use serde::Deserialize; use crate::users::{ User, auth::{ - AuthError, COOKIE_NAME, UserAuthRequired, UserAuthenticate, + AuthError, COOKIE_NAME, SessionAuthRequired, SessionAuthenticate, UserAuthRequired, implementation::authenticate_via_credentials, }, sessions::Session, @@ -38,5 +38,8 @@ pub async fn login(Json(creds): Json) -> Result } pub async fn logout(headers: HeaderMap) -> Result { - todo!() + let mut s = Session::authenticate(&headers)?.required()?; + s.revoke(Some(&User::get_by_id(s.user_id)?))?; + let cookie = format!("{COOKIE_NAME}=revoking; Path=/; HttpOnly; Max-Age=0"); + Ok(([(header::SET_COOKIE, cookie)], "Logged out!").into_response()) } diff --git a/src/users/auth/implementation.rs b/src/users/auth/implementation.rs index f886ce3..c2cca0b 100644 --- a/src/users/auth/implementation.rs +++ b/src/users/auth/implementation.rs @@ -15,8 +15,8 @@ use crate::{ users::{ User, auth::{ - AuthError, COOKIE_NAME, TokenSize, UserAuthDummyData, UserAuthRequired, - UserAuthenticate, UserPasswordHashing, + AuthError, COOKIE_NAME, SessionAuthRequired, SessionAuthenticate, TokenSize, + UserAuthDummyData, UserAuthRequired, UserAuthenticate, UserPasswordHashing, }, sessions::Session, }, @@ -63,6 +63,14 @@ impl UserAuthRequired for Option { } } } +impl SessionAuthRequired for Option { + fn required(self) -> Result { + match self { + Self::None => Err(AuthError::AuthRequired), + Self::Some(s) => Ok(s), + } + } +} impl UserPasswordHashing for User { fn hash_password(passw: &str) -> Result { @@ -122,49 +130,63 @@ impl<'a> AuthScheme<'a> { impl UserAuthenticate for User { fn authenticate(headers: &HeaderMap) -> Result, AuthError> { - let mut auth_values = Vec::new(); - for auth_header in headers.get_all(AUTHORIZATION).iter() { - if let Ok(s) = auth_header.to_str() { - auth_values.push(s.to_string()); - } - } - for cookie_header in headers.get_all(COOKIE).iter() { - if let Ok(cookies) = cookie_header.to_str() { - for cookie in cookies.split(';') { - let cookie = cookie.trim(); - if let Some(value) = cookie.strip_prefix(&format!("{}=", COOKIE_NAME)) { - auth_values.push(format!("Bearer {}", value)); - } - } - } - } - - let mut basic_auth: Option<&str> = None; - let mut bearer_auth: Option<&str> = None; - for header in &auth_values { - let header = header.trim(); - match AuthScheme::from_header(header) { - AuthScheme::Basic(creds) => { - if basic_auth.is_none() { - basic_auth = Some(creds); - } - } - AuthScheme::Bearer(token) => { - if bearer_auth.is_none() { - bearer_auth = Some(token); - } - } - AuthScheme::None => {} - } - } + let (basic_auth, bearer_auth) = auth_common(&headers); match (basic_auth, bearer_auth) { - (Some(creds), _) => authenticate_basic(creds), - (None, Some(token)) => authenticate_bearer(token), + (Some(creds), _) => authenticate_basic(&creds), + (None, Some(token)) => authenticate_bearer(&token), _ => Ok(None), } } } +impl SessionAuthenticate for Session { + fn authenticate(headers: &HeaderMap) -> Result, AuthError> { + let (_, bearer_auth) = auth_common(&headers); + if let Some(token) = bearer_auth { + authenticate_bearer_with_session(&token) + } else { + Ok(None) + } + } +} + +fn auth_common(headers: &HeaderMap) -> (Option, Option) { + let mut auth_values = Vec::new(); + for auth_header in headers.get_all(AUTHORIZATION).iter() { + if let Ok(s) = auth_header.to_str() { + auth_values.push(s.to_string()); + } + } + for cookie_header in headers.get_all(COOKIE).iter() { + if let Ok(cookies) = cookie_header.to_str() { + for cookie in cookies.split(';') { + let cookie = cookie.trim(); + if let Some(value) = cookie.strip_prefix(&format!("{}=", COOKIE_NAME)) { + auth_values.push(format!("Bearer {}", value)); + } + } + } + } + let mut basic_auth: Option = None; + let mut bearer_auth: Option = None; + for header in &auth_values { + let header = header.trim(); + match AuthScheme::from_header(header) { + AuthScheme::Basic(creds) => { + if basic_auth.is_none() { + basic_auth = Some(creds.into()); + } + } + AuthScheme::Bearer(token) => { + if bearer_auth.is_none() { + bearer_auth = Some(token.into()); + } + } + AuthScheme::None => {} + } + } + (basic_auth, bearer_auth) +} fn authenticate_basic(credentials: &str) -> Result, AuthError> { let decoded = BASE64_STANDARD.decode(credentials)?; @@ -205,3 +227,11 @@ fn authenticate_bearer(token: &str) -> Result, AuthError> { s.prolong()?; Ok(Some(User::get_by_id(s.user_id)?)) } +fn authenticate_bearer_with_session(token: &str) -> Result, AuthError> { + let mut s = Session::get_by_token(token)?; + if s.is_expired_or_revoked() { + return Err(AuthError::InvalidCredentials); + } + s.prolong()?; + Ok(Some(s)) +} diff --git a/src/users/auth/mod.rs b/src/users/auth/mod.rs index db34a0c..53cd935 100644 --- a/src/users/auth/mod.rs +++ b/src/users/auth/mod.rs @@ -1,7 +1,10 @@ use axum::http::HeaderMap; use rand08::{RngCore, rngs::OsRng}; -use crate::users::{User, UserError, sessions::SessionError}; +use crate::users::{ + User, UserError, + sessions::{Session, SessionError}, +}; pub mod implementation; @@ -13,6 +16,12 @@ pub trait UserAuthenticate { pub trait UserAuthRequired { fn required(self) -> Result; } +pub trait SessionAuthenticate { + fn authenticate(headers: &HeaderMap) -> Result, AuthError>; +} +pub trait SessionAuthRequired { + fn required(self) -> Result; +} pub trait UserPasswordHashing { /// Returns the hashed password as a String