Added register function
This commit is contained in:
parent
6322736baf
commit
30bf08986d
41
server/Cargo.lock
generated
41
server/Cargo.lock
generated
@ -47,6 +47,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
@ -192,6 +204,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@ -1191,6 +1212,17 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@ -1642,6 +1674,7 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1722,6 +1755,7 @@ dependencies = [
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@ -1759,6 +1793,7 @@ dependencies = [
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@ -1783,6 +1818,7 @@ dependencies = [
|
||||
"sqlx-core",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2096,6 +2132,10 @@ name = "uuid"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
@ -2426,6 +2466,7 @@ dependencies = [
|
||||
name = "wrbapp_server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"chrono",
|
||||
|
@ -8,20 +8,21 @@ 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" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "uuid" ] }
|
||||
|
||||
# Secondary crates
|
||||
csv = { version = "1.3" }
|
||||
serde = "1.0"
|
||||
dotenvy = "0.15.7"
|
||||
validator = { version = "0.19.0", features = [ "derive" ] }
|
||||
argon2 = "0.5"
|
||||
|
||||
|
||||
# Tertiary crates
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
chrono = "0.4.39"
|
||||
uuid = "1.12.0"
|
||||
uuid = { version = "1.12.0", features = ["v4", "fast-rng"] }
|
||||
serde_json = "1.0.137"
|
||||
rand = "0.9"
|
||||
thiserror = { version = "2.0" }
|
||||
|
@ -1,5 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
|
||||
use axum_extra::{
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
@ -8,6 +12,7 @@ use axum_extra::{
|
||||
};
|
||||
use bearer::verify_bearer;
|
||||
pub use error::AuthError;
|
||||
use tokio::task;
|
||||
|
||||
mod bearer;
|
||||
mod error;
|
||||
@ -46,3 +51,24 @@ where
|
||||
Err(AuthError::Unexpected.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn generate_password_hash(
|
||||
password: String,
|
||||
) -> Result<String, argon2::password_hash::Error> {
|
||||
let password_hash: Result<String, argon2::password_hash::Error> =
|
||||
task::spawn_blocking(move || {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
let password_hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)?
|
||||
.to_string();
|
||||
|
||||
Ok(password_hash)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(password_hash?)
|
||||
}
|
||||
|
@ -3,3 +3,5 @@ pub mod session;
|
||||
pub mod user;
|
||||
|
||||
pub use member::Member;
|
||||
pub use user::User;
|
||||
pub use user::UserMember;
|
||||
|
@ -2,7 +2,7 @@ use rand::distr::{Alphanumeric, SampleString};
|
||||
use sqlx::{PgPool, Postgres, QueryBuilder};
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Debug, Validate)]
|
||||
#[derive(Debug, Validate, sqlx::FromRow)]
|
||||
pub struct Member {
|
||||
#[validate(length(equal = 7))]
|
||||
pub member_id: String,
|
||||
@ -14,7 +14,40 @@ pub struct Member {
|
||||
pub groups: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct SearchMember {
|
||||
pub registration_tokens: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Member {
|
||||
pub async fn search(
|
||||
transaction: &PgPool,
|
||||
search: SearchMember,
|
||||
) -> Result<Vec<Self>, sqlx::Error> {
|
||||
if let None = search.registration_tokens {
|
||||
return Err(sqlx::Error::RowNotFound);
|
||||
}
|
||||
|
||||
let mut query = QueryBuilder::new("SELECT * from members WHERE 1=1");
|
||||
|
||||
if let Some(registration_tokens) = search.registration_tokens {
|
||||
if registration_tokens.len() == 0 {
|
||||
return Err(sqlx::Error::RowNotFound);
|
||||
}
|
||||
|
||||
query.push(" AND registration_token = ANY(");
|
||||
query.push_bind(registration_tokens);
|
||||
query.push(")");
|
||||
}
|
||||
|
||||
let members: Vec<Self> = query.build_query_as().fetch_all(transaction).await?;
|
||||
|
||||
if members.len() == 0 {
|
||||
return Err(sqlx::Error::RowNotFound);
|
||||
}
|
||||
|
||||
Ok(members)
|
||||
}
|
||||
|
||||
pub async fn get_many(transaction: &PgPool, members: Vec<Self>) -> Result<(), sqlx::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,8 +1,68 @@
|
||||
use sqlx::Postgres;
|
||||
|
||||
#[derive(validator::Validate)]
|
||||
struct User {
|
||||
pub id: uuid::Uuid,
|
||||
pub struct User {
|
||||
pub user_id: uuid::Uuid,
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub admin: bool,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub async fn insert(
|
||||
transaction: &mut sqlx::Transaction<'_, Postgres>,
|
||||
email: &str,
|
||||
password: &str,
|
||||
) -> Result<uuid::Uuid, sqlx::Error> {
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO users (
|
||||
user_id, email, password, admin
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4
|
||||
);
|
||||
",
|
||||
&user_id,
|
||||
email,
|
||||
password,
|
||||
false
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserMember {
|
||||
user_id: uuid::Uuid,
|
||||
member_id: String,
|
||||
}
|
||||
|
||||
impl UserMember {
|
||||
pub async fn insert_many(
|
||||
transaction: &mut sqlx::Transaction<'_, Postgres>,
|
||||
user_ids: &Vec<uuid::Uuid>,
|
||||
member_ids: &Vec<String>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO users_members (
|
||||
user_id, member_id
|
||||
)
|
||||
SELECT * FROM UNNEST($1::uuid[], $2::varchar[])
|
||||
",
|
||||
&user_ids[..],
|
||||
&member_ids[..]
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ async fn main() {
|
||||
};
|
||||
|
||||
// Serve app
|
||||
let app = Router::new().merge(routes()).with_state(app_state);
|
||||
let app = Router::new().nest("/v1", routes()).with_state(app_state);
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:3000")
|
||||
.await
|
||||
|
@ -1,12 +1,5 @@
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use member::migrate::{migrate_confirm, migrate_request};
|
||||
|
||||
use crate::{auth::Permissions, AppState};
|
||||
use axum::{extract::State, http::StatusCode, routing::get, Router};
|
||||
|
||||
pub mod auth;
|
||||
pub mod member;
|
||||
@ -16,8 +9,8 @@ pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(root))
|
||||
// .route("/member/:id", get())
|
||||
.route("/members/migrate_request", post(migrate_request))
|
||||
.route("/members/migrate_confirm", post(migrate_confirm))
|
||||
.merge(member::routes())
|
||||
.merge(auth::routes())
|
||||
}
|
||||
|
||||
async fn root(
|
||||
|
@ -1 +1,67 @@
|
||||
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::User as DbUser;
|
||||
use crate::database::model::UserMember as DbUserMember;
|
||||
use crate::{
|
||||
auth::{generate_password_hash, Permissions},
|
||||
AppState,
|
||||
};
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/auth/login", post(login))
|
||||
.route("/auth/register", post(register))
|
||||
}
|
||||
|
||||
pub async fn login<'a>(
|
||||
State(state): State<AppState>,
|
||||
permissions: Permissions<'a>,
|
||||
body: String,
|
||||
) -> Result<(), crate::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct AuthRequest {
|
||||
email: String,
|
||||
password: String,
|
||||
registration_tokens: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn register<'a>(
|
||||
State(state): State<AppState>,
|
||||
permissions: Permissions<'a>,
|
||||
Json(auth_request): Json<AuthRequest>,
|
||||
) -> Result<(), crate::Error> {
|
||||
// Get all the members to link with the user
|
||||
let members = DbMember::search(
|
||||
&state.pool,
|
||||
SearchMember {
|
||||
registration_tokens: Some(auth_request.registration_tokens),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let member_ids: Vec<String> = members.into_iter().map(|m| m.member_id).collect();
|
||||
|
||||
// Hash password
|
||||
let password_hash = match generate_password_hash(auth_request.password).await {
|
||||
Ok(hash) => hash,
|
||||
Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidToken)),
|
||||
};
|
||||
|
||||
let mut transaction = state.pool.begin().await?;
|
||||
|
||||
// Insert the user to the database
|
||||
let user_id = DbUser::insert(&mut transaction, &auth_request.email, &password_hash).await?;
|
||||
|
||||
// Link the user to the members
|
||||
let user_ids: Vec<uuid::Uuid> = vec![user_id; member_ids.len()];
|
||||
DbUserMember::insert_many(&mut transaction, &user_ids, &member_ids).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1 +1,11 @@
|
||||
use axum::{routing::post, Router};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
pub mod migrate;
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/members/migrate_request", post(migrate::migrate_request))
|
||||
.route("/members/migrate_confirm", post(migrate::migrate_confirm))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user