This commit is contained in:
Cara Salter 2025-03-20 14:39:43 -04:00
parent 4d167f7160
commit 0ccf714d73
6 changed files with 160 additions and 6 deletions

48
Cargo.lock generated
View file

@ -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"
@ -158,6 +170,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"
@ -208,7 +229,9 @@ checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
@ -574,7 +597,10 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
name = "goathacks-backend"
version = "0.1.0"
dependencies = [
"argon2",
"axum",
"chrono",
"kankyo",
"reqwest",
"serde",
"serde_json",
@ -585,6 +611,7 @@ dependencies = [
"tracing",
"tracing-appender",
"tracing-subscriber",
"uuid",
]
[[package]]
@ -978,6 +1005,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kankyo"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325a11231fa70c1d1b562655db757cefb6022876d62f173831f35bd670ae0c40"
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -1268,6 +1301,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",
"subtle",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@ -2440,6 +2484,10 @@ name = "uuid"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
"getrandom 0.3.2",
"serde",
]
[[package]]
name = "valuable"

View file

@ -9,10 +9,20 @@ thiserror="2"
tracing="0.1"
tracing-subscriber={version="0.3", features=["env-filter"]}
tracing-appender="0.2"
axum = "0.8"
serde_json="1"
kankyo = "0.3"
chrono = "0.4"
argon2 = "0.5"
[dependencies.axum]
version = "0.8"
features = [
"json"
]
[dependencies.reqwest]
version="0.12"
@ -26,6 +36,13 @@ features=["trace"]
version="1"
features=["derive"]
[dependencies.uuid]
version = "1"
features = [
"v4",
"serde"
]
# Async runtime
[dependencies.tokio]
version="1"

22
src/errors.rs Normal file
View file

@ -0,0 +1,22 @@
use axum::response::IntoResponse;
use axum::http::StatusCode;
use axum::Json;
use serde_json::json;
use thiserror::Error as thisError;
#[derive(thisError, Debug)]
pub enum Error {
#[error("Invalid value provided: {0}")]
ValueError(String)
}
pub type EmptyResult = Result<()>;
pub type Result<T> = std::result::Result<T, Error>;
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
let body = json!({"status": "error", "message": format!("{}", self)});
(StatusCode::INTERNAL_SERVER_ERROR, Json(body)).into_response()
}
}

View file

@ -1,22 +1,26 @@
use std::time::Duration;
use axum::{routing::get, Router};
use axum::{Router, routing::get};
use sqlx::postgres::PgPoolOptions;
use tokio::net::TcpListener;
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod models;
mod errors;
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "goathacks-backend=debug".into()))
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "goathacks-backend=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
let db_connection_str = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://localhost/goathacks".into());
let db_connection_str =
std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://localhost/goathacks".into());
let pool = PgPoolOptions::new()
.max_connections(5)

1
src/models/mod.rs Normal file
View file

@ -0,0 +1 @@
mod users;

62
src/models/users.rs Normal file
View file

@ -0,0 +1,62 @@
/*! Users are entities that interact with the system.
*
* They are notably separate from any API tokens.
*/
use std::fmt::{Debug, Display};
use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2};
use chrono::{DateTime, Utc};
use crate::errors::{EmptyResult, Result};
/** A single User with access to the system
*
* The specific User object contains profile and biographic data that's either useful to ACM or
* required by external partners (see: MLH).
*/
#[derive(Clone)]
struct User {
/// Unique user identifier, a V4 UUID
id: uuid::Uuid,
/// The user's name. Not guaranteed to be anything in particular, should be
/// treated as a standard UTF8 string
name: String,
/// User's (hashed) password
password_hash: String,
/// User's email. This must be validated for correct format!
email: String,
/// Whether the user is allowed to log in.
is_active: bool,
/// UTC date/time of creation
created: chrono::DateTime<Utc>,
/// UTC date/time of last login
last_login: Option<DateTime<Utc>>,
}
impl Display for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({})", self.email, self.id)
}
}
impl Debug for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({})", self.email, self.id)
}
}
impl User {
pub fn new_user() -> Self {
Self { id: uuid::Uuid::new_v4(), name: String::new(), password_hash: String::new(), email: String::new(), is_active: true, created: Utc::now(), last_login: None }
}
pub fn set_password(new_pw: String) -> EmptyResult {
let argon = Argon2::default();
let salt = SaltString::generate(&mut OsRng);
let mut out: Vec<u8> = Vec::new();
Ok(())
}
}