diff --git a/server/Cargo.lock b/server/Cargo.lock index ee4f6c7..4343b75 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1650,6 +1650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" dependencies = [ "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -1727,6 +1728,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -1769,6 +1771,7 @@ dependencies = [ "base64 0.22.1", "bitflags", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -1804,6 +1807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", @@ -2474,6 +2478,7 @@ dependencies = [ "dotenvy", "itertools", "rand 0.9.0", + "rand_chacha 0.9.0", "serde", "serde_json", "sqlx", diff --git a/server/Cargo.toml b/server/Cargo.toml index 655928b..63482ce 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" axum = { version = "0.8", features = [ "macros", "json" ] } axum-extra = { version = "0.10.0", features = [ "typed-header" ] } tokio = { version = "1.43", features = [ "rt-multi-thread", "macros" ] } -sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "uuid" ] } +sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "uuid", "chrono" ] } # Secondary crates csv = { version = "1.3" } @@ -21,10 +21,11 @@ argon2 = "0.5" # Tertiary crates tracing = "0.1" tracing-subscriber = "0.3" -chrono = "0.4.39" -uuid = { version = "1.12.0", features = ["v4", "fast-rng"] } +chrono = "0.4" +uuid = { version = "1.12", features = ["v4", "fast-rng"] } serde_json = "1.0.137" rand = "0.9" +rand_chacha = "0.9" thiserror = { version = "2.0" } itertools = "0.14" diff --git a/server/migrations/003_create_sessions.sql b/server/migrations/003_create_sessions.sql new file mode 100644 index 0000000..e1d3565 --- /dev/null +++ b/server/migrations/003_create_sessions.sql @@ -0,0 +1,7 @@ +CREATE TABLE "sessions" ( + session_id uuid NOT NULL PRIMARY KEY, + user_id uuid NOT NULL REFERENCES users (user_id) ON UPDATE cascade ON DELETE cascade, + token text NOT NULL UNIQUE, + expires_at timestamptz NOT NULL, + created_at timestamptz NOT NULL +); diff --git a/server/src/auth.rs b/server/src/auth.rs index 3911404..93dd59b 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -12,6 +12,9 @@ use axum_extra::{ }; use bearer::verify_bearer; pub use error::AuthError; +use rand::distr::Alphanumeric; +use rand::prelude::*; +use rand_chacha::ChaCha20Rng; use tokio::task; mod bearer; @@ -72,3 +75,13 @@ pub async fn generate_password_hash( Ok(password_hash?) } + +pub fn generate_session_token() -> String { + let session = ChaCha20Rng::from_os_rng() + .sample_iter(&Alphanumeric) + .take(60) + .map(char::from) + .collect::(); + + session +} diff --git a/server/src/database/model.rs b/server/src/database/model.rs index 17c301f..4ed8299 100644 --- a/server/src/database/model.rs +++ b/server/src/database/model.rs @@ -3,5 +3,6 @@ pub mod session; pub mod user; pub use member::Member; +pub use session::Session; pub use user::User; pub use user::UserMember; diff --git a/server/src/database/model/session.rs b/server/src/database/model/session.rs index e7d894f..2a1af46 100644 --- a/server/src/database/model/session.rs +++ b/server/src/database/model/session.rs @@ -1,6 +1,37 @@ -struct Session { - id: u32, - user_id: u32, - token: String, - expires: chrono::NaiveDateTime, +use chrono::{DateTime, Utc}; +use sqlx::Postgres; + +pub struct Session { + pub session_id: uuid::Uuid, + pub user_id: uuid::Uuid, + pub token: String, + pub expires_at: DateTime, + pub created_at: DateTime, +} + +impl Session { + pub async fn insert( + &self, + transaction: &mut sqlx::Transaction<'_, Postgres>, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + " + INSERT INTO sessions ( + session_id, user_id, token, expires_at, created_at + ) + VALUES ( + $1, $2, $3, $4, $5 + ) + ", + &self.session_id, + &self.user_id, + &self.token, + &self.expires_at, + &self.created_at + ) + .execute(&mut **transaction) + .await?; + + Ok(()) + } } diff --git a/server/src/model/session.rs b/server/src/model/session.rs index e69de29..afb4ac9 100644 --- a/server/src/model/session.rs +++ b/server/src/model/session.rs @@ -0,0 +1,54 @@ +use chrono::{DateTime, Duration, Utc}; + +use crate::auth::generate_session_token; + +pub struct Session { + pub session_id: uuid::Uuid, + pub user_id: uuid::Uuid, + pub token: String, + pub expires_at: DateTime, + pub created_at: DateTime, +} + +impl Session { + pub fn new(user_id: uuid::Uuid) -> Self { + let session_id = uuid::Uuid::new_v4(); + let token = generate_session_token(); + + let created_at = Utc::now(); + let expires_at = Utc::now() + Duration::days(7); + + return Self { + session_id, + user_id, + token, + expires_at, + created_at, + }; + } +} + +use crate::database::model::Session as DbSession; +impl From for Session { + fn from(db_session: DbSession) -> Self { + Self { + session_id: db_session.session_id, + user_id: db_session.user_id, + token: db_session.token, + expires_at: db_session.expires_at, + created_at: db_session.created_at, + } + } +} + +impl From for DbSession { + fn from(session: Session) -> Self { + Self { + session_id: session.session_id, + user_id: session.user_id, + token: session.token, + expires_at: session.expires_at, + created_at: session.created_at, + } + } +} diff --git a/server/src/routes/auth.rs b/server/src/routes/auth.rs index d5321ed..252e8b7 100644 --- a/server/src/routes/auth.rs +++ b/server/src/routes/auth.rs @@ -2,8 +2,10 @@ use axum::{extract::State, routing::post, Json, Router}; use crate::database::model::member::SearchMember; use crate::database::model::Member as DbMember; +use crate::database::model::Session as DbSession; use crate::database::model::User as DbUser; use crate::database::model::UserMember as DbUserMember; +use crate::model::session::Session; use crate::{ auth::{generate_password_hash, Permissions}, AppState, @@ -34,7 +36,7 @@ pub async fn register<'a>( State(state): State, permissions: Permissions<'a>, Json(auth_request): Json, -) -> Result<(), crate::Error> { +) -> Result { // Get all the members to link with the user let members = DbMember::search( &state.pool, @@ -61,7 +63,11 @@ pub async fn register<'a>( let user_ids: Vec = vec![user_id; member_ids.len()]; DbUserMember::insert_many(&mut transaction, &user_ids, &member_ids).await?; + // Create a new session + let db_session: DbSession = Session::new(user_id).into(); + db_session.insert(&mut transaction).await?; + transaction.commit().await?; - Ok(()) + Ok(db_session.token) } diff --git a/server/src/routes/member.rs b/server/src/routes/member.rs index e621c87..dcfe087 100644 --- a/server/src/routes/member.rs +++ b/server/src/routes/member.rs @@ -1,11 +1,19 @@ -use axum::{routing::post, Router}; +use axum::{extract::State, routing::post, Router}; -use crate::AppState; +use crate::{auth::Permissions, AppState}; pub mod migrate; pub fn routes() -> Router { Router::new() - .route("/members/migrate_request", post(migrate::migrate_request)) - .route("/members/migrate_confirm", post(migrate::migrate_confirm)) + // .route("/members/migrate_request", post(migrate::migrate_request)) + // .route("/members/migrate_confirm", post(migrate::migrate_confirm)) +} + +pub async fn get_members<'a>( + State(state): State, + permissions: Permissions<'a>, + body: String, +) -> Result<(), crate::Error> { + Ok(()) }