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",
|
"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]]
|
[[package]]
|
||||||
name = "atoi"
|
name = "atoi"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -192,6 +204,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@ -1191,6 +1212,17 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -1642,6 +1674,7 @@ dependencies = [
|
|||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1722,6 +1755,7 @@ dependencies = [
|
|||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1759,6 +1793,7 @@ dependencies = [
|
|||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1783,6 +1818,7 @@ dependencies = [
|
|||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2096,6 +2132,10 @@ name = "uuid"
|
|||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
|
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.15",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "validator"
|
name = "validator"
|
||||||
@ -2426,6 +2466,7 @@ dependencies = [
|
|||||||
name = "wrbapp_server"
|
name = "wrbapp_server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -8,20 +8,21 @@ edition = "2021"
|
|||||||
axum = { version = "0.8", features = [ "macros", "json" ] }
|
axum = { version = "0.8", features = [ "macros", "json" ] }
|
||||||
axum-extra = { version = "0.10.0", features = [ "typed-header" ] }
|
axum-extra = { version = "0.10.0", features = [ "typed-header" ] }
|
||||||
tokio = { version = "1.43", features = [ "rt-multi-thread", "macros" ] }
|
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
|
# Secondary crates
|
||||||
csv = { version = "1.3" }
|
csv = { version = "1.3" }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
validator = { version = "0.19.0", features = [ "derive" ] }
|
validator = { version = "0.19.0", features = [ "derive" ] }
|
||||||
|
argon2 = "0.5"
|
||||||
|
|
||||||
|
|
||||||
# Tertiary crates
|
# Tertiary crates
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.39"
|
||||||
uuid = "1.12.0"
|
uuid = { version = "1.12.0", features = ["v4", "fast-rng"] }
|
||||||
serde_json = "1.0.137"
|
serde_json = "1.0.137"
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
thiserror = { version = "2.0" }
|
thiserror = { version = "2.0" }
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
|
use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
|
||||||
use axum_extra::{
|
use axum_extra::{
|
||||||
headers::{authorization::Bearer, Authorization},
|
headers::{authorization::Bearer, Authorization},
|
||||||
@ -8,6 +12,7 @@ use axum_extra::{
|
|||||||
};
|
};
|
||||||
use bearer::verify_bearer;
|
use bearer::verify_bearer;
|
||||||
pub use error::AuthError;
|
pub use error::AuthError;
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
mod bearer;
|
mod bearer;
|
||||||
mod error;
|
mod error;
|
||||||
@ -46,3 +51,24 @@ where
|
|||||||
Err(AuthError::Unexpected.into())
|
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 mod user;
|
||||||
|
|
||||||
pub use member::Member;
|
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 sqlx::{PgPool, Postgres, QueryBuilder};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
#[derive(Debug, Validate)]
|
#[derive(Debug, Validate, sqlx::FromRow)]
|
||||||
pub struct Member {
|
pub struct Member {
|
||||||
#[validate(length(equal = 7))]
|
#[validate(length(equal = 7))]
|
||||||
pub member_id: String,
|
pub member_id: String,
|
||||||
@ -14,7 +14,40 @@ pub struct Member {
|
|||||||
pub groups: Vec<String>,
|
pub groups: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SearchMember {
|
||||||
|
pub registration_tokens: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Member {
|
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> {
|
pub async fn get_many(transaction: &PgPool, members: Vec<Self>) -> Result<(), sqlx::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,68 @@
|
|||||||
|
use sqlx::Postgres;
|
||||||
|
|
||||||
#[derive(validator::Validate)]
|
#[derive(validator::Validate)]
|
||||||
struct User {
|
pub struct User {
|
||||||
pub id: uuid::Uuid,
|
pub user_id: uuid::Uuid,
|
||||||
#[validate(email)]
|
#[validate(email)]
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub admin: bool,
|
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
|
// 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")
|
let listener = TcpListener::bind("127.0.0.1:3000")
|
||||||
.await
|
.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 crate::{auth::Permissions, AppState};
|
||||||
|
use axum::{extract::State, http::StatusCode, routing::get, Router};
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod member;
|
pub mod member;
|
||||||
@ -16,8 +9,8 @@ pub fn routes() -> Router<AppState> {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
// .route("/member/:id", get())
|
// .route("/member/:id", get())
|
||||||
.route("/members/migrate_request", post(migrate_request))
|
.merge(member::routes())
|
||||||
.route("/members/migrate_confirm", post(migrate_confirm))
|
.merge(auth::routes())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn root(
|
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 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