diff --git a/server/Cargo.lock b/server/Cargo.lock index 5703e1e..bbc48e8 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -2203,6 +2203,7 @@ checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" dependencies = [ "getrandom 0.2.15", "rand 0.8.5", + "serde", ] [[package]] diff --git a/server/Cargo.toml b/server/Cargo.toml index 875ffe4..ac97d19 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -23,7 +23,7 @@ bitflags = { version = "2.8", features = [ "serde" ] } tracing = "0.1" tracing-subscriber = "0.3" chrono = "0.4" -uuid = { version = "1.12", features = ["v4", "fast-rng"] } +uuid = { version = "1.12", features = ["v4", "fast-rng", "serde"] } serde_json = "1.0.137" rand = "0.9" rand_chacha = "0.9" diff --git a/server/migrations/001_create_members.sql b/server/migrations/001_create_members.sql index 7899046..53104c5 100644 --- a/server/migrations/001_create_members.sql +++ b/server/migrations/001_create_members.sql @@ -4,7 +4,7 @@ CREATE TABLE "members" ( full_name text NOT NULL, registration_token text NOT NULL UNIQUE, diploma text, - swim_groups bigint NOT NULL, - groups bigint NOT NULL + groups bigint NOT NULL, + roles bigint NOT NULL ); diff --git a/server/src/auth.rs b/server/src/auth.rs index b26803d..ddec7a2 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -6,7 +6,7 @@ use argon2::{ }; use axum::{ extract::FromRequestParts, - http::{request::Parts, StatusCode}, + http::{header, request::Parts, HeaderMap, StatusCode}, RequestPartsExt, }; use axum_extra::{ @@ -16,13 +16,15 @@ use axum_extra::{ TypedHeader, }; use bearer::verify_bearer; +use chrono::Utc; pub use error::AuthError; use rand::distr::Alphanumeric; use rand::prelude::*; use rand_chacha::ChaCha20Rng; +use sqlx::PgPool; use tokio::task; -use crate::database::model::User; +use crate::{database::model::Session, model::User}; mod bearer; mod error; @@ -73,6 +75,46 @@ where } } +pub async fn get_user_from_header(pool: &PgPool, headers: &HeaderMap) -> Result { + let bearer_value = headers.get(header::AUTHORIZATION); + let bearer_value = bearer_value + .ok_or_else(|| AuthError::InvalidToken)? + .to_str() + .map_err(|_| AuthError::InvalidToken)?; + + 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), + }; + + Ok(potential_user) +} + +pub fn get_token_from_bearer(bearer: &str) -> Result { + match bearer.strip_prefix("Bearer ") { + Some(token) => Ok(token.to_string()), + None => return Err(AuthError::InvalidToken), + } +} + pub async fn generate_password_hash( password: String, ) -> Result { diff --git a/server/src/auth/scopes.rs b/server/src/auth/scopes.rs index df25ba0..8b13789 100644 --- a/server/src/auth/scopes.rs +++ b/server/src/auth/scopes.rs @@ -1,19 +1 @@ -use crate::bitflags_serde_impl; -use bitflags::bitflags; -use serde::Deserialize; -bitflags! { - #[derive(Clone, Copy, Debug)] - pub struct Scopes: u64 { - const USER_READ = 1 << 0; - const USER_WRITE = 1 << 1; - const USER_DELETE = 1 << 2; - - const MEMBER_CREATE = 1 << 3; - const MEMBER_READ = 1 << 4; - const MEMBER_WRITE = 1 << 5; - const MEMBER_DELETE = 1 << 6; - } -} - -bitflags_serde_impl!(Scopes, u64); diff --git a/server/src/database/model/member.rs b/server/src/database/model/member.rs index bfc1355..769749d 100644 --- a/server/src/database/model/member.rs +++ b/server/src/database/model/member.rs @@ -2,7 +2,7 @@ use rand::distr::{Alphanumeric, SampleString}; use sqlx::{PgPool, Postgres, QueryBuilder}; use validator::Validate; -use crate::model::member::{Groups, SwimGroups}; +use crate::model::member::{Groups, Roles}; #[derive(Debug, Validate, sqlx::FromRow)] pub struct Member { @@ -12,8 +12,8 @@ pub struct Member { pub full_name: String, pub registration_token: Option, pub diploma: Option, - pub swim_groups: SwimGroups, pub groups: Groups, + pub roles: Roles, } impl Member { @@ -53,7 +53,7 @@ impl Member { } let mut query_builder = QueryBuilder::new( - "INSERT INTO members(member_id, first_name, full_name, registration_token, diploma, swim_groups, groups) " + "INSERT INTO members(member_id, first_name, full_name, registration_token, diploma, groups, roles) " ); query_builder.push_values(members.into_iter(), |mut b, member| { @@ -64,8 +64,8 @@ impl Member { b.push_bind(member.full_name); b.push_bind(registration_token); b.push_bind(member.diploma); - b.push_bind(member.swim_groups.bits() as i64); b.push_bind(member.groups.bits() as i64); + b.push_bind(member.roles.bits() as i64); }); let query = query_builder.build(); @@ -86,14 +86,14 @@ impl Member { sqlx::query!( " UPDATE ONLY members - SET first_name = $1, full_name = $2, diploma = $3, swim_groups = $4, groups = $5 + SET first_name = $1, full_name = $2, diploma = $3, groups = $4, roles = $5 WHERE member_id = $6 ", member.first_name, member.full_name, member.diploma, - member.swim_groups.bits() as i64, member.groups.bits() as i64, + member.roles.bits() as i64, member.member_id ) .execute(&mut **transaction) diff --git a/server/src/database/model/session.rs b/server/src/database/model/session.rs index ce551df..2043f5c 100644 --- a/server/src/database/model/session.rs +++ b/server/src/database/model/session.rs @@ -1,6 +1,7 @@ use chrono::{DateTime, Utc}; use sqlx::{PgPool, Postgres}; +#[derive(Debug)] pub struct Session { pub session_id: uuid::Uuid, pub user_id: uuid::Uuid, diff --git a/server/src/database/model/user.rs b/server/src/database/model/user.rs index 0a5c880..6c0f56e 100644 --- a/server/src/database/model/user.rs +++ b/server/src/database/model/user.rs @@ -44,6 +44,14 @@ impl User { Ok(user) } + + pub async fn get(transaction: &PgPool, user_id: uuid::Uuid) -> Result { + let user = sqlx::query_as!(Self, "SELECT * FROM users WHERE user_id = $1", user_id) + .fetch_one(transaction) + .await?; + + Ok(user) + } } #[derive(Debug)] diff --git a/server/src/model/member.rs b/server/src/model/member.rs index f039d52..641319f 100644 --- a/server/src/model/member.rs +++ b/server/src/model/member.rs @@ -13,21 +13,21 @@ pub struct Member { pub name: Name, pub registration_token: Option, pub diploma: Option, - pub swim_groups: SwimGroups, pub groups: Groups, + pub roles: Roles, } bitflags! { #[derive(Clone, Copy, Debug, Serialize, Deserialize)] - pub struct Groups: u64 { - const NONE = 1 << 0; + pub struct Roles: u64 { + const MEMBER = 1 << 0; const KADER = 1 << 1; const ZWEMZAKEN = 1 << 2; const WEDSTRIJDEN = 1 << 3; } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] - pub struct SwimGroups: u64 { + pub struct Groups: u64 { const NONE = 1 << 0; const A1 = 1 << 1; @@ -76,15 +76,15 @@ bitflags! { } } -impl From for SwimGroups { +impl From for Groups { fn from(value: i64) -> Self { - Self::from_bits(value as u64).unwrap_or(SwimGroups::NONE) + Self::from_bits(value as u64).unwrap_or(Groups::empty()) } } -impl From for Groups { +impl From for Roles { fn from(value: i64) -> Self { - Self::from_bits(value as u64).unwrap_or(Groups::NONE) + Self::from_bits(value as u64).unwrap_or(Roles::MEMBER) } } @@ -99,8 +99,8 @@ impl From for Member { }, registration_token: value.registration_token, diploma: value.diploma, - swim_groups: value.swim_groups, groups: value.groups, + roles: value.roles, } } } @@ -113,8 +113,8 @@ impl From for DbMember { full_name: value.name.full, registration_token: None, diploma: value.diploma, - swim_groups: value.swim_groups, groups: value.groups, + roles: value.roles, } } } diff --git a/server/src/model/session.rs b/server/src/model/session.rs index afb4ac9..aaefcf4 100644 --- a/server/src/model/session.rs +++ b/server/src/model/session.rs @@ -2,6 +2,7 @@ use chrono::{DateTime, Duration, Utc}; use crate::auth::generate_session_token; +#[derive(Debug)] pub struct Session { pub session_id: uuid::Uuid, pub user_id: uuid::Uuid, @@ -13,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 = generate_session_token(); + let token = format!("ses_{}", generate_session_token()); let created_at = Utc::now(); let expires_at = Utc::now() + Duration::days(7); diff --git a/server/src/model/user.rs b/server/src/model/user.rs index 3564290..9a7b3c7 100644 --- a/server/src/model/user.rs +++ b/server/src/model/user.rs @@ -1,5 +1,19 @@ +use serde::Serialize; + +#[derive(Serialize)] pub struct User { pub id: uuid::Uuid, pub email: String, pub admin: bool, } + +use crate::database::model::User as DbUser; +impl From for User { + fn from(db_user: DbUser) -> Self { + Self { + id: db_user.user_id, + email: db_user.email, + admin: db_user.admin, + } + } +} diff --git a/server/src/routes.rs b/server/src/routes.rs index 9b99c8b..6e3f71f 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -1,5 +1,14 @@ -use crate::{auth::Permissions, AppState}; -use axum::{extract::State, http::StatusCode, routing::get, Router}; +use crate::{ + auth::{get_user_from_header, Permissions}, + model::User, + AppState, +}; +use axum::{ + extract::State, + http::{HeaderMap, StatusCode}, + routing::get, + Json, Router, +}; pub mod auth; pub mod member; @@ -14,10 +23,11 @@ pub fn routes() -> Router { } async fn root( - State(_state): State, - permissions: Permissions<'_>, -) -> Result { - tracing::info!("{:?}", permissions); + State(state): State, + // permissions: Permissions<'_>, + headers: HeaderMap, +) -> Result, crate::Error> { + let user = get_user_from_header(&state.pool, &headers).await?; - Ok("Hello world".to_string()) + Ok(Json(user)) } diff --git a/server/src/routes/member/migrate.rs b/server/src/routes/member/migrate.rs index f6ad732..5d746c3 100644 --- a/server/src/routes/member/migrate.rs +++ b/server/src/routes/member/migrate.rs @@ -11,7 +11,7 @@ use crate::{ auth::{AuthError, Permissions}, database::model::Member as DbMember, model::{ - member::{Groups, Name, SwimGroups}, + member::{Groups, Name, Roles}, Member, }, util::convert_vec, @@ -119,7 +119,7 @@ struct Row { #[serde(rename = "E-mail")] email: String, #[serde(rename = "Verenigingssporten")] - swim_groups: String, + groups: String, #[serde(rename = "Diploma dropdown 1")] diploma: Option, } @@ -164,22 +164,22 @@ impl Row { members } - fn swim_groups_parsed(&self) -> SwimGroups { - let mut swim_groups: Vec = Vec::new(); + fn groups_parsed(&self) -> Groups { + let mut groups: Vec = Vec::new(); - let group_parts: Vec<&str> = self.swim_groups.split(", ").collect(); + let group_parts: Vec<&str> = self.groups.split(", ").collect(); for group in group_parts { let hour_parts: Vec<&str> = group.split(" - ").collect(); if let Some(group) = hour_parts.get(1) { - swim_groups.push(group.to_uppercase()) + groups.push(group.to_uppercase()) } } - let swim_groups_string = swim_groups.join("|"); + let groups_string = groups.join("|"); - bitflags::parser::from_str(&swim_groups_string).unwrap_or(SwimGroups::empty()) + bitflags::parser::from_str(&groups_string).unwrap_or(Groups::empty()) } } @@ -201,8 +201,8 @@ impl Into for Row { name, registration_token: None, diploma: self.diploma.clone(), - swim_groups: self.swim_groups_parsed(), - groups: Groups::empty(), + groups: self.groups_parsed(), + roles: Roles::MEMBER, } } } @@ -264,8 +264,8 @@ fn generate_diff(members_new: Vec, members_old: Vec) -> MembersD name: new_member.name.clone(), registration_token: old_member.registration_token, diploma: new_member.diploma.clone(), - swim_groups: new_member.swim_groups.clone(), - groups: old_member.groups, + groups: new_member.groups, + roles: old_member.roles, }) } else { members_remove.push(old_member);