feat(api): implemented /account/register

This commit is contained in:
antifallobst 2024-03-19 23:57:46 +01:00
parent 0bb3e456be
commit c13c70e6e4
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
8 changed files with 384 additions and 25 deletions

194
Cargo.lock generated
View File

@ -181,6 +181,21 @@ dependencies = [
"syn 2.0.53", "syn 2.0.53",
] ]
[[package]]
name = "actix-web-httpauth"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
dependencies = [
"actix-utils",
"actix-web",
"base64",
"futures-core",
"futures-util",
"log",
"pin-project-lite",
]
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.21.0" version = "0.21.0"
@ -239,6 +254,21 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.13" version = "0.6.13"
@ -293,6 +323,18 @@ version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
[[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"
@ -350,6 +392,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"
@ -380,6 +431,12 @@ dependencies = [
"alloc-stdlib", "alloc-stdlib",
] ]
[[package]]
name = "bumpalo"
version = "3.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@ -417,6 +474,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-targets 0.52.4",
]
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.0"
@ -927,17 +996,45 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icrc-server" name = "icrc-server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"actix-web-httpauth",
"anyhow", "anyhow",
"argon2",
"compile-time-run", "compile-time-run",
"dotenvy", "dotenvy",
"env_logger", "env_logger",
"log", "log",
"serde",
"sqlx", "sqlx",
"thiserror",
"uuid",
] ]
[[package]] [[package]]
@ -984,6 +1081,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "js-sys"
version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "language-tags" name = "language-tags"
version = "0.3.2" version = "0.3.2"
@ -1273,6 +1379,17 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.14" version = "1.0.14"
@ -1697,6 +1814,7 @@ dependencies = [
"atoi", "atoi",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"either", "either",
@ -1758,6 +1876,7 @@ dependencies = [
"sha2", "sha2",
"sqlx-core", "sqlx-core",
"sqlx-mysql", "sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite", "sqlx-sqlite",
"syn 1.0.109", "syn 1.0.109",
"tempfile", "tempfile",
@ -1776,6 +1895,7 @@ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"digest", "digest",
"dotenvy", "dotenvy",
@ -1817,6 +1937,7 @@ dependencies = [
"base64", "base64",
"bitflags 2.4.2", "bitflags 2.4.2",
"byteorder", "byteorder",
"chrono",
"crc", "crc",
"dotenvy", "dotenvy",
"etcetera", "etcetera",
@ -1852,6 +1973,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
dependencies = [ dependencies = [
"atoi", "atoi",
"chrono",
"flume", "flume",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -2121,6 +2243,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -2145,6 +2276,60 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.53",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.53",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.5.1" version = "1.5.1"
@ -2155,6 +2340,15 @@ dependencies = [
"wasite", "wasite",
] ]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.4",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"

View File

@ -13,6 +13,11 @@ log = "0.4.21"
env_logger = "0.11.3" env_logger = "0.11.3"
dotenvy = "0.15.7" dotenvy = "0.15.7"
compile-time-run = "0.2.12" compile-time-run = "0.2.12"
uuid = { version = "1.7.0", features = ["v4"] }
thiserror = "1.0.58"
argon2 = "0.5.3"
serde = { version = "1.0.197", features = ["default"] }
actix-web = "4.5.1" actix-web = "4.5.1"
sqlx = { version = "0.7.4", features = ["runtime-tokio", "tls-native-tls", "mysql"] } actix-web-httpauth = "0.8.1"
sqlx = { version = "0.7.4", features = ["runtime-tokio", "tls-native-tls", "mysql", "chrono"] }

View File

@ -1,6 +1,41 @@
use actix_web::{post, HttpResponse, Responder}; use crate::backend::{error::AccountRegisterError, Backend};
use actix_web::{post, web, HttpResponse, Responder};
use log::error;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct AccountRegisterRequest {
token: String,
password: String,
}
#[derive(Debug, Serialize)]
struct AccountRegisterResponse {
uuid: String,
}
#[post("/account/register")] #[post("/account/register")]
pub async fn account_register() -> impl Responder { pub async fn account_register(
HttpResponse::Ok() backend: web::Data<Backend>,
body: web::Json<AccountRegisterRequest>,
) -> impl Responder {
let body = body.into_inner();
match backend.account_register(body.token, body.password).await {
Err(e) => {
error!("{e}");
HttpResponse::InternalServerError().finish()
}
Ok(res) => match res {
Err(e) => match e {
AccountRegisterError::InvalidToken => HttpResponse::Unauthorized().finish(),
AccountRegisterError::SqlError(e) => {
error!("{e}");
HttpResponse::InternalServerError().finish()
}
},
Ok(uuid) => HttpResponse::Ok().json(AccountRegisterResponse {
uuid: uuid.to_string(),
}),
},
}
} }

View File

@ -14,12 +14,12 @@ pub async fn start(config: &Config, backend: Backend) -> Result<()> {
}) })
.bind((config.addr.as_str(), config.port))?; .bind((config.addr.as_str(), config.port))?;
info!("API starting");
if let Some(threads) = config.threads { if let Some(threads) = config.threads {
server.workers(threads as usize).run().await?; server.workers(threads as usize).run().await?;
} else { } else {
server.run().await?; server.run().await?;
} }
info!("API online");
Ok(()) Ok(())
} }

View File

