Refractored code and implemented logout

This commit is contained in:
xeovalyte 2025-02-20 18:40:10 +01:00
parent 6395df6cec
commit 9eb92ffff1
Signed by: xeovalyte
SSH Key Fingerprint: SHA256:GWI1hq+MNKR2UOcvk7n9tekASXT8vyazK7vDF9Xyciw
4 changed files with 104 additions and 37 deletions

View File

@ -31,10 +31,31 @@
"type": "github" "type": "github"
} }
}, },
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1737465171,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": { "gitignore": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"pre-commit-hooks", "git-hooks",
"nixpkgs" "nixpkgs"
] ]
}, },
@ -66,32 +87,14 @@
"type": "github" "type": "github"
} }
}, },
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1735882644,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "a5a961387e75ae44cc20f0a57ae463da5e959656",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"devenv": "devenv", "devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": [
"git-hooks"
]
} }
} }
}, },

View File

@ -11,21 +11,12 @@ use rand_chacha::ChaCha20Rng;
use sqlx::PgPool; use sqlx::PgPool;
use tokio::task; use tokio::task;
use crate::{ use crate::{database::model::Session, model::User};
database::model::Session,
model::User,
};
mod error; mod error;
pub async fn get_user_from_header(pool: &PgPool, headers: &HeaderMap) -> Result<User, AuthError> { pub async fn get_user_from_header(pool: &PgPool, headers: &HeaderMap) -> Result<User, AuthError> {
let bearer_value = headers.get(header::AUTHORIZATION); let token = get_token_from_headers(&headers)?;
let bearer_value = bearer_value
.ok_or(AuthError::InvalidToken)?
.to_str()
.map_err(|_| AuthError::InvalidToken)?;
let token = get_token_from_bearer(bearer_value)?;
let session = match Session::from_token(pool, &token).await { let session = match Session::from_token(pool, &token).await {
Ok(s) => s, Ok(s) => s,
@ -44,7 +35,13 @@ pub async fn get_user_from_header(pool: &PgPool, headers: &HeaderMap) -> Result<
Ok(db_user.into()) Ok(db_user.into())
} }
pub fn get_token_from_bearer(bearer: &str) -> Result<String, AuthError> { pub fn get_token_from_headers(headers: &HeaderMap) -> Result<String, AuthError> {
let bearer = headers.get(header::AUTHORIZATION);
let bearer = bearer
.ok_or(AuthError::InvalidToken)?
.to_str()
.map_err(|_| AuthError::InvalidToken)?;
match bearer.strip_prefix("Bearer ") { match bearer.strip_prefix("Bearer ") {
Some(token) => Ok(token.to_string()), Some(token) => Ok(token.to_string()),
None => Err(AuthError::InvalidToken), None => Err(AuthError::InvalidToken),

View File

@ -43,4 +43,54 @@ impl Session {
Ok(session) Ok(session)
} }
pub async fn remove_many(
session_ids: &[uuid::Uuid],
transaction: &mut sqlx::Transaction<'_, Postgres>,
) -> Result<(), sqlx::Error> {
let deleted_count = sqlx::query_scalar!(
"
WITH deleted AS (
DELETE FROM sessions
WHERE session_id = ANY($1)
RETURNING 1
)
SELECT COUNT(*) FROM deleted
",
session_ids
)
.fetch_one(&mut **transaction)
.await?;
if !deleted_count.is_some_and(|c| c >= 1) {
return Err(sqlx::Error::RowNotFound);
}
Ok(())
}
pub async fn remove_many_from_token(
transaction: &mut sqlx::Transaction<'_, Postgres>,
session_tokens: &[String],
) -> Result<(), sqlx::Error> {
let deleted_count = sqlx::query_scalar!(
"
WITH deleted AS (
DELETE FROM sessions
WHERE token = ANY($1)
RETURNING 1
)
SELECT COUNT(*) FROM deleted
",
session_tokens
)
.fetch_one(&mut **transaction)
.await?;
if !deleted_count.is_some_and(|c| c >= 1) {
return Err(sqlx::Error::RowNotFound);
}
Ok(())
}
} }

View File

@ -1,8 +1,12 @@
use axum::http::HeaderMap; use axum::http::HeaderMap;
use axum::{extract::State, routing::post, Json, Router}; use axum::{
extract::State,
routing::{get, post},
Json, Router,
};
use serde::Deserialize; use serde::Deserialize;
use crate::auth::verify_password_hash; use crate::auth::{get_token_from_headers, verify_password_hash};
use crate::auth::{get_user_from_header, AuthError}; use crate::auth::{get_user_from_header, AuthError};
use crate::database::model::user::UpdateUser; use crate::database::model::user::UpdateUser;
use crate::database::model::Member as DbMember; use crate::database::model::Member as DbMember;
@ -16,6 +20,7 @@ pub fn routes() -> Router<AppState> {
Router::new() Router::new()
.route("/auth/login", post(login)) .route("/auth/login", post(login))
.route("/auth/register", post(register)) .route("/auth/register", post(register))
.route("/auth/logout", get(logout))
.route("/auth/change_password", post(change_password)) .route("/auth/change_password", post(change_password))
.route("/auth/change_email", post(change_email)) .route("/auth/change_email", post(change_email))
} }
@ -88,6 +93,18 @@ pub async fn register(
Ok(db_session.token) Ok(db_session.token)
} }
pub async fn logout(State(state): State<AppState>, headers: HeaderMap) -> Result<(), crate::Error> {
let registration_token = get_token_from_headers(&headers)?;
let mut transaction = state.pool.begin().await?;
DbSession::remove_many_from_token(&mut transaction, &[registration_token]).await?;
transaction.commit().await?;
Ok(())
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ChangePasswordRequest { pub struct ChangePasswordRequest {
pub old_password: String, pub old_password: String,