From 29bfa8c60e7d79c05380701d0a38ecefde67284f Mon Sep 17 00:00:00 2001 From: xeovalyte Date: Fri, 7 Feb 2025 17:03:27 +0100 Subject: [PATCH] Added function to get all roles of members --- server/src/auth.rs | 109 +++++++--------------------- server/src/auth/bearer.rs | 8 -- server/src/auth/scopes.rs | 1 - server/src/database/model/user.rs | 18 +++++ server/src/model/member.rs | 1 + server/src/model/session.rs | 2 +- server/src/routes.rs | 16 +--- server/src/routes/member.rs | 3 +- server/src/routes/member/migrate.rs | 14 ++-- 9 files changed, 56 insertions(+), 116 deletions(-) delete mode 100644 server/src/auth/bearer.rs delete mode 100644 server/src/auth/scopes.rs diff --git a/server/src/auth.rs b/server/src/auth.rs index ddec7a2..b5c1f00 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -1,21 +1,8 @@ -use std::collections::HashSet; - use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, Argon2, PasswordHash, PasswordVerifier, }; -use axum::{ - extract::FromRequestParts, - http::{header, request::Parts, HeaderMap, StatusCode}, - RequestPartsExt, -}; -use axum_extra::{ - extract::cookie::{Cookie, CookieJar}, - headers::{authorization::Bearer, Authorization}, - typed_header::TypedHeaderRejectionReason, - TypedHeader, -}; -use bearer::verify_bearer; +use axum::http::{header, HeaderMap}; use chrono::Utc; pub use error::AuthError; use rand::distr::Alphanumeric; @@ -24,58 +11,17 @@ use rand_chacha::ChaCha20Rng; use sqlx::PgPool; use tokio::task; -use crate::{database::model::Session, model::User}; +use crate::{ + database::model::{Session, UserMember}, + model::{member::Roles, User}, +}; -mod bearer; mod error; -mod scopes; -#[derive(Debug)] -pub struct Permissions<'a>(pub HashSet<&'a str>); - -// Middleware for getting permissions -impl FromRequestParts for Permissions<'_> -where - S: Send + Sync, -{ - type Rejection = crate::Error; - - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - // First check if the request has a beaerer token to authenticate - match parts.extract::>>().await { - Ok(bearer) => { - verify_bearer(bearer.token().to_string()).map_err(|_| AuthError::InvalidToken)?; - - let permissions = Permissions { - 0: HashSet::from(["root"]), - }; - - return Ok(permissions); - } - Err(err) => match err.reason() { - TypedHeaderRejectionReason::Missing => (), - TypedHeaderRejectionReason::Error(_err) => { - return Err(AuthError::InvalidToken.into()) - } - _ => return Err(AuthError::Unexpected.into()), - }, - }; - - match parts.extract::().await { - Ok(jar) => { - if let Some(session_token) = jar.get("session_token") { - // TODO: Implement function to retrieve user permissions - tracing::info!("{session_token:?}") - } - } - Err(_) => (), - } - - Err(AuthError::Unauthorized.into()) - } -} - -pub async fn get_user_from_header(pool: &PgPool, headers: &HeaderMap) -> Result { +pub async fn get_user_from_header( + pool: &PgPool, + headers: &HeaderMap, +) -> Result<(Roles, User), AuthError> { let bearer_value = headers.get(header::AUTHORIZATION); let bearer_value = bearer_value .ok_or_else(|| AuthError::InvalidToken)? @@ -84,28 +30,25 @@ pub async fn get_user_from_header(pool: &PgPool, headers: &HeaderMap) -> Result< let token = get_token_from_bearer(bearer_value)?; - let potential_user = match token.split_once("_") { - Some(("ses", _)) => { - let session = match Session::from_token(&pool, &token).await { - Ok(s) => s, - Err(_) => return Err(AuthError::InvalidToken), - }; - - if session.expires_at < Utc::now() { - return Err(AuthError::InvalidToken); - } - - let db_user = match crate::database::model::User::get(&pool, session.user_id).await { - Ok(u) => u, - Err(_) => return Err(AuthError::InvalidToken), - }; - - db_user.into() - } - _ => return Err(AuthError::InvalidToken), + let session = match Session::from_token(&pool, &token).await { + Ok(s) => s, + Err(_) => return Err(AuthError::InvalidToken), }; - Ok(potential_user) + if session.expires_at < Utc::now() { + return Err(AuthError::InvalidToken); + } + + let db_user = match crate::database::model::User::get(&pool, session.user_id).await { + Ok(u) => u, + Err(_) => return Err(AuthError::InvalidToken), + }; + + let roles = UserMember::get_roles(&pool, &db_user.user_id) + .await + .unwrap_or(Roles::MEMBER); + + Ok((roles, db_user.into())) } pub fn get_token_from_bearer(bearer: &str) -> Result { diff --git a/server/src/auth/bearer.rs b/server/src/auth/bearer.rs deleted file mode 100644 index 380aa3a..0000000 --- a/server/src/auth/bearer.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub fn verify_bearer(token: String) -> Result<(), ()> { - let env_api_token = dotenvy::var("API_TOKEN").map_err(|_| ())?; - - match env_api_token == token { - true => Ok(()), - false => Err(()), - } -} diff --git a/server/src/auth/scopes.rs b/server/src/auth/scopes.rs deleted file mode 100644 index 8b13789..0000000 --- a/server/src/auth/scopes.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/server/src/database/model/user.rs b/server/src/database/model/user.rs index 6c0f56e..38a82b3 100644 --- a/server/src/database/model/user.rs +++ b/server/src/database/model/user.rs @@ -1,5 +1,7 @@ use sqlx::{PgPool, Postgres}; +use crate::model::member::Roles; + #[derive(validator::Validate)] pub struct User { pub user_id: uuid::Uuid, @@ -81,4 +83,20 @@ impl UserMember { Ok(()) } + + pub async fn get_roles(pool: &PgPool, user_id: &uuid::Uuid) -> Result { + let roles = sqlx::query_scalar!( + " + SELECT roles FROM users_members INNER JOIN members ON users_members.member_id = members.member_id AND users_members.user_id = $1; + ", + user_id + ).fetch_all(pool).await?; + + let roles: Vec = roles.into_iter().map(|r| r.into()).collect(); + let roles = roles + .into_iter() + .fold(Roles::empty(), |acc, flag| acc | flag); + + Ok(roles) + } } diff --git a/server/src/model/member.rs b/server/src/model/member.rs index 641319f..9539b24 100644 --- a/server/src/model/member.rs +++ b/server/src/model/member.rs @@ -24,6 +24,7 @@ bitflags! { const KADER = 1 << 1; const ZWEMZAKEN = 1 << 2; const WEDSTRIJDEN = 1 << 3; + const ADMIN = 1 << 4; } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/server/src/model/session.rs b/server/src/model/session.rs index aaefcf4..188a2f6 100644 --- a/server/src/model/session.rs +++ b/server/src/model/session.rs @@ -14,7 +14,7 @@ pub struct Session { impl Session { pub fn new(user_id: uuid::Uuid) -> Self { let session_id = uuid::Uuid::new_v4(); - let token = format!("ses_{}", generate_session_token()); + let token = generate_session_token(); let created_at = Utc::now(); let expires_at = Utc::now() + Duration::days(7); diff --git a/server/src/routes.rs b/server/src/routes.rs index 6e3f71f..b911c3d 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -1,14 +1,5 @@ -use crate::{ - auth::{get_user_from_header, Permissions}, - model::User, - AppState, -}; -use axum::{ - extract::State, - http::{HeaderMap, StatusCode}, - routing::get, - Json, Router, -}; +use crate::{auth::get_user_from_header, model::User, AppState}; +use axum::{extract::State, http::HeaderMap, routing::get, Json, Router}; pub mod auth; pub mod member; @@ -24,10 +15,9 @@ pub fn routes() -> Router { async fn root( State(state): State, - // permissions: Permissions<'_>, headers: HeaderMap, ) -> Result, crate::Error> { - let user = get_user_from_header(&state.pool, &headers).await?; + let (_roles, user) = get_user_from_header(&state.pool, &headers).await?; Ok(Json(user)) } diff --git a/server/src/routes/member.rs b/server/src/routes/member.rs index f45036b..a5cdcc6 100644 --- a/server/src/routes/member.rs +++ b/server/src/routes/member.rs @@ -1,6 +1,6 @@ use axum::{extract::State, routing::post, Router}; -use crate::{auth::Permissions, AppState}; +use crate::AppState; pub mod migrate; @@ -12,7 +12,6 @@ pub fn routes() -> Router { pub async fn get_members<'a>( State(state): State, - permissions: Permissions<'a>, body: String, ) -> Result<(), crate::Error> { Ok(()) diff --git a/server/src/routes/member/migrate.rs b/server/src/routes/member/migrate.rs index 5d746c3..609ccd6 100644 --- a/server/src/routes/member/migrate.rs +++ b/server/src/routes/member/migrate.rs @@ -2,13 +2,14 @@ use std::collections::HashMap; use axum::{ extract::{FromRef, State}, + http::HeaderMap, Json, }; use itertools::Itertools; use sqlx::PgPool; use crate::{ - auth::{AuthError, Permissions}, + auth::{get_user_from_header, AuthError}, database::model::Member as DbMember, model::{ member::{Groups, Name, Roles}, @@ -20,10 +21,12 @@ use crate::{ pub async fn migrate_request<'a>( State(state): State, - permissions: Permissions<'a>, + headers: HeaderMap, body: String, ) -> Result, crate::Error> { - if !permissions.0.contains("root") { + let (roles, _user) = get_user_from_header(&state.pool, &headers).await?; + + if !roles.contains(Roles::ADMIN) { return Err(AuthError::NoPermssions.into()); } @@ -50,13 +53,8 @@ pub async fn migrate_request<'a>( pub async fn migrate_confirm<'a>( State(state): State, - permissions: Permissions<'a>, body: String, ) -> Result<(), crate::Error> { - if !permissions.0.contains("root") { - return Err(AuthError::NoPermssions.into()); - } - tracing::info!("Migration is confirmed"); let count = match body.trim().parse::() {