@ -0,0 +1,11 @@
use sqlx::types::chrono::NaiveDateTime;
pub struct ActivationTokensRow {
pub token: String,
pub expire: NaiveDateTime,
}
pub struct UsersRow {
pub userid: String,
pub password: String,
}

10
src/backend/error.rs Normal file
View File

@ -0,0 +1,10 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AccountRegisterError {
#[error("The given token is invalid")]
InvalidToken,
#[error("SQL Error: {0}")]
SqlError(sqlx::Error),
}

View File

@ -1,20 +1,31 @@
mod db_structures;
pub mod error;
use crate::backend::error::AccountRegisterError;
use crate::config::Config; use crate::config::Config;
use anyhow::Result; use anyhow::Result;
use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
Argon2,
};
use db_structures::{ActivationTokensRow, UsersRow};
use log::info; use log::info;
use sqlx::MySqlPool; use sqlx::{types::chrono::Utc, MySqlPool};
use uuid::Uuid;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Backend { pub struct Backend {
pool: MySqlPool, pool: MySqlPool,
} }
pub async fn start(config: &Config) -> Result<Backend> { impl Backend {
pub async fn start(config: &Config) -> Result<Self> {
let pool = MySqlPool::connect(config.database.as_str()).await?.into(); let pool = MySqlPool::connect(config.database.as_str()).await?.into();
sqlx::query!( sqlx::query!(
r#"CREATE TABLE IF NOT EXISTS Users ( r#"CREATE TABLE IF NOT EXISTS Users (
username VARCHAR(32) NOT NULL PRIMARY KEY, userid UUID NOT NULL PRIMARY KEY,
password VARCHAR(98) NOT NULL password VARCHAR(128) NOT NULL
);"# );"#
) )
.execute(&pool) .execute(&pool)
@ -22,8 +33,27 @@ pub async fn start(config: &Config) -> Result<Backend> {
sqlx::query!( sqlx::query!(
r#"CREATE TABLE IF NOT EXISTS Relays ( r#"CREATE TABLE IF NOT EXISTS Relays (
id BINARY(16) NOT NULL PRIMARY KEY, id UUID NOT NULL PRIMARY KEY,
secret VARCHAR(98) NOT NULL secret VARCHAR(128) NOT NULL
);"#
)
.execute(&pool)
.await?;
sqlx::query!(
r#"CREATE TABLE IF NOT EXISTS ActivationTokens (
token CHAR(48) NOT NULL PRIMARY KEY,
expire DATETIME NOT NULL
);"#
)
.execute(&pool)
.await?;
sqlx::query!(
r#"CREATE TABLE IF NOT EXISTS AuthTokens (
token CHAR(48) NOT NULL PRIMARY KEY,
userid UUID NOT NULL,
exipre DATETIME NOT NULL
);"# );"#
) )
.execute(&pool) .execute(&pool)
@ -31,5 +61,78 @@ pub async fn start(config: &Config) -> Result<Backend> {
info!("Backend initialized"); info!("Backend initialized");
Ok(Backend { pool }) Ok(Self { pool })
}
async fn check_activation_token(&self, token: &str) -> Result<bool, sqlx::Error> {
match sqlx::query_as!(
ActivationTokensRow,
r#"SELECT * FROM ActivationTokens WHERE token = ?;"#,
token
)
.fetch_one(&self.pool)
.await
{
Err(e) => match e {
sqlx::Error::RowNotFound => Ok(false),
_ => Err(e),
},
Ok(row) => {
sqlx::query!(r#"DELETE FROM ActivationTokens WHERE token = ?;"#, token)
.execute(&self.pool)
.await?;
if row.expire > Utc::now().naive_utc() {
Ok(true)
} else {
Ok(false)
}
}
}
}
async fn get_user(&self, userid: Uuid) -> Result<Option<()>, sqlx::Error> {
match sqlx::query_as!(
UsersRow,
r#"SELECT * FROM Users WHERE userid = ?;"#,
userid.as_bytes().as_slice()
)
.fetch_one(&self.pool)
.await
{
Err(e) => match e {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(e),
},
Ok(_row) => Ok(Some(())),
}
}
pub async fn account_register(
&self,
token: String,
password: String,
) -> Result<Result<Uuid, error::AccountRegisterError>> {
if !self.check_activation_token(&token).await? {
return Ok(Err(AccountRegisterError::InvalidToken));
}
let salt = SaltString::generate(&mut OsRng);
let hash = Argon2::default()
.hash_password(password.as_bytes(), &salt)
.map_err(|_| anyhow::Error::msg("Failed to hash the password"))?
.to_string();
let userid = Uuid::new_v4();
sqlx::query!(
r#"INSERT INTO Users VALUES (?, ?);"#,
userid.as_bytes().as_slice(),
hash
)
.execute(&self.pool)
.await?;
Ok(Ok(userid))
}
} }

View File

@ -2,6 +2,7 @@ mod api;
mod backend; mod backend;
mod config; mod config;
use crate::backend::Backend;
use anyhow::Result; use anyhow::Result;
use compile_time_run::run_command_str; use compile_time_run::run_command_str;
use config::Config; use config::Config;
@ -23,7 +24,7 @@ async fn main() -> Result<()> {
println!("{config:#?}"); println!("{config:#?}");
let backend = backend::start(&config).await?; let backend = Backend::start(&config).await?;
api::start(&config, backend).await?; api::start(&config, backend).await?;
Ok(()) Ok(())