diff --git a/api/.env b/api/.env index 840fd28..307a409 100644 --- a/api/.env +++ b/api/.env @@ -1,8 +1,8 @@ HOSTNAME=localhost PORT=42068 -DOMAIN=http://localhost:42069 -RP_ID=localhost +DOMAIN=http://api.debtpirate.app +RP_ID=debtpirate.app TOKEN_KEY=k4.local.hWoS2ZulK9xPEATtXH1Dvj_iynzqfUv5ER5_IFTg5-Q -DATABASE_URL=postgres://debt-pirate:test@192.168.122.215/debt_pirate +DATABASE_URL=postgres://debt_pirate:HRURqlUmtjIy@192.168.122.251/debt_pirate ASSETS_DIR=/home/zcdziura/Documents/Projects/debt-pirate/api/assets MAINTENANCE_USER_ACCOUNT=debt_pirate:HRURqlUmtjIy diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json new file mode 100644 index 0000000..3f4b3f6 --- /dev/null +++ b/api/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'auth-test'", + "cargo": { + "args": [ + "build", + "--bin=auth-test", + "--package=auth-test" + ], + "filter": { + "name": "auth-test", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'auth-test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=auth-test", + "--package=auth-test" + ], + "filter": { + "name": "auth-test", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/api/src/db/mod.rs b/api/src/db/mod.rs index b3bf63e..6903f20 100644 --- a/api/src/db/mod.rs +++ b/api/src/db/mod.rs @@ -3,7 +3,7 @@ mod user; use sqlx::{self, postgres::PgPoolOptions, Pool, Postgres}; pub use user::*; -use crate::error::AppError; +use crate::models::AppError; pub type DbPool = Pool; diff --git a/api/src/db/user.rs b/api/src/db/user.rs index 70e0652..eea68a8 100644 --- a/api/src/db/user.rs +++ b/api/src/db/user.rs @@ -1,8 +1,6 @@ use sqlx::prelude::FromRow; use uuid::Uuid; -use crate::error::AppError; - use super::DbPool; #[derive(Debug)] diff --git a/api/src/main.rs b/api/src/main.rs index 24dba1b..d3c36c2 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -3,7 +3,6 @@ use std::{process, sync::mpsc::channel}; use models::Environment; mod db; -mod error; mod models; mod requests; mod services; diff --git a/api/src/models/environment.rs b/api/src/models/environment.rs index 5034ff0..cc4c7fa 100644 --- a/api/src/models/environment.rs +++ b/api/src/models/environment.rs @@ -7,14 +7,22 @@ use std::{ use once_cell::sync::Lazy; use pasetors::{keys::SymmetricKey, version4::V4}; -use crate::{error::AppError, services::UserConfirmationMessage}; +use crate::services::UserConfirmationMessage; -static REQUIRED_ENV_VARS: Lazy> = Lazy::new(|| { - HashSet::::from_iter( - ["TOKEN_KEY", "DATABASE_URL", "ASSETS_DIR"] - .into_iter() - .map(ToString::to_string), - ) +use super::AppError; + +static REQUIRED_ENV_VARS: Lazy> = Lazy::new(|| { + [ + "HOSTNAME", + "PORT", + "DOMAIN", + "RP_ID", + "TOKEN_KEY", + "DATABASE_URL", + "ASSETS_DIR", + ] + .into_iter() + .collect() }); #[derive(Clone)] @@ -35,10 +43,12 @@ impl Environment { dotenvy::dotenv_iter() .expect("Missing .env file") .filter_map(|item| item.ok()) - .filter(|(key, _)| REQUIRED_ENV_VARS.contains(key)) + .filter(|(key, _)| REQUIRED_ENV_VARS.contains(key.as_str())) .for_each(|(key, value)| match key.as_str() { "HOSTNAME" => builder.with_hostname(value), "PORT" => builder.with_port(value), + "DOMAIN" => builder.with_domain(value), + "RP_ID" => builder.with_rp_id(value), "TOKEN_KEY" => builder.with_token_key(value), "DATABASE_URL" => builder.with_database_url(value), "ASSETS_DIR" => builder.with_assets_dir(value), @@ -65,6 +75,10 @@ impl Environment { self.domain.as_str() } + pub fn rp_id(&self) -> &str { + self.rp_id.as_str() + } + pub fn token_key(&self) -> &SymmetricKey { &self.token_key } @@ -108,7 +122,7 @@ impl From for Environment { } } -#[derive(Default)] +#[derive(Debug, Default)] pub struct EnvironmentObjectBuilder { pub hostname: Option, pub port: Option, @@ -136,7 +150,7 @@ impl EnvironmentObjectBuilder { ("DATABASE_URL", self.database_url.as_deref()), ] .into_iter() - .filter_map(|(key, value)| value.xor(Some(key))) + .filter_map(|(key, value)| value.map(|_| key).xor(Some(key))) .collect::>(); if self.token_key.is_none() { diff --git a/api/src/error.rs b/api/src/models/error.rs similarity index 100% rename from api/src/error.rs rename to api/src/models/error.rs diff --git a/api/src/models/mod.rs b/api/src/models/mod.rs index 87ffc86..7446c05 100644 --- a/api/src/models/mod.rs +++ b/api/src/models/mod.rs @@ -1,5 +1,7 @@ mod api_response; mod environment; +mod error; pub use api_response::*; pub use environment::*; +pub use error::*; diff --git a/api/src/requests/mod.rs b/api/src/requests/mod.rs index e4bee0c..e2ab47d 100644 --- a/api/src/requests/mod.rs +++ b/api/src/requests/mod.rs @@ -4,9 +4,12 @@ use std::sync::Arc; use axum::Router; use tokio::net::TcpListener; -use webauthn_rs::Webauthn; +use webauthn_rs::prelude::*; -use crate::{db::DbPool, error::AppError, models::Environment}; +use crate::{ + db::DbPool, + models::{AppError, Environment}, +}; #[derive(Clone)] pub struct AppState { @@ -17,9 +20,25 @@ pub struct AppState { impl AppState { pub fn new(pool: DbPool, env: Environment) -> Self { - let rp_id = + let rp_id = env.rp_id(); + let rp_origin = env + .domain() + .parse::() + .expect("RP_ORIGIN must be in a valid domain name format"); - Self { pool, env } + let webauthn = Arc::new( + WebauthnBuilder::new(rp_id, &rp_origin) + .map(|builder| builder.allow_any_port(true)) + .and_then(WebauthnBuilder::build) + .inspect_err(|err| eprintln!("{err}")) + .expect("Unable to build authenticator"), + ); + + Self { + pool, + env, + webauthn, + } } pub fn pool(&self) -> &DbPool { @@ -29,6 +48,10 @@ impl AppState { pub fn env(&self) -> &Environment { &self.env } + + pub fn webauthn(&self) -> Arc { + Arc::clone(&self.webauthn) + } } pub async fn start_app(pool: DbPool, env: Environment) -> Result<(), AppError> { diff --git a/api/src/requests/user/new_user/models/mod.rs b/api/src/requests/user/new_user/models/mod.rs index d30e4ef..4000572 100644 --- a/api/src/requests/user/new_user/models/mod.rs +++ b/api/src/requests/user/new_user/models/mod.rs @@ -1,5 +1,3 @@ mod registration_request; -mod registration_response; pub use registration_request::*; -pub use registration_response::*; diff --git a/api/src/requests/user/new_user/models/registration_response.rs b/api/src/requests/user/new_user/models/registration_response.rs deleted file mode 100644 index 970f0cf..0000000 --- a/api/src/requests/user/new_user/models/registration_response.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::time::SystemTime; - -use humantime::format_rfc3339_seconds; -use serde::Serialize; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UserRegistrationResponse { - user_id: i32, - auth: UserPostResponseToken, -} - -impl UserRegistrationResponse { - pub fn new(user_id: i32, token: String, expiration: SystemTime) -> Self { - let auth = UserPostResponseToken { - token, - expiration: format_rfc3339_seconds(expiration).to_string(), - }; - - Self { user_id, auth } - } -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UserPostResponseToken { - pub token: String, - pub expiration: String, -} diff --git a/api/src/requests/user/new_user/request.rs b/api/src/requests/user/new_user/request.rs index 1039841..6a5e6b2 100644 --- a/api/src/requests/user/new_user/request.rs +++ b/api/src/requests/user/new_user/request.rs @@ -8,6 +8,8 @@ use axum::{ }; use http::StatusCode; use tower_sessions::Session; +use uuid::Uuid; +use webauthn_rs::prelude::CreationChallengeResponse; use crate::{ db::NewUserEntity, @@ -16,7 +18,7 @@ use crate::{ services::{auth_token::generate_token, UserConfirmationMessage}, }; -use super::models::{UserRegistrationRequest, UserRegistrationResponse}; +use super::models::UserRegistrationRequest; static FIFTEEN_MINUTES: u64 = 60 * 15; @@ -31,9 +33,17 @@ async fn user_registration_post_handler( session: Session, Json(request): Json, ) -> Result { - session.remove_value("reg_state"); + let _ = session.remove_value("reg_state").await; let UserRegistrationRequest { name, email } = request; + let user_uuid = Uuid::now_v7(); + + // TODO: Fetch any already saved credentials for the given user for exclusion + let (creation_challenge_response, reg) = app_state + .webauthn() + .start_passkey_registration(user_uuid, email.as_str(), name.as_str(), None) + .expect("Invalid passkey registration"); + // let new_user = NewUserEntity::new(email.clone(), name.clone()); // let UserEntity { id: user_id, name, email , ..} = insert_new_user(app_state.pool(), new_user).await // .map_err(|err| { @@ -55,12 +65,12 @@ async fn user_registration_post_handler( // eprintln!("Got the rollowing error while sending across the channel: {err}"); // }); - let new_user_entity = NewUserEntity::new(name, email); + // let new_user_entity = NewUserEntity::new(name, email); let response = ( - StatusCode::CREATED, - Json(ApiResponse::::new( - UserRegistrationResponse::new(user_id, auth_token, expiration), + StatusCode::OK, + Json(ApiResponse::::new( + creation_challenge_response, )), ); diff --git a/api/src/services/auth_token.rs b/api/src/services/auth_token.rs index 1300ee5..bdf40c2 100644 --- a/api/src/services/auth_token.rs +++ b/api/src/services/auth_token.rs @@ -10,7 +10,7 @@ use pasetors::{ }; use uuid::Uuid; -use crate::error::AppError; +use crate::models::AppError; static FOURTY_FIVE_DAYS: u64 = 3_888_000; // 60 * 60 * 24 * 45 static ONE_HOUR: u64 = 3_600;