From 67fad103142995e9a4aad3d9d61c1ad499e1c9fe Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Wed, 24 May 2023 05:59:58 +1200 Subject: [PATCH 01/73] add logic to register endpoint --- .idea/workspace.xml | 71 ++++++++++++++++-------------- backend/Cargo.lock | 15 +++++++ backend/Cargo.toml | 3 +- backend/src/api/auth/controller.rs | 8 +++- backend/src/api/auth/mod.rs | 1 + backend/src/api/auth/model.rs | 9 ++++ backend/src/db/models/mod.rs | 2 +- backend/src/db/models/user.rs | 3 +- 8 files changed, 73 insertions(+), 39 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 4351308..7e12e3d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,12 +8,17 @@ - - - - - + + + + + + + + + + - { + "keyToString": { + "RunOnceActivity.OpenDatabaseViewOnStart": "true", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "main", + "last_opened_file_path": "/home/jchad/Code/domus/.github/ISSUE_TEMPLATE", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "node", + "nodejs_package_manager_path": "npm", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", + "prettierjs.PrettierConfiguration.Package": "", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "database.query.execution", + "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" ] } -}]]> +} @@ -125,7 +130,7 @@ - + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ae80887..bfd33a5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -258,6 +258,7 @@ dependencies = [ "actix-web", "chrono", "diesel", + "serde", "uuid", ] @@ -985,6 +986,20 @@ name = "serde" version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] [[package]] name = "serde_json" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index cf48aa0..c5021b5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,4 +9,5 @@ edition = "2021" actix-web = "4" chrono = "0.4.24" diesel = { version = "2.0.0", features = ["postgres","chrono","uuid"] } -uuid = {version = "1.3.3", features = ["v4","fast-rng","macro-diagnostics"]} +uuid = { version = "1.3.3", features = ["v4","fast-rng","macro-diagnostics"] } +serde = { version="1.0.163", features = ["derive"] } diff --git a/backend/src/api/auth/controller.rs b/backend/src/api/auth/controller.rs index 5cab71f..db96f9f 100644 --- a/backend/src/api/auth/controller.rs +++ b/backend/src/api/auth/controller.rs @@ -1,6 +1,10 @@ +use crate::api::auth::model::RegisterNewUser; use actix_web::*; +use crate::db::models::user::create_user; #[get("/register")] -pub async fn register() -> String { - "Hello there!".to_string() +pub async fn register(user: web::Json) -> impl Responder { + create_user(user); + + HttpResponse::NoContent() } diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs index 70b4bf7..1b39b59 100644 --- a/backend/src/api/auth/mod.rs +++ b/backend/src/api/auth/mod.rs @@ -1,6 +1,7 @@ use actix_web::web; mod controller; +mod model; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::scope("/auth").service(controller::register)); diff --git a/backend/src/api/auth/model.rs b/backend/src/api/auth/model.rs index e69de29..1455952 100644 --- a/backend/src/api/auth/model.rs +++ b/backend/src/api/auth/model.rs @@ -0,0 +1,9 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct RegisterNewUser { + email: String, + first_name: String, + last_name: String, + password: String, +} diff --git a/backend/src/db/models/mod.rs b/backend/src/db/models/mod.rs index 0eba110..22d12a3 100644 --- a/backend/src/db/models/mod.rs +++ b/backend/src/db/models/mod.rs @@ -1 +1 @@ -mod user; +pub mod user; diff --git a/backend/src/db/models/user.rs b/backend/src/db/models/user.rs index 0f81743..fc7b108 100644 --- a/backend/src/db/models/user.rs +++ b/backend/src/db/models/user.rs @@ -22,9 +22,8 @@ pub struct NewUser<'a> { pub password: &'a str, } -pub fn create_user(conn: &mut PgConnection, user: NewUser) -> User { +pub fn create_user(conn: &mut PgConnection, user: NewUser) -> QueryResult { diesel::insert_into(schema::users::table) .values(&user) .get_result(conn) - .expect("Error saving new post") } From 722343788c6c8176b430186b788f3779cbebd4b6 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Wed, 24 May 2023 07:08:11 +1200 Subject: [PATCH 02/73] add validator --- .idea/workspace.xml | 62 ++++++++++++------------- backend/Cargo.lock | 74 +++++++++++++++++++++++++++++- backend/Cargo.toml | 1 + backend/src/api/auth/controller.rs | 14 ++++-- backend/src/api/auth/mod.rs | 1 + backend/src/api/auth/model.rs | 7 ++- backend/src/api/auth/service.rs | 1 + 7 files changed, 120 insertions(+), 40 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 7e12e3d..46efd70 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,17 +8,13 @@ - - - - - + - { - "keyToString": { - "RunOnceActivity.OpenDatabaseViewOnStart": "true", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "git-widget-placeholder": "main", - "last_opened_file_path": "/home/jchad/Code/domus/.github/ISSUE_TEMPLATE", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_interpreter_path": "node", - "nodejs_package_manager_path": "npm", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", - "prettierjs.PrettierConfiguration.Package": "", - "project.structure.last.edited": "Modules", - "project.structure.proportion": "0.15", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "database.query.execution", - "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -130,7 +126,7 @@ - + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index bfd33a5..3b43e3e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -260,6 +260,7 @@ dependencies = [ "diesel", "serde", "uuid", + "validator", ] [[package]] @@ -627,6 +628,17 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.3.0" @@ -637,6 +649,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "indexmap" version = "1.9.3" @@ -677,6 +695,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.144" @@ -720,6 +744,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "memchr" version = "2.5.0" @@ -1230,7 +1260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", + "idna 0.3.0", "percent-encoding", ] @@ -1256,6 +1286,48 @@ dependencies = [ "syn 2.0.16", ] +[[package]] +name = "validator" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591" +dependencies = [ + "idna 0.2.3", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c5021b5..60b50fe 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,3 +11,4 @@ chrono = "0.4.24" diesel = { version = "2.0.0", features = ["postgres","chrono","uuid"] } uuid = { version = "1.3.3", features = ["v4","fast-rng","macro-diagnostics"] } serde = { version="1.0.163", features = ["derive"] } +validator = { version = "0.16.0", features=["derive"] } diff --git a/backend/src/api/auth/controller.rs b/backend/src/api/auth/controller.rs index db96f9f..54cabf6 100644 --- a/backend/src/api/auth/controller.rs +++ b/backend/src/api/auth/controller.rs @@ -1,10 +1,14 @@ use crate::api::auth::model::RegisterNewUser; use actix_web::*; -use crate::db::models::user::create_user; +use validator::Validate; -#[get("/register")] -pub async fn register(user: web::Json) -> impl Responder { - create_user(user); +#[post("/register")] +pub async fn register(user: web::Json) -> HttpResponse { + let val_result = user.validate(); + if let Err(e) = val_result { + return HttpResponse::BadRequest().json(e); + } - HttpResponse::NoContent() + println!("Registering new user: {:?}", user); + HttpResponse::NoContent().finish() } diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs index 1b39b59..1cbdc4a 100644 --- a/backend/src/api/auth/mod.rs +++ b/backend/src/api/auth/mod.rs @@ -2,6 +2,7 @@ use actix_web::web; mod controller; mod model; +mod service; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::scope("/auth").service(controller::register)); diff --git a/backend/src/api/auth/model.rs b/backend/src/api/auth/model.rs index 1455952..649c0d0 100644 --- a/backend/src/api/auth/model.rs +++ b/backend/src/api/auth/model.rs @@ -1,9 +1,14 @@ use serde::Deserialize; +use validator::Validate; -#[derive(Deserialize)] +#[derive(Deserialize, Validate, Debug)] pub struct RegisterNewUser { + #[validate(email)] email: String, + #[validate(length(min = 1))] first_name: String, + #[validate(length(min = 1))] last_name: String, + #[validate(length(min = 8, max = 64))] password: String, } diff --git a/backend/src/api/auth/service.rs b/backend/src/api/auth/service.rs index e69de29..8b13789 100644 --- a/backend/src/api/auth/service.rs +++ b/backend/src/api/auth/service.rs @@ -0,0 +1 @@ + From 314193c949c6fc773ac1ea15a5911c3e464416d2 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Wed, 24 May 2023 08:40:48 +1200 Subject: [PATCH 03/73] add httpresponse --- .idea/runConfigurations/Backend.xml | 4 +- .idea/workspace.xml | 11 +++-- backend/Cargo.lock | 61 +++++++++++++++++++++++++- backend/Cargo.toml | 2 + backend/src/api/auth/controller.rs | 3 ++ backend/src/api/mod.rs | 11 +++++ backend/src/api/shared/api_response.rs | 58 ++++++++++++++++++++++++ backend/src/api/shared/mod.rs | 1 + backend/src/main.rs | 16 +++++-- 9 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 backend/src/api/shared/api_response.rs create mode 100644 backend/src/api/shared/mod.rs diff --git a/.idea/runConfigurations/Backend.xml b/.idea/runConfigurations/Backend.xml index b4f52d6..0955657 100644 --- a/.idea/runConfigurations/Backend.xml +++ b/.idea/runConfigurations/Backend.xml @@ -9,7 +9,9 @@ + + + - - - + + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 3b43e3e..4034196 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -245,6 +245,17 @@ dependencies = [ "libc", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -258,6 +269,8 @@ dependencies = [ "actix-web", "chrono", "diesel", + "env_logger", + "log", "serde", "uuid", "validator", @@ -472,6 +485,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "flate2" version = "1.0.26" @@ -573,6 +599,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -605,6 +640,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "iana-time-zone" version = "0.1.56" @@ -808,7 +849,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1121,6 +1162,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "time" version = "0.1.45" @@ -1422,6 +1472,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 60b50fe..18bf5c4 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -12,3 +12,5 @@ diesel = { version = "2.0.0", features = ["postgres","chrono","uuid"] } uuid = { version = "1.3.3", features = ["v4","fast-rng","macro-diagnostics"] } serde = { version="1.0.163", features = ["derive"] } validator = { version = "0.16.0", features=["derive"] } +env_logger = "0.9.0" +log = "0.4.17" diff --git a/backend/src/api/auth/controller.rs b/backend/src/api/auth/controller.rs index 54cabf6..19eaad0 100644 --- a/backend/src/api/auth/controller.rs +++ b/backend/src/api/auth/controller.rs @@ -1,9 +1,12 @@ use crate::api::auth::model::RegisterNewUser; use actix_web::*; +use log; use validator::Validate; #[post("/register")] pub async fn register(user: web::Json) -> HttpResponse { + log::trace!("Registering new user: {:?}", user); + let val_result = user.validate(); if let Err(e) = val_result { return HttpResponse::BadRequest().json(e); diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index dd80897..bd4f93f 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,7 +1,18 @@ use actix_web::web; +use log::warn; mod auth; +mod shared; pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.app_data(web::JsonConfig::default().error_handler(|err, _req| { + warn!("Json error: {:?}", err); + + actix_web::error::InternalError::from_response( + err, + shared::api_response::Response::fail(err.to_string()).into(), + ) + .into() + })); auth::configure(cfg); } diff --git a/backend/src/api/shared/api_response.rs b/backend/src/api/shared/api_response.rs new file mode 100644 index 0000000..fd4ef38 --- /dev/null +++ b/backend/src/api/shared/api_response.rs @@ -0,0 +1,58 @@ +// https://github.com/omniti-labs/jsend + +use actix_web::HttpResponse; +use serde::Serialize; + +#[derive(Serialize)] +enum ResponseStatus { + Success, // All went well, and (usually) some data was returned. + Error, // An error occurred in processing the request, i.e. an exception was thrown + Fail, // There was a problem with the data submitted, or some pre-condition of the API call wasn't satisfied +} + +#[derive(Serialize)] +pub struct Response { + status: ResponseStatus, + data: Option, + message: Option, + code: Option, +} + +impl From> for HttpResponse { + fn from(value: Response) -> Self { + match value.status { + ResponseStatus::Success => HttpResponse::Ok().json(value), + ResponseStatus::Error => HttpResponse::InternalServerError().json(value), + ResponseStatus::Fail => HttpResponse::BadRequest().json(value), + } + } +} + +impl Response { + pub fn success(data: T) -> Self { + Self { + status: ResponseStatus::Success, + data: Option::from(data), + message: None, + code: None, + } + } + + pub fn error(message: String, code: Option, data: Option) -> Self { + Self { + status: ResponseStatus::Error, + data, + message: Option::from(message), + code, + } + } + + pub fn fail(data: T) -> Self { + Self { + status: ResponseStatus::Fail, + data: Option::from(data), + message: None, + code: None, + } + } +} diff --git a/backend/src/api/shared/mod.rs b/backend/src/api/shared/mod.rs new file mode 100644 index 0000000..4f99ff6 --- /dev/null +++ b/backend/src/api/shared/mod.rs @@ -0,0 +1 @@ +pub mod api_response; diff --git a/backend/src/main.rs b/backend/src/main.rs index 09c2e55..796801c 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,10 +1,18 @@ +use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; use backend::api; +use env_logger::Env; #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(web::scope("/v1").configure(api::configure))) - .bind(("127.0.0.1", 8080))? - .run() - .await + env_logger::init_from_env(Env::default().default_filter_or("info")); + + HttpServer::new(|| { + App::new() + .wrap(Logger::default()) + .service(web::scope("/v1").configure(api::configure)) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await } From d69a9e217e387745bc5532cf50df59c7aec603ef Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Thu, 25 May 2023 07:29:35 +1200 Subject: [PATCH 04/73] add json error handler --- .idea/workspace.xml | 65 ++++++++++---------- backend/Cargo.lock | 1 + backend/Cargo.toml | 1 + backend/src/api/mod.rs | 14 ++--- backend/src/api/shared/api_response.rs | 36 +++++++++-- backend/src/api/shared/json_error_handler.rs | 34 ++++++++++ backend/src/api/shared/mod.rs | 1 + 7 files changed, 104 insertions(+), 48 deletions(-) create mode 100644 backend/src/api/shared/json_error_handler.rs diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1e7d796..67fef43 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,15 +8,13 @@ - - - + - - + + - { + "keyToString": { + "RunOnceActivity.OpenDatabaseViewOnStart": "true", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "1-crud-methods-for-user-resource-1", + "last_opened_file_path": "/home/jchad/Code/domus/.github/ISSUE_TEMPLATE", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "node", + "nodejs_package_manager_path": "npm", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", + "prettierjs.PrettierConfiguration.Package": "", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "database.query.execution", + "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" ] } -}]]> +} @@ -130,6 +128,7 @@ + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 4034196..e602230 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -272,6 +272,7 @@ dependencies = [ "env_logger", "log", "serde", + "serde_json", "uuid", "validator", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 18bf5c4..1bd9c1d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -14,3 +14,4 @@ serde = { version="1.0.163", features = ["derive"] } validator = { version = "0.16.0", features=["derive"] } env_logger = "0.9.0" log = "0.4.17" +serde_json = "1.0.96" diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index bd4f93f..1964b73 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,18 +1,12 @@ use actix_web::web; -use log::warn; mod auth; mod shared; pub fn configure(cfg: &mut web::ServiceConfig) { - cfg.app_data(web::JsonConfig::default().error_handler(|err, _req| { - warn!("Json error: {:?}", err); - - actix_web::error::InternalError::from_response( - err, - shared::api_response::Response::fail(err.to_string()).into(), - ) - .into() - })); + cfg.app_data( + web::JsonConfig::default() + .error_handler(|err, _req| shared::json_error_handler::handle_json_error(err).into()), + ); auth::configure(cfg); } diff --git a/backend/src/api/shared/api_response.rs b/backend/src/api/shared/api_response.rs index fd4ef38..c816390 100644 --- a/backend/src/api/shared/api_response.rs +++ b/backend/src/api/shared/api_response.rs @@ -1,25 +1,51 @@ // https://github.com/omniti-labs/jsend use actix_web::HttpResponse; -use serde::Serialize; +use log::error; +use serde::{Serialize, Serializer}; -#[derive(Serialize)] enum ResponseStatus { Success, // All went well, and (usually) some data was returned. Error, // An error occurred in processing the request, i.e. an exception was thrown Fail, // There was a problem with the data submitted, or some pre-condition of the API call wasn't satisfied } +impl Serialize for ResponseStatus { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ResponseStatus::Success => serializer.serialize_str("success"), + ResponseStatus::Error => serializer.serialize_str("error"), + ResponseStatus::Fail => serializer.serialize_str("fail"), + } + } +} + #[derive(Serialize)] pub struct Response { status: ResponseStatus, + #[serde(skip_serializing_if = "Option::is_none")] data: Option, + #[serde(skip_serializing_if = "Option::is_none")] message: Option, + #[serde(skip_serializing_if = "Option::is_none")] code: Option, } impl From> for HttpResponse { fn from(value: Response) -> Self { + if let Some(code) = value.code { + let status_code = actix_web::http::StatusCode::from_u16(code); + return if let Ok(status_code) = status_code { + HttpResponse::build(status_code).json(value) + } else { + error!("Invalid status code: {}", code); + HttpResponse::InternalServerError().finish() + }; + } + match value.status { ResponseStatus::Success => HttpResponse::Ok().json(value), ResponseStatus::Error => HttpResponse::InternalServerError().json(value), @@ -47,11 +73,11 @@ impl Response { } } - pub fn fail(data: T) -> Self { + pub fn fail(message: String, data: Option) -> Self { Self { status: ResponseStatus::Fail, - data: Option::from(data), - message: None, + data, + message: Option::from(message), code: None, } } diff --git a/backend/src/api/shared/json_error_handler.rs b/backend/src/api/shared/json_error_handler.rs new file mode 100644 index 0000000..37387c5 --- /dev/null +++ b/backend/src/api/shared/json_error_handler.rs @@ -0,0 +1,34 @@ +use crate::api::shared::api_response::Response; +use actix_web::error::{InternalError, JsonPayloadError}; +use log::error; +use serde_json::error::Category; +use serde_json::Error as JsonError; + +pub fn handle_json_error(err: JsonPayloadError) -> InternalError { + let response = get_response(&err); + InternalError::from_response(err, response.into()) +} + +fn get_response(err: &JsonPayloadError) -> Response { + match err { + JsonPayloadError::OverflowKnownLength { .. } + | JsonPayloadError::Overflow { .. } + | JsonPayloadError::ContentType => Response::fail(err.to_string(), None), + JsonPayloadError::Deserialize(e) => get_json_error_message(e), + JsonPayloadError::Serialize(_) => { + Response::error("failed to serialize response".to_string(), None, None) + } + JsonPayloadError::Payload(e) => Response::fail(e.to_string(), None), + _ => { + error!("Unhandled JsonPayloadError: {:?}", err); + Response::error("an unknown error occurred".to_string(), None, None) + } + } +} + +fn get_json_error_message(err: &JsonError) -> Response { + match err.classify() { + Category::Io => Response::error(err.to_string(), None, None), + Category::Syntax | Category::Eof | Category::Data => Response::fail(err.to_string(), None), + } +} diff --git a/backend/src/api/shared/mod.rs b/backend/src/api/shared/mod.rs index 4f99ff6..6a6b8b8 100644 --- a/backend/src/api/shared/mod.rs +++ b/backend/src/api/shared/mod.rs @@ -1 +1,2 @@ pub mod api_response; +pub mod json_error_handler; From a1e8fe533eefc5b508be3a83b6f9ba65023c18c3 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Thu, 25 May 2023 07:53:14 +1200 Subject: [PATCH 05/73] add password hash function --- .idea/workspace.xml | 8 +++--- backend/Cargo.lock | 45 ++++++++++++++++++++++++++++++++ backend/Cargo.toml | 1 + backend/src/api/auth/service.rs | 12 +++++++++ backend/src/api/shared/errors.rs | 0 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 backend/src/api/shared/errors.rs diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 67fef43..d6924ea 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,13 +8,11 @@ - + - - - + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index e602230..dc1bf75 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -245,6 +245,17 @@ dependencies = [ "libc", ] +[[package]] +name = "argon2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] + [[package]] name = "atty" version = "0.2.14" @@ -267,6 +278,7 @@ name = "backend" version = "0.1.0" dependencies = [ "actix-web", + "argon2", "chrono", "diesel", "env_logger", @@ -283,12 +295,27 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[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" @@ -475,6 +502,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -883,6 +911,17 @@ dependencies = [ "windows-sys 0.45.0", ] +[[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 = "paste" version = "1.0.12" @@ -1141,6 +1180,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 1bd9c1d..21641a8 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -15,3 +15,4 @@ validator = { version = "0.16.0", features=["derive"] } env_logger = "0.9.0" log = "0.4.17" serde_json = "1.0.96" +argon2 = "0.5.0" diff --git a/backend/src/api/auth/service.rs b/backend/src/api/auth/service.rs index 8b13789..9c760ab 100644 --- a/backend/src/api/auth/service.rs +++ b/backend/src/api/auth/service.rs @@ -1 +1,13 @@ +use argon2::password_hash::rand_core::OsRng; +use argon2::password_hash::SaltString; +use argon2::{Argon2, PasswordHasher}; + +pub fn hash_password(password: &str) -> Result { + let password_bytes = password.as_bytes(); + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + let password_hash = argon2.hash_password(password_bytes, &salt)?; + + Ok(password_hash.to_string()) +} diff --git a/backend/src/api/shared/errors.rs b/backend/src/api/shared/errors.rs new file mode 100644 index 0000000..e69de29 From ea566cd91ed85383436e9b7511f33cd1dee0cee6 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 27 May 2023 05:58:07 +1200 Subject: [PATCH 06/73] add openapi docs --- .../Backend__Compose_Deployment.xml | 11 - .idea/workspace.xml | 102 ++++-- backend/Cargo.lock | 303 ++++++++++++++++-- backend/Cargo.toml | 5 +- backend/migrations/.keep | 0 .../2023-05-20-201029_create_user/up.sql | 2 +- backend/src/api/api_docs.rs | 20 ++ backend/src/api/auth/api_docs.rs | 28 ++ backend/src/api/auth/controller.rs | 9 +- backend/src/api/auth/mod.rs | 7 +- backend/src/api/auth/model.rs | 3 +- backend/src/api/auth/service.rs | 3 +- backend/src/api/mod.rs | 11 +- backend/src/api/shared/mod.rs | 1 + backend/src/main.rs | 6 + 15 files changed, 439 insertions(+), 72 deletions(-) delete mode 100644 .idea/runConfigurations/Backend__Compose_Deployment.xml delete mode 100644 backend/migrations/.keep create mode 100644 backend/src/api/api_docs.rs create mode 100644 backend/src/api/auth/api_docs.rs diff --git a/.idea/runConfigurations/Backend__Compose_Deployment.xml b/.idea/runConfigurations/Backend__Compose_Deployment.xml deleted file mode 100644 index 64ddc4e..0000000 --- a/.idea/runConfigurations/Backend__Compose_Deployment.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d6924ea..ec9ffae 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,11 +8,20 @@ - + + + + + + + + + + - { - "keyToString": { - "RunOnceActivity.OpenDatabaseViewOnStart": "true", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "git-widget-placeholder": "1-crud-methods-for-user-resource-1", - "last_opened_file_path": "/home/jchad/Code/domus/.github/ISSUE_TEMPLATE", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_interpreter_path": "node", - "nodejs_package_manager_path": "npm", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", - "prettierjs.PrettierConfiguration.Package": "", - "project.structure.last.edited": "Modules", - "project.structure.proportion": "0.15", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "database.query.execution", - "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", - "vue.rearranger.settings.migration": "true" + +}]]> + + - - @@ -93,17 +102,42 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index dc1bf75..a926c28 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -256,17 +256,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -285,6 +274,8 @@ dependencies = [ "log", "serde", "serde_json", + "utoipa", + "utoipa-swagger-ui", "uuid", "validator", ] @@ -444,6 +435,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -505,6 +505,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "encoding_rs" version = "0.8.32" @@ -516,17 +536,38 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.26" @@ -630,21 +671,18 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "http" @@ -733,6 +771,30 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", + "serde", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -777,6 +839,12 @@ version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "local-channel" version = "0.1.3" @@ -832,6 +900,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1048,6 +1126,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.8.1" @@ -1065,6 +1154,41 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +[[package]] +name = "rust-embed" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 1.0.109", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1074,12 +1198,35 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1146,6 +1293,26 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1217,6 +1384,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "time" version = "0.1.45" @@ -1328,6 +1515,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -1360,6 +1556,49 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utoipa" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b" +dependencies = [ + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.16", + "uuid", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c" +dependencies = [ + "actix-web", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "utoipa", + "zip", +] + [[package]] name = "uuid" version = "1.3.3" @@ -1436,6 +1675,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1674,6 +1923,18 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 21641a8..d88afee 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -2,6 +2,7 @@ name = "backend" version = "0.1.0" edition = "2021" +license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,7 +13,9 @@ diesel = { version = "2.0.0", features = ["postgres","chrono","uuid"] } uuid = { version = "1.3.3", features = ["v4","fast-rng","macro-diagnostics"] } serde = { version="1.0.163", features = ["derive"] } validator = { version = "0.16.0", features=["derive"] } -env_logger = "0.9.0" +env_logger = "0.10.0" log = "0.4.17" serde_json = "1.0.96" argon2 = "0.5.0" +utoipa = { version = "3", features = ["actix_extras", "uuid", "chrono"] } +utoipa-swagger-ui = { version = "3", features = ["actix-web"] } \ No newline at end of file diff --git a/backend/migrations/.keep b/backend/migrations/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/backend/migrations/2023-05-20-201029_create_user/up.sql b/backend/migrations/2023-05-20-201029_create_user/up.sql index 833759a..13d1ade 100644 --- a/backend/migrations/2023-05-20-201029_create_user/up.sql +++ b/backend/migrations/2023-05-20-201029_create_user/up.sql @@ -1,7 +1,7 @@ -- Your SQL goes here CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - email TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, first_name TEXT NOT NULL, last_name TEXT NOT NULL, password TEXT NOT NULL, diff --git a/backend/src/api/api_docs.rs b/backend/src/api/api_docs.rs new file mode 100644 index 0000000..83ba39d --- /dev/null +++ b/backend/src/api/api_docs.rs @@ -0,0 +1,20 @@ +use utoipa::Modify; +use utoipa::OpenApi as OpenApiTrait; + +use super::auth::api_docs::AuthApiDoc; + +struct ApiMerger; + +impl Modify for ApiMerger { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + openapi.merge(AuthApiDoc::openapi()); + } +} + +#[derive(OpenApiTrait)] +#[openapi( + info(title="Domus API"), + servers((url="/v1/")), + modifiers(&ApiMerger), +)] +pub struct ApiDocs {} diff --git a/backend/src/api/auth/api_docs.rs b/backend/src/api/auth/api_docs.rs new file mode 100644 index 0000000..c1073ef --- /dev/null +++ b/backend/src/api/auth/api_docs.rs @@ -0,0 +1,28 @@ +use utoipa::Modify; +use utoipa::OpenApi as OpenApiTrait; + +use super::{controller, model}; + +struct AuthScopeAddon; + +impl Modify for AuthScopeAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + let keys: Vec = openapi.paths.paths.keys().cloned().collect(); + + for key in keys { + let new_key = format!("/auth{}", key); + if let Some(value) = openapi.paths.paths.remove(&key) { + openapi.paths.paths.insert(new_key, value); + } + } + } +} + +#[derive(OpenApiTrait)] +#[openapi( + paths(controller::register), + components(schemas(model::RegisterNewUser)), + modifiers(&AuthScopeAddon), + tags((name="Auth", description="Endpoints for authentication and user management")) +)] +pub struct AuthApiDoc; diff --git a/backend/src/api/auth/controller.rs b/backend/src/api/auth/controller.rs index 19eaad0..aebfda5 100644 --- a/backend/src/api/auth/controller.rs +++ b/backend/src/api/auth/controller.rs @@ -1,8 +1,15 @@ -use crate::api::auth::model::RegisterNewUser; +use super::model::RegisterNewUser; use actix_web::*; use log; use validator::Validate; +#[utoipa::path( + responses( + (status = 204, description="User registered successfully"), + ), + request_body = RegisterNewUser, + tag="Auth" +)] #[post("/register")] pub async fn register(user: web::Json) -> HttpResponse { log::trace!("Registering new user: {:?}", user); diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs index 1cbdc4a..ebeceda 100644 --- a/backend/src/api/auth/mod.rs +++ b/backend/src/api/auth/mod.rs @@ -1,9 +1,14 @@ use actix_web::web; +use api_docs::AuthApiDoc; +use utoipa::openapi::OpenApi; +use utoipa::OpenApi as OpenApiTrait; +pub mod api_docs; mod controller; mod model; mod service; -pub fn configure(cfg: &mut web::ServiceConfig) { +pub fn configure(cfg: &mut web::ServiceConfig, api_docs: &mut OpenApi) { cfg.service(web::scope("/auth").service(controller::register)); + api_docs.merge(AuthApiDoc::openapi()); } diff --git a/backend/src/api/auth/model.rs b/backend/src/api/auth/model.rs index 649c0d0..960e201 100644 --- a/backend/src/api/auth/model.rs +++ b/backend/src/api/auth/model.rs @@ -1,7 +1,8 @@ use serde::Deserialize; +use utoipa::ToSchema; use validator::Validate; -#[derive(Deserialize, Validate, Debug)] +#[derive(Deserialize, Validate, ToSchema, Debug)] pub struct RegisterNewUser { #[validate(email)] email: String, diff --git a/backend/src/api/auth/service.rs b/backend/src/api/auth/service.rs index 9c760ab..bcdd8e2 100644 --- a/backend/src/api/auth/service.rs +++ b/backend/src/api/auth/service.rs @@ -1,9 +1,8 @@ - use argon2::password_hash::rand_core::OsRng; use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHasher}; -pub fn hash_password(password: &str) -> Result { +pub fn hash_password(password: &str) -> Result { let password_bytes = password.as_bytes(); let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 1964b73..46919e9 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,12 +1,19 @@ use actix_web::web; +use utoipa::openapi::{Info, OpenApi, OpenApiBuilder, Server}; -mod auth; +pub mod api_docs; +pub mod auth; mod shared; pub fn configure(cfg: &mut web::ServiceConfig) { + let mut api_docs: OpenApi = OpenApiBuilder::new() + .info(Info::new("Domus API", "0.1.0")) + .servers(Some([Server::new("/v1/")])) + .build(); + cfg.app_data( web::JsonConfig::default() .error_handler(|err, _req| shared::json_error_handler::handle_json_error(err).into()), ); - auth::configure(cfg); + auth::configure(cfg, &mut api_docs); } diff --git a/backend/src/api/shared/mod.rs b/backend/src/api/shared/mod.rs index 6a6b8b8..b346073 100644 --- a/backend/src/api/shared/mod.rs +++ b/backend/src/api/shared/mod.rs @@ -1,2 +1,3 @@ pub mod api_response; +mod errors; pub mod json_error_handler; diff --git a/backend/src/main.rs b/backend/src/main.rs index 796801c..b37a9e2 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -2,6 +2,8 @@ use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; use backend::api; use env_logger::Env; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -10,6 +12,10 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .wrap(Logger::default()) + .service(SwaggerUi::new("/swagger-ui/{_:.*}").url( + "/v1/api-docs/openapi.json", + api::api_docs::ApiDocs::openapi(), + )) .service(web::scope("/v1").configure(api::configure)) }) .bind(("127.0.0.1", 8080))? From d1881c1dc9b9c6702601baaaf21b5b3b46ec83d4 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 27 May 2023 06:17:44 +1200 Subject: [PATCH 07/73] add db connection pool --- .idea/workspace.xml | 74 ++++++++++++------------------ backend/Cargo.lock | 29 ++++++++++++ backend/Cargo.toml | 6 ++- backend/src/api/auth/controller.rs | 5 +- backend/src/db/connection.rs | 14 ++++-- backend/src/main.rs | 8 ++++ 6 files changed, 85 insertions(+), 51 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ec9ffae..b70feb2 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,19 +8,11 @@ - - - - - - - - - + - { + "keyToString": { + "RunOnceActivity.OpenDatabaseViewOnStart": "true", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "1-crud-methods-for-user-resource-1", + "last_opened_file_path": "/home/jchad/Code/domus/backend/src/api", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "node", + "nodejs_package_manager_path": "npm", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", + "prettierjs.PrettierConfiguration.Package": "", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "Docker", + "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" ] } -}]]> +} @@ -132,12 +124,6 @@ - - - - - - diff --git a/backend/Cargo.lock b/backend/Cargo.lock index a926c28..ff7d65d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -270,8 +270,10 @@ dependencies = [ "argon2", "chrono", "diesel", + "dotenvy", "env_logger", "log", + "r2d2", "serde", "serde_json", "utoipa", @@ -479,6 +481,7 @@ dependencies = [ "diesel_derives", "itoa", "pq-sys", + "r2d2", "uuid", ] @@ -525,6 +528,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -1087,6 +1096,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.8.5" @@ -1227,6 +1247,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "scopeguard" version = "1.1.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d88afee..637040a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" [dependencies] actix-web = "4" chrono = "0.4.24" -diesel = { version = "2.0.0", features = ["postgres","chrono","uuid"] } +diesel = { version = "2.0.0", features = ["postgres","chrono","uuid","r2d2"] } uuid = { version = "1.3.3", features = ["v4","fast-rng","macro-diagnostics"] } serde = { version="1.0.163", features = ["derive"] } validator = { version = "0.16.0", features=["derive"] } @@ -18,4 +18,6 @@ log = "0.4.17" serde_json = "1.0.96" argon2 = "0.5.0" utoipa = { version = "3", features = ["actix_extras", "uuid", "chrono"] } -utoipa-swagger-ui = { version = "3", features = ["actix-web"] } \ No newline at end of file +utoipa-swagger-ui = { version = "3", features = ["actix-web"] } +r2d2 = "0.8.10" +dotenvy = "0.15.7" diff --git a/backend/src/api/auth/controller.rs b/backend/src/api/auth/controller.rs index aebfda5..3c1c22e 100644 --- a/backend/src/api/auth/controller.rs +++ b/backend/src/api/auth/controller.rs @@ -3,14 +3,16 @@ use actix_web::*; use log; use validator::Validate; +/// Register a new user #[utoipa::path( responses( (status = 204, description="User registered successfully"), + (status = 400, description="Bad request") ), request_body = RegisterNewUser, tag="Auth" )] -#[post("/register")] +#[post("/user")] pub async fn register(user: web::Json) -> HttpResponse { log::trace!("Registering new user: {:?}", user); @@ -20,5 +22,6 @@ pub async fn register(user: web::Json) -> HttpResponse { } println!("Registering new user: {:?}", user); + HttpResponse::NoContent().finish() } diff --git a/backend/src/db/connection.rs b/backend/src/db/connection.rs index 2edaaa5..5c56fc9 100644 --- a/backend/src/db/connection.rs +++ b/backend/src/db/connection.rs @@ -1,8 +1,14 @@ -use diesel::{Connection, PgConnection}; +use diesel::PgConnection; +use r2d2; use std::env; -pub fn establish_connection() -> PgConnection { +pub(crate) type DbPool = r2d2::Pool>; + +pub fn establish_connection() -> DbPool { let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - PgConnection::establish(&database_url) - .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) + let manager = diesel::r2d2::ConnectionManager::::new(database_url); + + r2d2::Pool::builder() + .build(manager) + .expect("Failed to create pool.") } diff --git a/backend/src/main.rs b/backend/src/main.rs index b37a9e2..c6955d8 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,14 +1,22 @@ use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; use backend::api; +use backend::db; +use dotenvy::dotenv; use env_logger::Env; +use log::info; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; #[actix_web::main] async fn main() -> std::io::Result<()> { + dotenv().ok(); env_logger::init_from_env(Env::default().default_filter_or("info")); + db::connection::establish_connection(); + + info!("Starting server at http://localhost:8080"); + HttpServer::new(|| { App::new() .wrap(Logger::default()) From 3a7cb48af4ca83ebec7e88a7552259efb862bcc7 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 27 May 2023 06:55:15 +1200 Subject: [PATCH 08/73] add user creation err mgmt needs more work --- .idea/workspace.xml | 27 ++++++++++----------------- backend/src/api/auth/controller.rs | 12 ++++++++---- backend/src/api/auth/model.rs | 17 ++++++++++++----- backend/src/api/auth/service.rs | 28 ++++++++++++++++++++++++++++ backend/src/main.rs | 5 +++-- 5 files changed, 61 insertions(+), 28 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b70feb2..df0a034 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -9,10 +9,9 @@ - - - + + - + + + + + + + @@ -103,12 +108,6 @@ - - - - - - @@ -118,12 +117,6 @@ - - - - - - diff --git a/backend/src/api/auth/controller.rs b/backend/src/api/auth/controller.rs index 3c1c22e..80192cc 100644 --- a/backend/src/api/auth/controller.rs +++ b/backend/src/api/auth/controller.rs @@ -1,4 +1,5 @@ -use super::model::RegisterNewUser; +use super::{model, service}; +use crate::db::connection::DbPool; use actix_web::*; use log; use validator::Validate; @@ -13,15 +14,18 @@ use validator::Validate; tag="Auth" )] #[post("/user")] -pub async fn register(user: web::Json) -> HttpResponse { - log::trace!("Registering new user: {:?}", user); +pub async fn register( + pool: web::Data, + user: web::Json, +) -> HttpResponse { + log::trace!("Registering new user: {:?}", user.email); let val_result = user.validate(); if let Err(e) = val_result { return HttpResponse::BadRequest().json(e); } - println!("Registering new user: {:?}", user); + service::register_user(pool, user.into_inner()).await.ok(); HttpResponse::NoContent().finish() } diff --git a/backend/src/api/auth/model.rs b/backend/src/api/auth/model.rs index 960e201..2b2c486 100644 --- a/backend/src/api/auth/model.rs +++ b/backend/src/api/auth/model.rs @@ -2,14 +2,21 @@ use serde::Deserialize; use utoipa::ToSchema; use validator::Validate; -#[derive(Deserialize, Validate, ToSchema, Debug)] +#[derive(Deserialize, Validate, ToSchema)] pub struct RegisterNewUser { #[validate(email)] - email: String, + #[schema(example = "john.smith@example.com")] + pub email: String, + #[validate(length(min = 1))] - first_name: String, + #[schema(example = "John", min_length = 1)] + pub first_name: String, + #[validate(length(min = 1))] - last_name: String, + #[schema(example = "Smith", min_length = 1)] + pub last_name: String, + #[validate(length(min = 8, max = 64))] - password: String, + #[schema(example = "Password123", min_length = 8, max_length = 64)] + pub password: String, } diff --git a/backend/src/api/auth/service.rs b/backend/src/api/auth/service.rs index bcdd8e2..b00115c 100644 --- a/backend/src/api/auth/service.rs +++ b/backend/src/api/auth/service.rs @@ -1,7 +1,35 @@ +use super::model::RegisterNewUser; +use crate::db::connection::DbPool; +use crate::db::models::user::{create_user, NewUser}; +use actix_web::{error, web}; use argon2::password_hash::rand_core::OsRng; use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHasher}; +pub async fn register_user( + pool: web::Data, + user: RegisterNewUser, +) -> Result<(), error::Error> { + let password_hash = hash_password(&user.password).unwrap(); + + web::block(move || { + let mut conn = pool.get().expect("couldn't get db connection from pool"); + create_user( + &mut conn, + NewUser { + email: &user.email, + first_name: &user.first_name, + last_name: &user.last_name, + password: &password_hash, + }, + ) + }) + .await? + .map_err(error::ErrorInternalServerError)?; + + Ok(()) +} + pub fn hash_password(password: &str) -> Result { let password_bytes = password.as_bytes(); let salt = SaltString::generate(&mut OsRng); diff --git a/backend/src/main.rs b/backend/src/main.rs index c6955d8..d7e725e 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -13,13 +13,14 @@ async fn main() -> std::io::Result<()> { dotenv().ok(); env_logger::init_from_env(Env::default().default_filter_or("info")); - db::connection::establish_connection(); + let pool = db::connection::establish_connection(); info!("Starting server at http://localhost:8080"); - HttpServer::new(|| { + HttpServer::new(move || { App::new() .wrap(Logger::default()) + .app_data(web::Data::new(pool.clone())) .service(SwaggerUi::new("/swagger-ui/{_:.*}").url( "/v1/api-docs/openapi.json", api::api_docs::ApiDocs::openapi(), From d172b857d724d79e0386a9db668dd013a6ab6dad Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 27 May 2023 09:02:07 +1200 Subject: [PATCH 09/73] add watch config --- .idea/runConfigurations/Backend.xml | 2 +- .idea/runConfigurations/Backend_Watch.xml | 19 ++++++++++ .idea/workspace.xml | 28 +++++++++------ backend/Cargo.lock | 44 +++++++++++------------ backend/Cargo.toml | 3 +- backend/src/main.rs | 3 +- 6 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 .idea/runConfigurations/Backend_Watch.xml diff --git a/.idea/runConfigurations/Backend.xml b/.idea/runConfigurations/Backend.xml index 0955657..b7dbd63 100644 --- a/.idea/runConfigurations/Backend.xml +++ b/.idea/runConfigurations/Backend.xml @@ -2,7 +2,7 @@ - - - - - - - - - diff --git a/backend/src/handlers/auth.rs b/backend/src/handlers/auth.rs index f2fc404..df5d54e 100644 --- a/backend/src/handlers/auth.rs +++ b/backend/src/handlers/auth.rs @@ -5,6 +5,7 @@ use axum::{ routing::{get, post}, Json, Router, }; +use tracing::info; pub fn get_router() -> Router { Router::new() @@ -30,8 +31,8 @@ pub fn get_router() -> Router { (status = 409, description = "Conflict. User already exists."), ) )] -async fn register(Json(_payload): Json) -> StatusCode { - // info!("Registering new user", payload); +async fn register(Json(payload): Json) -> StatusCode { + info!(email = payload.email, "registering new user"); StatusCode::CREATED } From 5cb23034a95ddec89de2058a138bcd7e4b805664 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 13 Aug 2023 04:49:34 +1200 Subject: [PATCH 18/73] fallback to 403 --- .idea/workspace.xml | 49 +++------------------------------------------ backend/src/main.rs | 7 +++++++ 2 files changed, 10 insertions(+), 46 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 96aebed..06bc6e7 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -9,7 +9,7 @@ - + - - - - - - - - - - + - - - - - - - - - - - - - - - diff --git a/backend/src/main.rs b/backend/src/main.rs index 0cbffde..e2cbe38 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,8 +1,10 @@ mod api_docs; +mod error; mod handlers; mod models; mod services; +use axum::http::StatusCode; use axum::Router; use std::net::SocketAddr; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; @@ -30,6 +32,7 @@ async fn main() { .url("/api-docs/openapi.json", api_docs::ApiDocs::openapi()), ) .nest("/v1", handlers::get_router()) + .fallback(fallback) .layer( TraceLayer::new_for_http() // .make_span_with(DefaultMakeSpan::new().include_headers(true)) .on_request(DefaultOnRequest::new().level(Level::INFO)) @@ -44,3 +47,7 @@ async fn main() { .await .unwrap(); } + +async fn fallback() -> StatusCode { + StatusCode::FORBIDDEN +} From 5909f0d2af8d26d700b18c5c85ad0da1605ca19b Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 13 Aug 2023 14:59:50 +1200 Subject: [PATCH 19/73] add diesel --- .../Backend__Compose_Deployment.xml | 11 + .idea/workspace.xml | 68 +++-- backend/Cargo.lock | 275 +++++++++++++++++- backend/Cargo.toml | 7 +- .../down.sql | 6 + .../up.sql | 36 +++ .../2023-05-20-201029_create_user/up.sql | 2 +- .../up.sql | 4 +- .../2023-08-13-010504_uuid-id-format/down.sql | 8 + .../2023-08-13-010504_uuid-id-format/up.sql | 12 + backend/src/database.rs | 15 + backend/src/db/schema.rs | 30 ++ backend/src/error.rs | 3 + backend/src/main.rs | 20 +- 14 files changed, 460 insertions(+), 37 deletions(-) create mode 100644 .idea/runConfigurations/Backend__Compose_Deployment.xml create mode 100644 backend/migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 backend/migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 backend/migrations/2023-08-13-010504_uuid-id-format/down.sql create mode 100644 backend/migrations/2023-08-13-010504_uuid-id-format/up.sql create mode 100644 backend/src/db/schema.rs diff --git a/.idea/runConfigurations/Backend__Compose_Deployment.xml b/.idea/runConfigurations/Backend__Compose_Deployment.xml new file mode 100644 index 0000000..64ddc4e --- /dev/null +++ b/.idea/runConfigurations/Backend__Compose_Deployment.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 06bc6e7..78d4070 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -9,6 +9,12 @@ + + + + + + - { - "keyToString": { - "RunOnceActivity.OpenDatabaseViewOnStart": "true", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "git-widget-placeholder": "refactor", - "last_opened_file_path": "/home/jchad/Code/domus/backend/src", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_interpreter_path": "node", - "nodejs_package_manager_path": "npm", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", - "prettierjs.PrettierConfiguration.Package": "/home/jchad/Code/domus/web/node_modules/prettier", - "project.structure.last.edited": "Modules", - "project.structure.proportion": "0.15", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "language.rust.rustfmt", - "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -93,7 +99,7 @@ - + @@ -103,6 +109,7 @@ + @@ -155,7 +162,8 @@ - + + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 2943553..f894b2c 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -107,6 +107,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + [[package]] name = "bitflags" version = "1.3.2" @@ -189,6 +195,73 @@ dependencies = [ "typenum", ] +[[package]] +name = "deadpool" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +dependencies = [ + "async-trait", + "deadpool-runtime", + "num_cpus", + "retain_mut", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" + +[[package]] +name = "diesel" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a532c1f99a0f596f6960a60d1e119e91582b24b39e2d83a190e61262c3ef0c" +dependencies = [ + "bitflags 2.4.0", + "byteorder", + "diesel_derives", + "itoa", +] + +[[package]] +name = "diesel-async" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e7974099f0d9bde0e010dd3a673555276a474f3362a7a52ab535a57b7c5056" +dependencies = [ + "async-trait", + "deadpool", + "diesel", + "futures-util", + "scoped-futures", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "diesel_derives" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74398b79d81e52e130d991afeed9c86034bb1b7735f46d2f5bf7deb261d80303" +dependencies = [ + "diesel_table_macro_syntax", + "proc-macro2", + "quote", + "syn 2.0.27", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" +dependencies = [ + "syn 2.0.27", +] + [[package]] name = "digest" version = "0.10.7" @@ -197,6 +270,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -224,6 +298,9 @@ name = "domus" version = "0.1.0" dependencies = [ "axum", + "deadpool", + "diesel", + "diesel-async", "hyper", "serde", "serde_json", @@ -243,6 +320,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "flate2" version = "1.0.26" @@ -275,6 +358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -283,6 +367,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -302,9 +397,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", + "futures-macro", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -371,6 +469,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.9" @@ -428,7 +535,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -534,6 +641,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest", +] + [[package]] name = "memchr" version = "2.5.0" @@ -647,6 +763,24 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.2" @@ -679,6 +813,41 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "postgres-protocol" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b7fa9f396f51dffd61546fd8573ee20592287996568e6175ceb0f8699ad75d" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f028f05971fe20f512bcc679e2c10227e57809a3af86a7606304435bc8896cd6" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -721,6 +890,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -794,6 +993,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "retain_mut" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" + [[package]] name = "rust-embed" version = "6.8.1" @@ -856,6 +1061,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-futures" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" +dependencies = [ + "cfg-if", + "pin-utils", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -953,6 +1168,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.8" @@ -978,6 +1199,32 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "stringprep" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1066,7 +1313,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.4.9", "tokio-macros", "windows-sys 0.48.0", ] @@ -1082,6 +1329,30 @@ dependencies = [ "syn 2.0.27", ] +[[package]] +name = "tokio-postgres" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e89f6234aa8fd43779746012fcf53603cdb91fdd8399aa0de868c2d56b6dde1" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "socket2 0.5.3", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-util" version = "0.7.8" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d67e65c..5ed5410 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/j-chad/domus" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = { version = "0.6.19" } +axum = "0.6.19" hyper = { version = "0.14.27", features = ["full"] } tokio = { version = "1.29.1", features = ["full"] } tower = "0.4.13" @@ -21,3 +21,8 @@ utoipa = { version = "3", features = ["axum_extras", "uuid", "chrono"] } utoipa-swagger-ui = { version = "3", features = ["axum"] } validator = { version = "0.16.1", features = ["derive"] } tower-http = { version = "0.4.3", features = ["trace"] } +diesel = "2.1.0" +diesel-async = { version = "0.3.1", features = ["postgres", "deadpool"] } +deadpool = "0.9.5" +dotenvy = { version = "0.15" } + diff --git a/backend/migrations/00000000000000_diesel_initial_setup/down.sql b/backend/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/backend/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/backend/migrations/00000000000000_diesel_initial_setup/up.sql b/backend/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/backend/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/backend/migrations/2023-05-20-201029_create_user/up.sql b/backend/migrations/2023-05-20-201029_create_user/up.sql index 13d1ade..cef8d25 100644 --- a/backend/migrations/2023-05-20-201029_create_user/up.sql +++ b/backend/migrations/2023-05-20-201029_create_user/up.sql @@ -1,6 +1,6 @@ -- Your SQL goes here CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id TEXT PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT NOT NULL UNIQUE, first_name TEXT NOT NULL, last_name TEXT NOT NULL, diff --git a/backend/migrations/2023-06-10-234545_refresh_token_table/up.sql b/backend/migrations/2023-06-10-234545_refresh_token_table/up.sql index 3c9e149..197832e 100644 --- a/backend/migrations/2023-06-10-234545_refresh_token_table/up.sql +++ b/backend/migrations/2023-06-10-234545_refresh_token_table/up.sql @@ -1,7 +1,7 @@ -- Your SQL goes here CREATE TABLE refresh_tokens ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES users(id), + id TEXT PRIMARY KEY DEFAULT gen_random_uuid(), + user_id TEXT NOT NULL REFERENCES users(id), expires_at TIMESTAMP NOT NULL DEFAULT NOW() + INTERVAL '1 week', created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP, diff --git a/backend/migrations/2023-08-13-010504_uuid-id-format/down.sql b/backend/migrations/2023-08-13-010504_uuid-id-format/down.sql new file mode 100644 index 0000000..19efbb5 --- /dev/null +++ b/backend/migrations/2023-08-13-010504_uuid-id-format/down.sql @@ -0,0 +1,8 @@ +-- This file should undo anything in `up.sql` +DROP FUNCTION gen_random_app_id(column_id varchar(5)); + +ALTER TABLE users + ALTER COLUMN id SET DEFAULT gen_random_uuid(); + +ALTER TABLE refresh_tokens + ALTER COLUMN id SET DEFAULT gen_random_uuid(); diff --git a/backend/migrations/2023-08-13-010504_uuid-id-format/up.sql b/backend/migrations/2023-08-13-010504_uuid-id-format/up.sql new file mode 100644 index 0000000..535bccf --- /dev/null +++ b/backend/migrations/2023-08-13-010504_uuid-id-format/up.sql @@ -0,0 +1,12 @@ +-- Your SQL goes here +CREATE FUNCTION gen_random_app_id(column_id varchar(5)) RETURNS TEXT AS $$ +BEGIN + RETURN column_id || '|' || gen_random_uuid(); +END; +$$ LANGUAGE plpgsql; + +ALTER TABLE users + ALTER COLUMN id SET DEFAULT gen_random_app_id('user'); + +ALTER TABLE refresh_tokens + ALTER COLUMN id SET DEFAULT gen_random_app_id('rtokn'); diff --git a/backend/src/database.rs b/backend/src/database.rs index e69de29..01ca899 100644 --- a/backend/src/database.rs +++ b/backend/src/database.rs @@ -0,0 +1,15 @@ +use deadpool::managed::Pool; +use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::AsyncPgConnection; +use std::env; + +pub type ConnectionPool = Pool>; + +pub fn get_connection_pool() -> ConnectionPool { + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let config = AsyncDieselConnectionManager::::new(database_url); + + Pool::builder(config) + .build() + .expect("Failed to create database pool.") +} diff --git a/backend/src/db/schema.rs b/backend/src/db/schema.rs new file mode 100644 index 0000000..3edd190 --- /dev/null +++ b/backend/src/db/schema.rs @@ -0,0 +1,30 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + refresh_tokens (id) { + id -> Text, + user_id -> Text, + expires_at -> Timestamp, + created_at -> Timestamp, + updated_at -> Nullable, + } +} + +diesel::table! { + users (id) { + id -> Text, + email -> Text, + first_name -> Text, + last_name -> Text, + password -> Text, + created_at -> Timestamp, + updated_at -> Nullable, + } +} + +diesel::joinable!(refresh_tokens -> users (user_id)); + +diesel::allow_tables_to_appear_in_same_query!( + refresh_tokens, + users, +); diff --git a/backend/src/error.rs b/backend/src/error.rs index e69de29..5a7c818 100644 --- a/backend/src/error.rs +++ b/backend/src/error.rs @@ -0,0 +1,3 @@ +// pub enum AppError { +// ValidationError { field: String, message: String }, +// } diff --git a/backend/src/main.rs b/backend/src/main.rs index e2cbe38..40cbee5 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ mod api_docs; +mod database; mod error; mod handlers; mod models; @@ -6,7 +7,10 @@ mod services; use axum::http::StatusCode; use axum::Router; +use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::AsyncPgConnection; use std::net::SocketAddr; +use std::sync::Arc; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; use tower_http::LatencyUnit; use tracing::Level; @@ -15,6 +19,19 @@ use tracing_subscriber::util::SubscriberInitExt; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; +#[derive(Clone)] +struct AppState { + pub database_pool: database::ConnectionPool, +} + +impl AppState { + fn new() -> Self { + let database_pool = database::get_connection_pool(); + + Self { database_pool } + } +} + #[tokio::main] async fn main() { // initialize tracing @@ -27,6 +44,7 @@ async fn main() { .init(); let app = Router::new() + .with_state(Arc::new(AppState::new())) .merge( SwaggerUi::new("/swagger-ui") .url("/api-docs/openapi.json", api_docs::ApiDocs::openapi()), @@ -49,5 +67,5 @@ async fn main() { } async fn fallback() -> StatusCode { - StatusCode::FORBIDDEN + StatusCode::UNAUTHORIZED } From b9f7705c15b592406109e6dee6e653e27f3a679e Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 19 Aug 2023 19:03:10 +1200 Subject: [PATCH 20/73] get it working --- .idea/workspace.xml | 70 +++++++------- backend/Cargo.lock | 176 ++++++++++++++++++++++++++++++++++- backend/Cargo.toml | 3 +- backend/src/db/mod.rs | 2 + backend/src/db/models.rs | 14 +++ backend/src/handlers/auth.rs | 37 +++++++- backend/src/handlers/mod.rs | 4 +- backend/src/main.rs | 11 ++- backend/src/models/auth.rs | 27 +++++- 9 files changed, 295 insertions(+), 49 deletions(-) create mode 100644 backend/src/db/mod.rs create mode 100644 backend/src/db/models.rs diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 78d4070..50b8e2d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,14 +8,15 @@ + + - - - - + + + - { + "keyToString": { + "RunOnceActivity.OpenDatabaseViewOnStart": "true", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "refactor", + "last_opened_file_path": "/home/jchad/Code/domus/backend/src", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "node", + "nodejs_package_manager_path": "npm", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", + "prettierjs.PrettierConfiguration.Package": "/home/jchad/Code/domus/web/node_modules/prettier", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "editor.preferences.import", + "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" ] } -}]]> +} @@ -99,7 +100,7 @@ - + @@ -163,7 +164,8 @@ - + + diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f894b2c..dd251af 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[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]] name = "async-trait" version = "0.1.72" @@ -134,6 +149,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + [[package]] name = "byteorder" version = "1.4.3" @@ -158,6 +179,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.9" @@ -222,6 +264,7 @@ checksum = "f7a532c1f99a0f596f6960a60d1e119e91582b24b39e2d83a190e61262c3ef0c" dependencies = [ "bitflags 2.4.0", "byteorder", + "chrono", "diesel_derives", "itoa", ] @@ -298,9 +341,11 @@ name = "domus" version = "0.1.0" dependencies = [ "axum", + "chrono", "deadpool", "diesel", "diesel-async", + "dotenvy", "hyper", "serde", "serde_json", @@ -314,6 +359,12 @@ dependencies = [ "validator", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "equivalent" version = "1.0.1" @@ -423,7 +474,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -542,6 +593,29 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[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]] name = "idna" version = "0.3.0" @@ -595,6 +669,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -689,7 +772,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] @@ -703,6 +786,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -1283,6 +1375,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1667,12 +1770,72 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.27", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + [[package]] name = "winapi" version = "0.3.9" @@ -1704,6 +1867,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 5ed5410..47f51d3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -21,8 +21,9 @@ utoipa = { version = "3", features = ["axum_extras", "uuid", "chrono"] } utoipa-swagger-ui = { version = "3", features = ["axum"] } validator = { version = "0.16.1", features = ["derive"] } tower-http = { version = "0.4.3", features = ["trace"] } -diesel = "2.1.0" +diesel = { version = "2.1.0", features = ["chrono"] } diesel-async = { version = "0.3.1", features = ["postgres", "deadpool"] } deadpool = "0.9.5" dotenvy = { version = "0.15" } +chrono = "0.4.26" diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs new file mode 100644 index 0000000..d5cbad7 --- /dev/null +++ b/backend/src/db/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod schema; diff --git a/backend/src/db/models.rs b/backend/src/db/models.rs new file mode 100644 index 0000000..c852599 --- /dev/null +++ b/backend/src/db/models.rs @@ -0,0 +1,14 @@ +use diesel::{Queryable, Selectable}; + +#[derive(Queryable, Selectable)] +#[diesel(table_name = crate::db::schema::users)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct User { + pub id: String, + pub email: String, + pub password: String, + pub first_name: String, + pub last_name: String, + pub created_at: chrono::NaiveDateTime, + pub updated_at: Option, +} diff --git a/backend/src/handlers/auth.rs b/backend/src/handlers/auth.rs index df5d54e..affb7d9 100644 --- a/backend/src/handlers/auth.rs +++ b/backend/src/handlers/auth.rs @@ -1,13 +1,21 @@ -use crate::models::auth::RegisterNewUserRequest; +use crate::db::models::User; +use crate::db::schema::users; +use crate::models::auth::{RegisterNewUserRequest, UserResponse}; +use axum::extract::State; use axum::{ http::StatusCode, response::IntoResponse, routing::{get, post}, Json, Router, }; +use std::sync::Arc; + +use crate::AppState; +use diesel::SelectableHelper; +use diesel_async::RunQueryDsl; use tracing::info; -pub fn get_router() -> Router { +pub fn get_router() -> Router> { Router::new() .route("/register", post(register)) .route("/login", post(login)) @@ -31,9 +39,30 @@ pub fn get_router() -> Router { (status = 409, description = "Conflict. User already exists."), ) )] -async fn register(Json(payload): Json) -> StatusCode { +async fn register( + State(pool): State>, + Json(payload): Json, +) -> Result<(StatusCode, Json), StatusCode> { info!(email = payload.email, "registering new user"); - StatusCode::CREATED + + let mut conn = pool + .database_pool + .get() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let new_user: UserResponse = diesel::insert_into(users::table) + .values(&payload) + .returning(User::as_returning()) + .get_result(&mut conn) + .await + .map_err(|e| { + info!(email = payload.email, "failed to register new user: {}", e); + StatusCode::CONFLICT + })? + .into(); + + Ok((StatusCode::CREATED, Json(new_user))) } /// Login with an existing users credentials diff --git a/backend/src/handlers/mod.rs b/backend/src/handlers/mod.rs index 28d54a5..9e85e7a 100644 --- a/backend/src/handlers/mod.rs +++ b/backend/src/handlers/mod.rs @@ -1,7 +1,9 @@ +use std::sync::Arc; +use crate::AppState; use axum::Router; pub mod auth; -pub fn get_router() -> Router { +pub fn get_router() -> Router> { Router::new().nest("/auth", auth::get_router()) } diff --git a/backend/src/main.rs b/backend/src/main.rs index 40cbee5..5c919a9 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,5 +1,6 @@ mod api_docs; mod database; +mod db; mod error; mod handlers; mod models; @@ -7,8 +8,6 @@ mod services; use axum::http::StatusCode; use axum::Router; -use diesel_async::pooled_connection::AsyncDieselConnectionManager; -use diesel_async::AsyncPgConnection; use std::net::SocketAddr; use std::sync::Arc; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; @@ -20,7 +19,7 @@ use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; #[derive(Clone)] -struct AppState { +pub struct AppState { pub database_pool: database::ConnectionPool, } @@ -34,6 +33,8 @@ impl AppState { #[tokio::main] async fn main() { + dotenvy::dotenv().unwrap(); + // initialize tracing tracing_subscriber::registry() .with( @@ -44,7 +45,6 @@ async fn main() { .init(); let app = Router::new() - .with_state(Arc::new(AppState::new())) .merge( SwaggerUi::new("/swagger-ui") .url("/api-docs/openapi.json", api_docs::ApiDocs::openapi()), @@ -55,7 +55,8 @@ async fn main() { TraceLayer::new_for_http() // .make_span_with(DefaultMakeSpan::new().include_headers(true)) .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().latency_unit(LatencyUnit::Millis)), - ); + ) + .with_state(Arc::new(AppState::new())); // run our app with hyper let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); diff --git a/backend/src/models/auth.rs b/backend/src/models/auth.rs index 4cc9d1a..fdb0639 100644 --- a/backend/src/models/auth.rs +++ b/backend/src/models/auth.rs @@ -1,9 +1,13 @@ -use serde::Deserialize; +use crate::db::models::User; +use crate::db::schema::users; +use diesel::prelude::*; +use serde::{Deserialize, Serialize}; use std::fmt; use utoipa::ToSchema; use validator::Validate; -#[derive(Deserialize, Validate, ToSchema)] +#[derive(Deserialize, Validate, ToSchema, Insertable)] +#[diesel(table_name = users)] pub struct RegisterNewUserRequest { #[validate(email)] #[schema(example = "john.smith@example.com", format = "email")] @@ -66,3 +70,22 @@ impl fmt::Debug for RefreshTokenRequest { .finish() } } + +#[derive(Serialize, ToSchema)] +pub struct UserResponse { + pub id: String, + pub email: String, + pub first_name: String, + pub last_name: String, +} + +impl From for UserResponse { + fn from(user: User) -> Self { + Self { + id: user.id, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + } + } +} From 83ea10821830436fac1fba2f5ef36c4acae51cf4 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 20 Aug 2023 05:59:23 +1200 Subject: [PATCH 21/73] move things around --- .idea/workspace.xml | 76 +++++++++---------- .../auth.rs => api/auth/controllers.rs} | 40 ++++------ backend/src/api/auth/mod.rs | 17 +++++ .../{models/auth.rs => api/auth/models.rs} | 16 +++- backend/src/{handlers => api}/mod.rs | 2 +- backend/src/db/models.rs | 11 ++- backend/src/main.rs | 2 +- 7 files changed, 94 insertions(+), 70 deletions(-) rename backend/src/{handlers/auth.rs => api/auth/controllers.rs} (71%) create mode 100644 backend/src/api/auth/mod.rs rename backend/src/{models/auth.rs => api/auth/models.rs} (86%) rename backend/src/{handlers => api}/mod.rs (100%) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 50b8e2d..48b55e7 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,15 +8,13 @@ - - + - - - - + + + - + - { - "keyToString": { - "RunOnceActivity.OpenDatabaseViewOnStart": "true", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "git-widget-placeholder": "refactor", - "last_opened_file_path": "/home/jchad/Code/domus/backend/src", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_interpreter_path": "node", - "nodejs_package_manager_path": "npm", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", - "prettierjs.PrettierConfiguration.Package": "/home/jchad/Code/domus/web/node_modules/prettier", - "project.structure.last.edited": "Modules", - "project.structure.proportion": "0.15", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "editor.preferences.import", - "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", - "vue.rearranger.settings.migration": "true" + +}]]> + - + + + - @@ -165,7 +165,7 @@ - + diff --git a/backend/src/handlers/auth.rs b/backend/src/api/auth/controllers.rs similarity index 71% rename from backend/src/handlers/auth.rs rename to backend/src/api/auth/controllers.rs index affb7d9..588fe43 100644 --- a/backend/src/handlers/auth.rs +++ b/backend/src/api/auth/controllers.rs @@ -1,13 +1,8 @@ -use crate::db::models::User; +use super::models::{RegisterNewUserRequest, UserResponse}; +use crate::db::models::{NewUser, User}; use crate::db::schema::users; -use crate::models::auth::{RegisterNewUserRequest, UserResponse}; use axum::extract::State; -use axum::{ - http::StatusCode, - response::IntoResponse, - routing::{get, post}, - Json, Router, -}; +use axum::{http::StatusCode, response::IntoResponse, Json}; use std::sync::Arc; use crate::AppState; @@ -15,15 +10,6 @@ use diesel::SelectableHelper; use diesel_async::RunQueryDsl; use tracing::info; -pub fn get_router() -> Router> { - Router::new() - .route("/register", post(register)) - .route("/login", post(login)) - .route("/logout", post(logout)) - .route("/refresh_token", post(refresh_token)) - .route("/user", get(get_user)) -} - /// Register a new user #[utoipa::path( post, @@ -39,7 +25,7 @@ pub fn get_router() -> Router> { (status = 409, description = "Conflict. User already exists."), ) )] -async fn register( +pub async fn register( State(pool): State>, Json(payload): Json, ) -> Result<(StatusCode, Json), StatusCode> { @@ -51,18 +37,20 @@ async fn register( .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - let new_user: UserResponse = diesel::insert_into(users::table) - .values(&payload) + let new_user = NewUser::from(payload); + + let new_user_response: UserResponse = diesel::insert_into(users::table) + .values(&new_user) .returning(User::as_returning()) .get_result(&mut conn) .await .map_err(|e| { - info!(email = payload.email, "failed to register new user: {}", e); + info!(email = new_user.email, "failed to register new user: {}", e); StatusCode::CONFLICT })? .into(); - Ok((StatusCode::CREATED, Json(new_user))) + Ok((StatusCode::CREATED, Json(new_user_response))) } /// Login with an existing users credentials @@ -78,7 +66,7 @@ async fn register( (status = 501, description = "Not Implemented") ) )] -async fn login() -> impl IntoResponse { +pub async fn login() -> impl IntoResponse { StatusCode::NOT_IMPLEMENTED } @@ -91,7 +79,7 @@ async fn login() -> impl IntoResponse { (status = 501, description = "Not Implemented") ) )] -async fn logout() -> impl IntoResponse { +pub async fn logout() -> impl IntoResponse { StatusCode::NOT_IMPLEMENTED } @@ -110,7 +98,7 @@ async fn logout() -> impl IntoResponse { (status = 501, description = "Not Implemented") ) )] -async fn refresh_token() -> impl IntoResponse { +pub async fn refresh_token() -> impl IntoResponse { StatusCode::NOT_IMPLEMENTED } @@ -123,6 +111,6 @@ async fn refresh_token() -> impl IntoResponse { (status = 501, description = "Not Implemented") ) )] -async fn get_user() -> impl IntoResponse { +pub async fn get_user() -> impl IntoResponse { StatusCode::NOT_IMPLEMENTED } diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs new file mode 100644 index 0000000..5468a9d --- /dev/null +++ b/backend/src/api/auth/mod.rs @@ -0,0 +1,17 @@ +use crate::AppState; +use axum::routing::{get, post}; +use axum::Router; +use controllers::{get_user, login, logout, refresh_token, register}; +use std::sync::Arc; + +pub mod controllers; +mod models; + +pub fn get_router() -> Router> { + Router::new() + .route("/register", post(register)) + .route("/login", post(login)) + .route("/logout", post(logout)) + .route("/refresh_token", post(refresh_token)) + .route("/user", get(get_user)) +} diff --git a/backend/src/models/auth.rs b/backend/src/api/auth/models.rs similarity index 86% rename from backend/src/models/auth.rs rename to backend/src/api/auth/models.rs index fdb0639..ca8ef29 100644 --- a/backend/src/models/auth.rs +++ b/backend/src/api/auth/models.rs @@ -1,4 +1,4 @@ -use crate::db::models::User; +use crate::db::models::{NewUser, User}; use crate::db::schema::users; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -6,8 +6,7 @@ use std::fmt; use utoipa::ToSchema; use validator::Validate; -#[derive(Deserialize, Validate, ToSchema, Insertable)] -#[diesel(table_name = users)] +#[derive(Deserialize, Validate, ToSchema)] pub struct RegisterNewUserRequest { #[validate(email)] #[schema(example = "john.smith@example.com", format = "email")] @@ -37,6 +36,17 @@ impl fmt::Debug for RegisterNewUserRequest { } } +impl From for NewUser { + fn from(request: RegisterNewUserRequest) -> Self { + Self { + email: request.email, + first_name: request.first_name, + last_name: request.last_name, + password: request.password, + } + } +} + #[derive(Deserialize, Validate, ToSchema)] pub struct LoginUserRequest { #[validate(email)] diff --git a/backend/src/handlers/mod.rs b/backend/src/api/mod.rs similarity index 100% rename from backend/src/handlers/mod.rs rename to backend/src/api/mod.rs index 9e85e7a..3400d3e 100644 --- a/backend/src/handlers/mod.rs +++ b/backend/src/api/mod.rs @@ -1,6 +1,6 @@ -use std::sync::Arc; use crate::AppState; use axum::Router; +use std::sync::Arc; pub mod auth; diff --git a/backend/src/db/models.rs b/backend/src/db/models.rs index c852599..874c33e 100644 --- a/backend/src/db/models.rs +++ b/backend/src/db/models.rs @@ -1,4 +1,4 @@ -use diesel::{Queryable, Selectable}; +use diesel::{Insertable, Queryable, Selectable}; #[derive(Queryable, Selectable)] #[diesel(table_name = crate::db::schema::users)] @@ -12,3 +12,12 @@ pub struct User { pub created_at: chrono::NaiveDateTime, pub updated_at: Option, } + +#[derive(Insertable)] +#[diesel(table_name = crate::db::schema::users)] +pub struct NewUser { + pub email: String, + pub first_name: String, + pub last_name: String, + pub password: String, +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 5c919a9..bb94cd9 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,8 +1,8 @@ +mod api; mod api_docs; mod database; mod db; mod error; -mod handlers; mod models; mod services; From 7698ccff229badc586c4fa25512fb0a169051f84 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 20 Aug 2023 06:03:58 +1200 Subject: [PATCH 22/73] add gitignore --- backend/.gitignore | 208 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 1 deletion(-) diff --git a/backend/.gitignore b/backend/.gitignore index 1de5659..429d5dd 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1 +1,207 @@ -target \ No newline at end of file +# Created by https://www.toptal.com/developers/gitignore/api/rust,jetbrains,linux,macos,windows +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,jetbrains,linux,macos,windows + +### JetBrains ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### JetBrains Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,jetbrains,linux,macos,windows From 4b7226d55818fd030bb4e7444469b369f52e50f1 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 20 Aug 2023 06:04:12 +1200 Subject: [PATCH 23/73] use AppState type --- backend/src/api/auth/controllers.rs | 2 +- backend/src/api/auth/mod.rs | 3 +-- backend/src/api/mod.rs | 3 +-- backend/src/main.rs | 8 +++++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index 588fe43..edb20b6 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -26,7 +26,7 @@ use tracing::info; ) )] pub async fn register( - State(pool): State>, + State(pool): State, Json(payload): Json, ) -> Result<(StatusCode, Json), StatusCode> { info!(email = payload.email, "registering new user"); diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs index 5468a9d..a6e55d9 100644 --- a/backend/src/api/auth/mod.rs +++ b/backend/src/api/auth/mod.rs @@ -2,12 +2,11 @@ use crate::AppState; use axum::routing::{get, post}; use axum::Router; use controllers::{get_user, login, logout, refresh_token, register}; -use std::sync::Arc; pub mod controllers; mod models; -pub fn get_router() -> Router> { +pub fn get_router() -> Router { Router::new() .route("/register", post(register)) .route("/login", post(login)) diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 3400d3e..3b4b824 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,9 +1,8 @@ use crate::AppState; use axum::Router; -use std::sync::Arc; pub mod auth; -pub fn get_router() -> Router> { +pub fn get_router() -> Router { Router::new().nest("/auth", auth::get_router()) } diff --git a/backend/src/main.rs b/backend/src/main.rs index bb94cd9..2de11a3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -19,11 +19,11 @@ use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; #[derive(Clone)] -pub struct AppState { +pub struct AppStateInternal { pub database_pool: database::ConnectionPool, } -impl AppState { +impl AppStateInternal { fn new() -> Self { let database_pool = database::get_connection_pool(); @@ -31,6 +31,8 @@ impl AppState { } } +pub type AppState = Arc; + #[tokio::main] async fn main() { dotenvy::dotenv().unwrap(); @@ -56,7 +58,7 @@ async fn main() { .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().latency_unit(LatencyUnit::Millis)), ) - .with_state(Arc::new(AppState::new())); + .with_state(Arc::new(AppStateInternal::new())); // run our app with hyper let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); From 03b420f1fbf0c65ee8ebc9c117014154a27cf5c5 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 20 Aug 2023 06:05:07 +1200 Subject: [PATCH 24/73] Remove ignored files --- .idea/workspace.xml | 66 ++++++++++++++++++++++----------------------- backend/.gitignore | 4 --- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 48b55e7..a9eec7d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,13 +8,12 @@ - - - - + + + + - - { + "keyToString": { + "RunOnceActivity.OpenDatabaseViewOnStart": "true", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "refactor", + "last_opened_file_path": "/home/jchad/Code/domus/backend/src/api/auth", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "node", + "nodejs_package_manager_path": "npm", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", + "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", + "prettierjs.PrettierConfiguration.Package": "/home/jchad/Code/domus/web/node_modules/prettier", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "editor.preferences.import", + "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" ] } -}]]> +} @@ -166,6 +165,7 @@ + diff --git a/backend/.gitignore b/backend/.gitignore index 429d5dd..378e620 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -168,10 +168,6 @@ Temporary Items debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk From 989e5205e0ac303d1a2f9df7c87820ee6aaa5e03 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 20 Aug 2023 06:11:28 +1200 Subject: [PATCH 25/73] remove other ignored file --- .idea/workspace.xml | 179 -------------------------------------------- 1 file changed, 179 deletions(-) delete mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index a9eec7d..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "customColor": "", - "associatedIndex": 4 -} - - - - - { - "keyToString": { - "RunOnceActivity.OpenDatabaseViewOnStart": "true", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "git-widget-placeholder": "refactor", - "last_opened_file_path": "/home/jchad/Code/domus/backend/src/api/auth", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_interpreter_path": "node", - "nodejs_package_manager_path": "npm", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/api/shared/APIResponse.rs": "true", - "org.rust.disableDetachedFileInspection/home/jchad/Code/domus/backend/src/main.rs": "true", - "prettierjs.PrettierConfiguration.Package": "/home/jchad/Code/domus/web/node_modules/prettier", - "project.structure.last.edited": "Modules", - "project.structure.proportion": "0.15", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "editor.preferences.import", - "ts.external.directory.path": "/home/jchad/Code/domus/web/node_modules/typescript/lib", - "vue.rearranger.settings.migration": "true" - }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" - ] - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1684545689656 - - - - - - - - + + +
+ + +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+ Not registered? + Create an account + +
+ + + + + + From d11e4d2a57b344267e1dcbe90674e226f6f656b0 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Tue, 12 Sep 2023 12:50:49 +1200 Subject: [PATCH 43/73] add register and error page --- web/src/app.html | 6 +- web/src/routes/+error.svelte | 38 ++++++++++++ web/src/routes/+page.svelte | 25 +++++--- web/src/routes/auth/+layout.svelte | 10 +++ web/src/routes/auth/login/+page.svelte | 59 ++++++++++++++++++ web/src/routes/auth/register/+page.svelte | 68 ++++++++++++++++++++ web/src/routes/login/+page.svelte | 76 ----------------------- web/tailwind.config.ts | 2 +- 8 files changed, 196 insertions(+), 88 deletions(-) create mode 100644 web/src/routes/+error.svelte create mode 100644 web/src/routes/auth/+layout.svelte create mode 100644 web/src/routes/auth/login/+page.svelte create mode 100644 web/src/routes/auth/register/+page.svelte delete mode 100644 web/src/routes/login/+page.svelte diff --git a/web/src/app.html b/web/src/app.html index 01284d9..900f6a3 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@ %sveltekit.head% - -
%sveltekit.body%
+ +
%sveltekit.body%
diff --git a/web/src/routes/+error.svelte b/web/src/routes/+error.svelte new file mode 100644 index 0000000..18119ff --- /dev/null +++ b/web/src/routes/+error.svelte @@ -0,0 +1,38 @@ + + +
+
+

{status}

+

{details.title}

+

+ {details.description} +

+ +
+
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 4b2cb1d..c21c9fb 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,12 +1,21 @@ + const THEMES = ['light', 'dark', 'valentine']; + + function changeTheme() { + // theme is changed via data-theme attribute on the html tag + const html = document.querySelector('html'); + if (!html) throw new Error('html tag not found'); -

Welcome to SvelteKit

-

Visita kit.svelte.dev to read the documentation

+ const currentTheme = html.getAttribute('data-theme'); + const nextTheme = THEMES[(THEMES.indexOf(currentTheme ?? 'light') + 1) % THEMES.length]; -
- Blog - About -
+ if (!nextTheme) throw new Error('next theme not found'); + + html.setAttribute('data-theme', nextTheme); + } + - +Login +Register +404 + diff --git a/web/src/routes/auth/+layout.svelte b/web/src/routes/auth/+layout.svelte new file mode 100644 index 0000000..1bdb740 --- /dev/null +++ b/web/src/routes/auth/+layout.svelte @@ -0,0 +1,10 @@ +
+ +
+
+ +
+
+
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte new file mode 100644 index 0000000..af3fd20 --- /dev/null +++ b/web/src/routes/auth/login/+page.svelte @@ -0,0 +1,59 @@ +
+ Domus +

Sign in.

+
+ +
+
+
+ + +
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+ Not registered? + Create an account + +
+
+
diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte new file mode 100644 index 0000000..bcaaa2b --- /dev/null +++ b/web/src/routes/auth/register/+page.svelte @@ -0,0 +1,68 @@ +
+ Domus +

Create an account.

+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ Already have an account? + Sign in + +
+
+
diff --git a/web/src/routes/login/+page.svelte b/web/src/routes/login/+page.svelte deleted file mode 100644 index 63dca7f..0000000 --- a/web/src/routes/login/+page.svelte +++ /dev/null @@ -1,76 +0,0 @@ -
-
- -
-
-
- Domus -

Sign in.

-
- -
-
-
- - -
- -
- - -
- -
-
- -
- - -
- -
- -
- -
- Not registered? - Create an account - -
-
-
-
-
-
-
diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts index 0194764..ce6d87a 100644 --- a/web/tailwind.config.ts +++ b/web/tailwind.config.ts @@ -2,7 +2,7 @@ import type { Config } from 'tailwindcss'; import daisyui from 'daisyui'; export default { - content: ['./src/routes/**/*.{svelte,js,ts}'], + content: ['./src/routes/**/*.{svelte,js,ts}', './src/app.html'], theme: { extend: {} }, From f853306979fb440e26c47445c3d0bd8f2879f1a3 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 23 Sep 2023 18:08:42 +1200 Subject: [PATCH 44/73] add function to generate auth token --- backend/Cargo.lock | 101 +++++++++++++++++++++++++--- backend/Cargo.toml | 3 +- backend/src/api/auth/controllers.rs | 10 ++- backend/src/api/auth/utils.rs | 19 ++++++ 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 1d37832..5bc61b5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -64,12 +64,6 @@ dependencies = [ "syn 2.0.27", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - [[package]] name = "autocfg" version = "1.1.0" @@ -228,7 +222,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", - "time", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -296,6 +290,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "deadpool" version = "0.9.5" @@ -315,6 +315,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "diesel" version = "2.1.0" @@ -410,6 +416,7 @@ dependencies = [ "diesel-async", "dotenvy", "hyper", + "pasetors", "serde", "serde_json", "tokio", @@ -429,6 +436,15 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ed25519-compact" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" +dependencies = [ + "getrandom", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -441,6 +457,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "flate2" version = "1.0.26" @@ -537,8 +559,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -884,6 +908,17 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "orion" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11468cc6afd61a126fe3f91cc4cc8a0dbe7917d0a4b5e8357ba91cc47444462" +dependencies = [ + "fiat-crypto", + "subtle", + "zeroize", +] + [[package]] name = "overload" version = "0.1.1" @@ -913,6 +948,23 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "pasetors" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba765699a309908d55950919a3445e9491453e89b2587b1b2abe4143a48894c0" +dependencies = [ + "ct-codecs", + "ed25519-compact", + "getrandom", + "orion", + "regex", + "serde_json", + "subtle", + "time 0.3.25", + "zeroize", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -1461,6 +1513,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1775,7 +1855,6 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ - "atomic", "getrandom", "serde", ] @@ -2091,6 +2170,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + [[package]] name = "zip" version = "0.6.6" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index a965a25..4a763e8 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -22,7 +22,7 @@ utoipa-swagger-ui = { version = "3", features = ["axum"] } validator = { version = "0.16.1", features = ["derive"] } tower-http = { version = "0.4.3", features = ["trace"] } diesel = { version = "2.1.0", features = ["chrono", "uuid"] } -uuid = { version = "1.4.1", features = ["v4", "v7"] } +uuid = { version = "1.4.1", features = ["v4"] } diesel-async = { version = "0.3.1", features = ["postgres", "deadpool"] } deadpool = "0.9.5" dotenvy = { version = "0.15" } @@ -30,4 +30,5 @@ chrono = "0.4.26" base62 = "2.0.2" const_format = "0.2.31" argon2 = "0.5.1" +pasetors = "0.6.7" diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index 711d9e2..ecbbb7c 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -1,6 +1,6 @@ use super::models::{RegisterNewUserRequest, UserResponse}; use crate::api::auth::models::{AuthResponse, LoginUserRequest}; -use crate::api::auth::utils::hash_password; +use crate::api::auth::utils::{generate_auth_token, hash_password}; use crate::api::error::ErrorType::{Unknown, UserAlreadyExists}; use crate::api::error::{APIError, APIErrorBuilder}; use crate::api::utils::db::get_db_connection; @@ -13,6 +13,7 @@ use diesel::prelude::*; use diesel::SelectableHelper; use diesel_async::RunQueryDsl; use tracing::{error, info, warn}; +use uuid::Uuid; /// Register a new user #[utoipa::path( @@ -99,8 +100,11 @@ pub async fn login( Ok(( StatusCode::OK, Json(AuthResponse { - access_token: "test".to_string(), - refresh_token: "test".to_string(), + access_token: generate_auth_token(&_user).map_err(|e| { + error!(error = %e, "failed to generate auth token"); + APIErrorBuilder::error(Unknown).build() + })?, + refresh_token: Uuid::new_v4().to_string(), }), )) } diff --git a/backend/src/api/auth/utils.rs b/backend/src/api/auth/utils.rs index 2d063ac..e09d948 100644 --- a/backend/src/api/auth/utils.rs +++ b/backend/src/api/auth/utils.rs @@ -1,6 +1,13 @@ +use crate::db::user::User; use argon2::password_hash::rand_core::OsRng; use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHasher, PasswordVerifier}; +use pasetors::claims::Claims; +use pasetors::errors::Error as ClaimError; +use pasetors::keys::{AsymmetricKeyPair, Generate}; +use pasetors::public; +use pasetors::version4::V4; +use uuid::Uuid; pub fn verify_password(password: &str, hash: &str) -> Result<(), argon2::password_hash::Error> { let argon2 = Argon2::default(); @@ -18,3 +25,15 @@ pub fn hash_password(password: &str) -> Result Result { + let mut claims = Claims::new()?; + claims.issuer("domus-api.jacksonc.dev")?; + claims.subject(user.id.as_hyphenated().to_string().as_str())?; + claims.audience("domus.jacksonc.dev")?; + claims.token_identifier(Uuid::new_v4().to_string().as_str())?; + + // Generate the keys and sign the claims. + let kp = AsymmetricKeyPair::::generate()?; + public::sign(&kp.secret, &claims, None, None) +} From fe9966100be030785d0fa16fac646f874ead5f9b Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 23 Sep 2023 18:41:01 +1200 Subject: [PATCH 45/73] finish login route --- backend/src/api/api_docs.rs | 1 + backend/src/api/auth/controllers.rs | 35 +++++++++++++++++++++-------- backend/src/api/auth/models.rs | 1 + backend/src/api/error.rs | 15 ++++++++++++- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/backend/src/api/api_docs.rs b/backend/src/api/api_docs.rs index 4c33e02..ad74690 100644 --- a/backend/src/api/api_docs.rs +++ b/backend/src/api/api_docs.rs @@ -21,6 +21,7 @@ use utoipa::OpenApi; auth_models::UserResponse, auth_models::LoginUserRequest, auth_models::RefreshTokenRequest, + auth_models::AuthResponse, ) ) )] diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index ecbbb7c..ff52d0f 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -1,7 +1,7 @@ use super::models::{RegisterNewUserRequest, UserResponse}; use crate::api::auth::models::{AuthResponse, LoginUserRequest}; -use crate::api::auth::utils::{generate_auth_token, hash_password}; -use crate::api::error::ErrorType::{Unknown, UserAlreadyExists}; +use crate::api::auth::utils::{generate_auth_token, hash_password, verify_password}; +use crate::api::error::ErrorType::{LoginIncorrect, Unknown, UserAlreadyExists}; use crate::api::error::{APIError, APIErrorBuilder}; use crate::api::utils::db::get_db_connection; use crate::db::schema::users; @@ -88,19 +88,36 @@ pub async fn login( let mut conn = get_db_connection(&state.database_pool).await?; - let _user: User = User::all() + let user: Result = User::all() .filter(User::by_email(&payload.email)) .first(&mut conn) - .await - .map_err(|e| { - warn!(email = payload.email, "failed to login: {}", e); - APIErrorBuilder::error(Unknown).build() - })?; + .await; + + let password_matches: bool = match user { + Ok(ref user) => verify_password(&payload.password, &user.password).is_ok(), + _ => { + // Prevent timing side channel attacks by always taking the same amount of time to verify a password. + let _ = verify_password("", "").is_ok(); + false + } + }; + + if !password_matches { + info!(email = payload.email, "failed to login"); + return Err(APIErrorBuilder::error(LoginIncorrect) + .with_field("email", payload.email.into()) + .build()); + } + + let unwrapped_user = user.map_err(|e| { + warn!(error = %e, "database error when logging in. This should never happen."); + APIErrorBuilder::error(Unknown).build() + })?; Ok(( StatusCode::OK, Json(AuthResponse { - access_token: generate_auth_token(&_user).map_err(|e| { + access_token: generate_auth_token(&unwrapped_user).map_err(|e| { error!(error = %e, "failed to generate auth token"); APIErrorBuilder::error(Unknown).build() })?, diff --git a/backend/src/api/auth/models.rs b/backend/src/api/auth/models.rs index 63463d9..5ca4747 100644 --- a/backend/src/api/auth/models.rs +++ b/backend/src/api/auth/models.rs @@ -90,6 +90,7 @@ impl From for UserResponse { #[derive(Serialize, ToSchema)] pub struct AuthResponse { + #[schema(format = "paseto")] pub access_token: String, pub refresh_token: String, } diff --git a/backend/src/api/error.rs b/backend/src/api/error.rs index 6a8eaa9..d89646e 100644 --- a/backend/src/api/error.rs +++ b/backend/src/api/error.rs @@ -15,6 +15,7 @@ pub enum ErrorType { Unknown, ValidationError, UserAlreadyExists, + LoginIncorrect, } impl ErrorType { @@ -23,6 +24,7 @@ impl ErrorType { ErrorType::Unknown => "about:blank", ErrorType::ValidationError => concatcp!(ERROR_URI, "validation-error"), ErrorType::UserAlreadyExists => concatcp!(ERROR_URI, "user-already-exists"), + ErrorType::LoginIncorrect => concatcp!(ERROR_URI, "login-incorrect"), } } @@ -31,6 +33,7 @@ impl ErrorType { ErrorType::Unknown => "An unknown error has occurred.", ErrorType::ValidationError => "Your request is not valid.", ErrorType::UserAlreadyExists => "A user with that email already exists.", + ErrorType::LoginIncorrect => "Login Incorrect.", } } @@ -39,6 +42,14 @@ impl ErrorType { ErrorType::Unknown => StatusCode::INTERNAL_SERVER_ERROR, ErrorType::ValidationError => StatusCode::BAD_REQUEST, ErrorType::UserAlreadyExists => StatusCode::CONFLICT, + ErrorType::LoginIncorrect => StatusCode::UNAUTHORIZED, + } + } + + pub fn get_detail(&self) -> Option<&'static str> { + match self { + ErrorType::LoginIncorrect => Some("The email or password you entered is incorrect. Please check your credentials and try again."), + _ => None, } } } @@ -162,7 +173,9 @@ impl APIErrorBuilder { APIError { status: self.error_type.get_status(), title: self.error_type.get_title().to_string(), - detail: self.detail, + detail: self + .detail + .or(self.error_type.get_detail().map(|s| s.to_string())), instance: self.instance, extra: self.extra, error_type: self.error_type.get_type().to_string(), From d21fe27b8242c60b60d54dfe34ea2e4075639a12 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 23 Sep 2023 18:48:02 +1200 Subject: [PATCH 46/73] halve expiry time --- backend/src/api/auth/utils.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/api/auth/utils.rs b/backend/src/api/auth/utils.rs index e09d948..b1d0239 100644 --- a/backend/src/api/auth/utils.rs +++ b/backend/src/api/auth/utils.rs @@ -7,8 +7,11 @@ use pasetors::errors::Error as ClaimError; use pasetors::keys::{AsymmetricKeyPair, Generate}; use pasetors::public; use pasetors::version4::V4; +use std::time::Duration; use uuid::Uuid; +const TOKEN_EXPIRY_TIME: Duration = Duration::new(30 * 60, 0); // 30 minutes + pub fn verify_password(password: &str, hash: &str) -> Result<(), argon2::password_hash::Error> { let argon2 = Argon2::default(); let password_bytes = password.as_bytes(); @@ -27,13 +30,14 @@ pub fn hash_password(password: &str) -> Result Result { - let mut claims = Claims::new()?; + let mut claims = Claims::new_expires_in(&TOKEN_EXPIRY_TIME)?; claims.issuer("domus-api.jacksonc.dev")?; claims.subject(user.id.as_hyphenated().to_string().as_str())?; claims.audience("domus.jacksonc.dev")?; claims.token_identifier(Uuid::new_v4().to_string().as_str())?; // Generate the keys and sign the claims. + // TODO: store the private key elsewhere and don't generate it every time. let kp = AsymmetricKeyPair::::generate()?; public::sign(&kp.secret, &claims, None, None) } From dc421c8d2351a0b28c09c46226145065f1ac6e76 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 23 Sep 2023 19:06:44 +1200 Subject: [PATCH 47/73] save refresh token to db --- backend/src/api/auth/controllers.rs | 15 +++++++++++++-- backend/src/db/mod.rs | 1 + backend/src/db/refresh_token.rs | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 backend/src/db/refresh_token.rs diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index ff52d0f..bf0e554 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -4,6 +4,7 @@ use crate::api::auth::utils::{generate_auth_token, hash_password, verify_passwor use crate::api::error::ErrorType::{LoginIncorrect, Unknown, UserAlreadyExists}; use crate::api::error::{APIError, APIErrorBuilder}; use crate::api::utils::db::get_db_connection; +use crate::db::refresh_token::{NewRefreshToken, RefreshToken}; use crate::db::schema::users; use crate::db::user::{NewUser, User}; use crate::AppState; @@ -13,7 +14,6 @@ use diesel::prelude::*; use diesel::SelectableHelper; use diesel_async::RunQueryDsl; use tracing::{error, info, warn}; -use uuid::Uuid; /// Register a new user #[utoipa::path( @@ -114,6 +114,17 @@ pub async fn login( APIErrorBuilder::error(Unknown).build() })?; + let refresh_token = diesel::insert_into(crate::db::schema::refresh_tokens::table) + .values(&NewRefreshToken { + user_id: unwrapped_user.id, + }) + .get_result::(&mut conn) + .await + .map_err(|e| { + error!(error = %e, "failed to insert refresh token"); + APIErrorBuilder::error(Unknown).build() + })?; + Ok(( StatusCode::OK, Json(AuthResponse { @@ -121,7 +132,7 @@ pub async fn login( error!(error = %e, "failed to generate auth token"); APIErrorBuilder::error(Unknown).build() })?, - refresh_token: Uuid::new_v4().to_string(), + refresh_token: refresh_token.id.to_string(), }), )) } diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs index bca37da..1fb1e75 100644 --- a/backend/src/db/mod.rs +++ b/backend/src/db/mod.rs @@ -1,3 +1,4 @@ pub mod database; +pub mod refresh_token; pub mod schema; pub mod user; diff --git a/backend/src/db/refresh_token.rs b/backend/src/db/refresh_token.rs new file mode 100644 index 0000000..384e49c --- /dev/null +++ b/backend/src/db/refresh_token.rs @@ -0,0 +1,19 @@ +use diesel::prelude::*; +use uuid::Uuid; + +#[derive(Queryable, Selectable)] +#[diesel(table_name = crate::db::schema::refresh_tokens)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct RefreshToken { + pub id: Uuid, + pub user_id: Uuid, + pub expires_at: chrono::NaiveDateTime, + pub created_at: chrono::NaiveDateTime, + pub updated_at: Option, +} + +#[derive(Insertable)] +#[diesel(table_name = crate::db::schema::refresh_tokens)] +pub struct NewRefreshToken { + pub user_id: Uuid, +} From b87e567e24c84dd1e93e6b294dad78ba0d35d17a Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 24 Sep 2023 20:07:51 +1300 Subject: [PATCH 48/73] add most of the auth middleware --- backend/Cargo.lock | 2 ++ backend/Cargo.toml | 2 +- backend/src/api/auth/controllers.rs | 5 +-- backend/src/api/error.rs | 8 +++++ backend/src/api/middleware.rs | 52 +++++++++++++++++++++++++++++ backend/src/api/mod.rs | 1 + backend/src/middleware.rs | 0 7 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 backend/src/api/middleware.rs delete mode 100644 backend/src/middleware.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5bc61b5..25bb6ed 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -100,6 +100,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -117,6 +118,7 @@ dependencies = [ "rustversion", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 4a763e8..3afdda1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/j-chad/domus" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = "0.6.19" +axum = { version = "0.6.19", features = ["tracing"] } hyper = { version = "0.14.27", features = ["full"] } tokio = { version = "1.29.1", features = ["full"] } tower = "0.4.13" diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index bf0e554..998e365 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -175,9 +175,10 @@ pub async fn refresh_token() -> impl IntoResponse { path = "/auth/user", tag = "auth", responses( - (status = 501, description = "Not Implemented") + (status = 200, description = "Success", body = UserResponse), + (status = 401, description = "User not signed in", body = APIError), ) )] -pub async fn get_user() -> impl IntoResponse { +pub async fn get_user() -> Result<(StatusCode, Json), APIError> { StatusCode::NOT_IMPLEMENTED } diff --git a/backend/src/api/error.rs b/backend/src/api/error.rs index d89646e..203d417 100644 --- a/backend/src/api/error.rs +++ b/backend/src/api/error.rs @@ -16,6 +16,8 @@ pub enum ErrorType { ValidationError, UserAlreadyExists, LoginIncorrect, + Unauthorized, + Forbidden, } impl ErrorType { @@ -25,6 +27,8 @@ impl ErrorType { ErrorType::ValidationError => concatcp!(ERROR_URI, "validation-error"), ErrorType::UserAlreadyExists => concatcp!(ERROR_URI, "user-already-exists"), ErrorType::LoginIncorrect => concatcp!(ERROR_URI, "login-incorrect"), + ErrorType::Unauthorized => concatcp!(ERROR_URI, "unauthorized"), + ErrorType::Forbidden => concatcp!(ERROR_URI, "forbidden"), } } @@ -34,6 +38,8 @@ impl ErrorType { ErrorType::ValidationError => "Your request is not valid.", ErrorType::UserAlreadyExists => "A user with that email already exists.", ErrorType::LoginIncorrect => "Login Incorrect.", + ErrorType::Unauthorized => "You have not been authorized to perform this action.", + ErrorType::Forbidden => "You are not allowed to perform this action.", } } @@ -43,6 +49,8 @@ impl ErrorType { ErrorType::ValidationError => StatusCode::BAD_REQUEST, ErrorType::UserAlreadyExists => StatusCode::CONFLICT, ErrorType::LoginIncorrect => StatusCode::UNAUTHORIZED, + ErrorType::Unauthorized => StatusCode::UNAUTHORIZED, + ErrorType::Forbidden => StatusCode::FORBIDDEN, } } diff --git a/backend/src/api/middleware.rs b/backend/src/api/middleware.rs new file mode 100644 index 0000000..b9b018e --- /dev/null +++ b/backend/src/api/middleware.rs @@ -0,0 +1,52 @@ +use super::error::APIError; +use crate::api::error::APIErrorBuilder; +use crate::api::error::ErrorType::Unauthorized; +use crate::AppState; +use axum::extract::State; +use axum::http::{header, Request}; +use axum::middleware::Next; +use axum::response::IntoResponse; +use pasetors::claims::ClaimsValidationRules; +use pasetors::token::UntrustedToken; +use pasetors::version4::V4; +use pasetors::{public, Public}; + +const VALIDATION_RULES: ClaimsValidationRules = { + let mut rules = ClaimsValidationRules::new(); + rules.validate_issuer_with("domus-api.jacksonc.dev"); + rules.validate_audience_with("domus.jacksonc.dev"); + rules +}; + +pub async fn auth( + State(state): State, + req: Request, + next: Next, +) -> Result { + let token = req + .headers() + .get(header::AUTHORIZATION) + .and_then(|auth_header| auth_header.to_str().ok()) + .and_then(|auth_value| { + if auth_value.starts_with("Bearer ") { + Some(auth_value[7..].to_owned()) + } else { + None + } + }) + .ok_or( + APIErrorBuilder::error(Unauthorized) + .detail("You are not logged in. Please provide a token.") + .build(), + )?; + + let untrusted_token = UntrustedToken::::try_from(&token).or(Err( + APIErrorBuilder::error(Unauthorized) + .detail("The token you provided is invalid.") + .build(), + ))?; + + // let trusted_token = public::verify(&kp.public, &untrusted_token, &VALIDATION_RULES, None, None)?; + + Ok(next.run(req).await) +} diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 7c7bd76..3bb0b3b 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -4,6 +4,7 @@ use axum::Router; pub mod api_docs; pub mod auth; mod error; +mod middleware; mod utils; pub fn get_router() -> Router { diff --git a/backend/src/middleware.rs b/backend/src/middleware.rs deleted file mode 100644 index e69de29..0000000 From 08c91980c7b48f3cd77050b901167bfbd35a98a4 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 24 Sep 2023 20:40:39 +1300 Subject: [PATCH 49/73] add file to generate key pair --- backend/src/bin/generate-private-key.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 backend/src/bin/generate-private-key.rs diff --git a/backend/src/bin/generate-private-key.rs b/backend/src/bin/generate-private-key.rs new file mode 100644 index 0000000..6cdd3a7 --- /dev/null +++ b/backend/src/bin/generate-private-key.rs @@ -0,0 +1,16 @@ +use pasetors::keys::{AsymmetricKeyPair, Generate}; +use pasetors::paserk::FormatAsPaserk; +use pasetors::version4::V4; + +fn main() { + let sk = AsymmetricKeyPair::::generate().unwrap(); + + let mut public = String::new(); + sk.public.fmt(&mut public).unwrap(); + + let mut private = String::new(); + sk.secret.fmt(&mut private).unwrap(); + + println!("Public Key: {}", public); + println!("Private Key: {}", private); +} From 0328d30e3cb5d7b5625831b7c53fe99f4f08b44b Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 24 Sep 2023 20:40:58 +1300 Subject: [PATCH 50/73] only load from .env locally --- backend/src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/main.rs b/backend/src/main.rs index 79acafe..3472bcb 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ mod api; +mod config; mod db; use api::api_docs; @@ -32,7 +33,9 @@ pub type AppState = Arc; #[tokio::main] async fn main() { - dotenvy::dotenv().unwrap(); + if cfg!(debug_assertions) { + dotenvy::dotenv().unwrap(); + } // initialize tracing tracing_subscriber::registry() From 1caa4b9cd98c6265621e03d4d27cced04c3047e6 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 24 Sep 2023 20:41:11 +1300 Subject: [PATCH 51/73] fix type --- backend/src/api/auth/controllers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index 998e365..bca46be 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -179,6 +179,6 @@ pub async fn refresh_token() -> impl IntoResponse { (status = 401, description = "User not signed in", body = APIError), ) )] -pub async fn get_user() -> Result<(StatusCode, Json), APIError> { +pub async fn get_user() -> impl IntoResponse { StatusCode::NOT_IMPLEMENTED } From aaf89870a5cd5540ccc3597e3680bb8e9b9f5ea5 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 24 Sep 2023 22:00:50 +1300 Subject: [PATCH 52/73] add config file --- .idea/runConfigurations/Backend.xml | 20 --- backend/Cargo.lock | 229 ++++++++++++++++++++++-- backend/Cargo.toml | 5 +- backend/config.toml | 6 + backend/src/bin/generate-auth-keys.rs | 65 +++++++ backend/src/bin/generate-private-key.rs | 16 -- backend/src/config.rs | 41 +++++ backend/src/db/database.rs | 1 + backend/src/main.rs | 14 +- 9 files changed, 344 insertions(+), 53 deletions(-) delete mode 100644 .idea/runConfigurations/Backend.xml create mode 100644 backend/config.toml create mode 100644 backend/src/bin/generate-auth-keys.rs delete mode 100644 backend/src/bin/generate-private-key.rs diff --git a/.idea/runConfigurations/Backend.xml b/.idea/runConfigurations/Backend.xml deleted file mode 100644 index b54e179..0000000 --- a/.idea/runConfigurations/Backend.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - \ No newline at end of file diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 25bb6ed..cc1df26 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.2" @@ -142,6 +153,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f879ef8fc74665ed7f0e6127cb106315888fc2744f68e14b74f83edbb2a08992" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.2" @@ -229,6 +246,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const_format" version = "0.2.31" @@ -325,9 +361,9 @@ checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "diesel" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7a532c1f99a0f596f6960a60d1e119e91582b24b39e2d83a190e61262c3ef0c" +checksum = "d98235fdc2f355d330a8244184ab6b4b33c28679c0b4158f63138e51d6cf7e88" dependencies = [ "bitflags 2.4.0", "byteorder", @@ -339,9 +375,9 @@ dependencies = [ [[package]] name = "diesel-async" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e7974099f0d9bde0e010dd3a673555276a474f3362a7a52ab535a57b7c5056" +checksum = "acada1517534c92d3f382217b485db8a8638f111b0e3f2a2a8e26165050f77be" dependencies = [ "async-trait", "deadpool", @@ -354,9 +390,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74398b79d81e52e130d991afeed9c86034bb1b7735f46d2f5bf7deb261d80303" +checksum = "e054665eaf6d97d1e7125512bb2d35d07c73ac86cc6920174cb42d1ab697a554" dependencies = [ "diesel_table_macro_syntax", "proc-macro2", @@ -404,6 +440,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "domus" version = "0.1.0" @@ -412,6 +454,7 @@ dependencies = [ "axum", "base62", "chrono", + "config", "const_format", "deadpool", "diesel", @@ -597,6 +640,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -768,6 +814,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -780,6 +837,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lock_api" version = "0.4.10" @@ -845,6 +908,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -866,6 +935,16 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -910,6 +989,16 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "orion" version = "0.17.5" @@ -978,12 +1067,63 @@ dependencies = [ "subtle", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pest" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.27", +] + +[[package]] +name = "pest_meta" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.11.2" @@ -1036,11 +1176,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "postgres-protocol" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b7fa9f396f51dffd61546fd8573ee20592287996568e6175ceb0f8699ad75d" +checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" dependencies = [ - "base64", + "base64 0.21.2", "byteorder", "bytes", "fallible-iterator", @@ -1220,6 +1360,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + [[package]] name = "rust-embed" version = "6.8.1" @@ -1255,6 +1406,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1591,9 +1752,9 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e89f6234aa8fd43779746012fcf53603cdb91fdd8399aa0de868c2d56b6dde1" +checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" dependencies = [ "async-trait", "byteorder", @@ -1608,9 +1769,11 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", + "rand", "socket2 0.5.3", "tokio", "tokio-util", + "whoami", ] [[package]] @@ -1627,6 +1790,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.4.13" @@ -1762,6 +1934,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicase" version = "2.6.0" @@ -2000,6 +2178,26 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2172,6 +2370,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3afdda1..b969bfd 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -23,12 +23,13 @@ validator = { version = "0.16.1", features = ["derive"] } tower-http = { version = "0.4.3", features = ["trace"] } diesel = { version = "2.1.0", features = ["chrono", "uuid"] } uuid = { version = "1.4.1", features = ["v4"] } -diesel-async = { version = "0.3.1", features = ["postgres", "deadpool"] } +diesel-async = { version = "0.4.1", features = ["postgres", "deadpool"] } deadpool = "0.9.5" dotenvy = { version = "0.15" } chrono = "0.4.26" base62 = "2.0.2" const_format = "0.2.31" argon2 = "0.5.1" -pasetors = "0.6.7" +pasetors = { version = "0.6.7", features = ["paserk"] } +config = "0.13.3" diff --git a/backend/config.toml b/backend/config.toml new file mode 100644 index 0000000..826e40c --- /dev/null +++ b/backend/config.toml @@ -0,0 +1,6 @@ +[server] +host = "127.0.0.1" +port = 8080 + +[database] +max_pool_size = 16 \ No newline at end of file diff --git a/backend/src/bin/generate-auth-keys.rs b/backend/src/bin/generate-auth-keys.rs new file mode 100644 index 0000000..d59494d --- /dev/null +++ b/backend/src/bin/generate-auth-keys.rs @@ -0,0 +1,65 @@ +use const_format::concatcp; +use pasetors::keys::{AsymmetricKeyPair, Generate}; +use pasetors::paserk::FormatAsPaserk; +use pasetors::version4::V4; +use std::fs::OpenOptions; + +const ENV_PREFIX: &str = "DOMUS_AUTH."; +const ENV_PRIVATE_KEY: &str = concatcp!(ENV_PREFIX, "PRIVATE_KEY"); +const ENV_PUBLIC_KEY: &str = concatcp!(ENV_PREFIX, "PUBLIC_KEY"); + +fn main() { + let sk = AsymmetricKeyPair::::generate().unwrap(); + + let mut public = String::new(); + sk.public.fmt(&mut public).unwrap(); + + let mut private = String::new(); + sk.secret.fmt(&mut private).unwrap(); + + write_to_env_file(&private, &public); + + println!("Public Key: {}", public); + println!("Private Key: {}", private); + println!("Tokens have been added to your .env file") +} + +fn write_to_env_file(private: &str, public: &str) { + use std::io::prelude::*; + + // create .env file if it doesn't exist + let mut file = OpenOptions::new() + .write(true) + .append(false) + .read(true) + .create(true) + .open(".env") + .unwrap(); + + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + + // if either key is already in the file (with any value): + // - remove the line + contents = contents + .lines() + .filter(|line| !line.starts_with(ENV_PRIVATE_KEY) && !line.starts_with(ENV_PUBLIC_KEY)) + .map(|line| format!("{}\n", line)) + .collect(); + + contents = contents.trim().to_string(); + + let template = format!( + "{}={}\n{}={}\n", + ENV_PRIVATE_KEY, private, ENV_PUBLIC_KEY, public + ); + + if !contents.is_empty() { + contents.push('\n'); + } + + contents.push_str(&template); + file.set_len(0).unwrap(); + file.seek(std::io::SeekFrom::Start(0)).unwrap(); + file.write_all(contents.as_bytes()).unwrap(); +} diff --git a/backend/src/bin/generate-private-key.rs b/backend/src/bin/generate-private-key.rs deleted file mode 100644 index 6cdd3a7..0000000 --- a/backend/src/bin/generate-private-key.rs +++ /dev/null @@ -1,16 +0,0 @@ -use pasetors::keys::{AsymmetricKeyPair, Generate}; -use pasetors::paserk::FormatAsPaserk; -use pasetors::version4::V4; - -fn main() { - let sk = AsymmetricKeyPair::::generate().unwrap(); - - let mut public = String::new(); - sk.public.fmt(&mut public).unwrap(); - - let mut private = String::new(); - sk.secret.fmt(&mut private).unwrap(); - - println!("Public Key: {}", public); - println!("Private Key: {}", private); -} diff --git a/backend/src/config.rs b/backend/src/config.rs index e69de29..f7900fe 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -0,0 +1,41 @@ +use config::{Config, ConfigError, Environment, File}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, Clone)] +pub struct Auth { + pub private_key: String, + pub public_key: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Server { + pub host: String, + pub port: u16, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Database { + pub url: String, + pub max_pool_size: u8, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Settings { + pub server: Server, + pub database: Database, + pub auth: Auth, +} + +impl Settings { + pub fn new() -> Result { + //config file is project_root/config.toml + let s = Config::builder() + .add_source(File::with_name("config")) + .add_source(Environment::with_prefix("domus")) + .build()?; + + println!("{:?}", s); + + s.try_deserialize() + } +} diff --git a/backend/src/db/database.rs b/backend/src/db/database.rs index 413318c..afeaf4c 100644 --- a/backend/src/db/database.rs +++ b/backend/src/db/database.rs @@ -13,6 +13,7 @@ pub fn get_connection_pool() -> ConnectionPool { let config = AsyncDieselConnectionManager::::new(database_url); Pool::builder(config) + .max_size(16) .build() .expect("Failed to create database pool.") } diff --git a/backend/src/main.rs b/backend/src/main.rs index 3472bcb..e2b295f 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -2,6 +2,7 @@ mod api; mod config; mod db; +use crate::config::Settings; use api::api_docs; use axum::http::StatusCode; use axum::Router; @@ -16,16 +17,19 @@ use tracing_subscriber::util::SubscriberInitExt; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; -#[derive(Clone)] pub struct AppStateInternal { pub database_pool: database::ConnectionPool, + pub settings: config::Settings, } impl AppStateInternal { - fn new() -> Self { + fn new(settings: Settings) -> Self { let database_pool = database::get_connection_pool(); - Self { database_pool } + Self { + database_pool, + settings, + } } } @@ -37,6 +41,8 @@ async fn main() { dotenvy::dotenv().unwrap(); } + let config: Settings = Settings::new().unwrap(); + // initialize tracing tracing_subscriber::registry() .with( @@ -58,7 +64,7 @@ async fn main() { .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().latency_unit(LatencyUnit::Millis)), ) - .with_state(Arc::new(AppStateInternal::new())); + .with_state(Arc::new(AppStateInternal::new(config.clone()))); // run our app with hyper let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); From d0d5e17e62c37e943bb95e76bf5c564a5b6595fb Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sun, 24 Sep 2023 22:28:09 +1300 Subject: [PATCH 53/73] use config --- .idea/inspectionProfiles/Project_Default.xml | 31 ++++++++++++++++++++ backend/config.toml | 3 +- backend/src/config.rs | 3 -- backend/src/db/database.rs | 8 ++--- backend/src/main.rs | 8 ++--- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 03d9549..ea53a74 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,5 +2,36 @@
\ No newline at end of file diff --git a/backend/config.toml b/backend/config.toml index 826e40c..2143d18 100644 --- a/backend/config.toml +++ b/backend/config.toml @@ -1,6 +1,5 @@ [server] -host = "127.0.0.1" -port = 8080 +host = "127.0.0.1:3000" [database] max_pool_size = 16 \ No newline at end of file diff --git a/backend/src/config.rs b/backend/src/config.rs index f7900fe..3a71c64 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -10,7 +10,6 @@ pub struct Auth { #[derive(Debug, Deserialize, Clone)] pub struct Server { pub host: String, - pub port: u16, } #[derive(Debug, Deserialize, Clone)] @@ -34,8 +33,6 @@ impl Settings { .add_source(Environment::with_prefix("domus")) .build()?; - println!("{:?}", s); - s.try_deserialize() } } diff --git a/backend/src/db/database.rs b/backend/src/db/database.rs index afeaf4c..a31debe 100644 --- a/backend/src/db/database.rs +++ b/backend/src/db/database.rs @@ -1,19 +1,19 @@ +use crate::config::Settings; use deadpool::managed::Object; use diesel_async::pooled_connection::deadpool::Pool; use diesel_async::pooled_connection::AsyncDieselConnectionManager; use diesel_async::AsyncPgConnection; -use std::env; pub type Connection = Object>; pub type ConnectionPool = deadpool::managed::Pool, Connection>; -pub fn get_connection_pool() -> ConnectionPool { - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); +pub fn get_connection_pool(settings: &Settings) -> ConnectionPool { + let database_url = &settings.database.url; let config = AsyncDieselConnectionManager::::new(database_url); Pool::builder(config) - .max_size(16) + .max_size(settings.database.max_pool_size as usize) .build() .expect("Failed to create database pool.") } diff --git a/backend/src/main.rs b/backend/src/main.rs index e2b295f..e9d0d56 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -19,12 +19,12 @@ use utoipa_swagger_ui::SwaggerUi; pub struct AppStateInternal { pub database_pool: database::ConnectionPool, - pub settings: config::Settings, + pub settings: Settings, } impl AppStateInternal { fn new(settings: Settings) -> Self { - let database_pool = database::get_connection_pool(); + let database_pool = database::get_connection_pool(&settings); Self { database_pool, @@ -60,14 +60,14 @@ async fn main() { .nest("/v1", api::get_router()) .fallback(fallback) .layer( - TraceLayer::new_for_http() // .make_span_with(DefaultMakeSpan::new().include_headers(true)) + TraceLayer::new_for_http() .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().latency_unit(LatencyUnit::Millis)), ) .with_state(Arc::new(AppStateInternal::new(config.clone()))); // run our app with hyper - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let addr = config.server.host.parse::().unwrap(); tracing::info!("listening on http://{}", addr); tracing::debug!("docs at http://{}/swagger-ui", addr); axum::Server::bind(&addr) From 9958c2471b469a58f5fa13b0a9cf628ef053ba16 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 11:43:52 +1300 Subject: [PATCH 54/73] split config up --- backend/.gitignore | 3 +++ backend/config/default.toml | 5 +++++ backend/config/dev.toml | 2 ++ backend/src/config.rs | 8 ++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 backend/config/default.toml create mode 100644 backend/config/dev.toml diff --git a/backend/.gitignore b/backend/.gitignore index 378e620..bc9902a 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -201,3 +201,6 @@ $RECYCLE.BIN/ *.lnk # End of https://www.toptal.com/developers/gitignore/api/rust,jetbrains,linux,macos,windows + +.env +config/local.* \ No newline at end of file diff --git a/backend/config/default.toml b/backend/config/default.toml new file mode 100644 index 0000000..2143d18 --- /dev/null +++ b/backend/config/default.toml @@ -0,0 +1,5 @@ +[server] +host = "127.0.0.1:3000" + +[database] +max_pool_size = 16 \ No newline at end of file diff --git a/backend/config/dev.toml b/backend/config/dev.toml new file mode 100644 index 0000000..f4f5c8a --- /dev/null +++ b/backend/config/dev.toml @@ -0,0 +1,2 @@ +[database] +url = 'postgres://username:password@localhost/domus' \ No newline at end of file diff --git a/backend/src/config.rs b/backend/src/config.rs index 3a71c64..ded12f2 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -1,5 +1,6 @@ use config::{Config, ConfigError, Environment, File}; use serde::Deserialize; +use std::env; #[derive(Debug, Deserialize, Clone)] pub struct Auth { @@ -27,9 +28,12 @@ pub struct Settings { impl Settings { pub fn new() -> Result { - //config file is project_root/config.toml + let run_mode = env::var("DOMUS_ENV").or(Ok("prod".to_string()))?; + let s = Config::builder() - .add_source(File::with_name("config")) + .add_source(File::with_name("config/default")) + .add_source(File::with_name(&format!("config/{}", run_mode)).required(false)) + .add_source(File::with_name("config/local").required(false)) .add_source(Environment::with_prefix("domus")) .build()?; From 125bd4202e17755a2b856df8cff14774ee117567 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 12:20:17 +1300 Subject: [PATCH 55/73] rewrite setup script --- backend/Cargo.lock | 55 +++++++++++++++++++- backend/Cargo.toml | 1 + backend/src/bin/generate-auth-keys.rs | 65 ------------------------ backend/src/bin/setup-local-config.rs | 72 +++++++++++++++++++++++++++ backend/src/config.rs | 4 +- 5 files changed, 129 insertions(+), 68 deletions(-) delete mode 100644 backend/src/bin/generate-auth-keys.rs create mode 100644 backend/src/bin/setup-local-config.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index cc1df26..2c0cce7 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -261,7 +261,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml", + "toml 0.5.11", "yaml-rust", ] @@ -465,6 +465,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "toml 0.8.1", "tower", "tower-http", "tracing", @@ -1500,6 +1501,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1799,6 +1809,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -2370,6 +2414,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b969bfd..d534a66 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -32,4 +32,5 @@ const_format = "0.2.31" argon2 = "0.5.1" pasetors = { version = "0.6.7", features = ["paserk"] } config = "0.13.3" +toml = "0.8.0" diff --git a/backend/src/bin/generate-auth-keys.rs b/backend/src/bin/generate-auth-keys.rs deleted file mode 100644 index d59494d..0000000 --- a/backend/src/bin/generate-auth-keys.rs +++ /dev/null @@ -1,65 +0,0 @@ -use const_format::concatcp; -use pasetors::keys::{AsymmetricKeyPair, Generate}; -use pasetors::paserk::FormatAsPaserk; -use pasetors::version4::V4; -use std::fs::OpenOptions; - -const ENV_PREFIX: &str = "DOMUS_AUTH."; -const ENV_PRIVATE_KEY: &str = concatcp!(ENV_PREFIX, "PRIVATE_KEY"); -const ENV_PUBLIC_KEY: &str = concatcp!(ENV_PREFIX, "PUBLIC_KEY"); - -fn main() { - let sk = AsymmetricKeyPair::::generate().unwrap(); - - let mut public = String::new(); - sk.public.fmt(&mut public).unwrap(); - - let mut private = String::new(); - sk.secret.fmt(&mut private).unwrap(); - - write_to_env_file(&private, &public); - - println!("Public Key: {}", public); - println!("Private Key: {}", private); - println!("Tokens have been added to your .env file") -} - -fn write_to_env_file(private: &str, public: &str) { - use std::io::prelude::*; - - // create .env file if it doesn't exist - let mut file = OpenOptions::new() - .write(true) - .append(false) - .read(true) - .create(true) - .open(".env") - .unwrap(); - - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); - - // if either key is already in the file (with any value): - // - remove the line - contents = contents - .lines() - .filter(|line| !line.starts_with(ENV_PRIVATE_KEY) && !line.starts_with(ENV_PUBLIC_KEY)) - .map(|line| format!("{}\n", line)) - .collect(); - - contents = contents.trim().to_string(); - - let template = format!( - "{}={}\n{}={}\n", - ENV_PRIVATE_KEY, private, ENV_PUBLIC_KEY, public - ); - - if !contents.is_empty() { - contents.push('\n'); - } - - contents.push_str(&template); - file.set_len(0).unwrap(); - file.seek(std::io::SeekFrom::Start(0)).unwrap(); - file.write_all(contents.as_bytes()).unwrap(); -} diff --git a/backend/src/bin/setup-local-config.rs b/backend/src/bin/setup-local-config.rs new file mode 100644 index 0000000..7d03697 --- /dev/null +++ b/backend/src/bin/setup-local-config.rs @@ -0,0 +1,72 @@ +use pasetors::keys::{AsymmetricKeyPair, Generate}; +use pasetors::paserk::FormatAsPaserk; +use pasetors::version4::V4; +use serde::{Deserialize, Serialize}; +use std::fs; + +const LOCAL_CONFIG_FILE: &str = "config/local.toml"; + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct Auth { + private_key: String, + public_key: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct App { + host: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct Database { + url: String, + max_pool_size: u8, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct Settings { + app: Option, + database: Option, + auth: Option, +} + +fn main() { + let mut settings = get_settings(); + + add_auth_keys(&mut settings); + + write_settings(&settings); +} + +fn get_settings() -> Settings { + // create the file if it doesn't exist + if fs::metadata(LOCAL_CONFIG_FILE).is_err() { + fs::write(LOCAL_CONFIG_FILE, "").unwrap(); + } + + let contents = fs::read_to_string(LOCAL_CONFIG_FILE).unwrap(); + + //deserialize the file into a Settings struct + toml::from_str::(&contents).unwrap() +} + +fn write_settings(settings: &Settings) { + let contents = toml::to_string_pretty(settings).unwrap(); + + fs::write(LOCAL_CONFIG_FILE, contents).unwrap(); +} + +fn add_auth_keys(settings: &mut Settings) { + let sk = AsymmetricKeyPair::::generate().unwrap(); + + let mut public = String::new(); + sk.public.fmt(&mut public).unwrap(); + + let mut private = String::new(); + sk.secret.fmt(&mut private).unwrap(); + + settings.auth = Some(Auth { + private_key: private, + public_key: public, + }); +} diff --git a/backend/src/config.rs b/backend/src/config.rs index ded12f2..9484ad5 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -9,7 +9,7 @@ pub struct Auth { } #[derive(Debug, Deserialize, Clone)] -pub struct Server { +pub struct App { pub host: String, } @@ -21,7 +21,7 @@ pub struct Database { #[derive(Debug, Deserialize, Clone)] pub struct Settings { - pub server: Server, + pub server: App, pub database: Database, pub auth: Auth, } From 4bd5bd554e078e2c946737d56b76983210d4ca64 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 13:12:42 +1300 Subject: [PATCH 56/73] add a taskfile --- backend/taskfile.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 backend/taskfile.yml diff --git a/backend/taskfile.yml b/backend/taskfile.yml new file mode 100644 index 0000000..056c9b7 --- /dev/null +++ b/backend/taskfile.yml @@ -0,0 +1,10 @@ +version: 3 + +tasks: + local-env: + cmds: + - cargo run --bin setup-local-config + + setup-infra: + cmds: + - docker-compose up -d \ No newline at end of file From 083933f9724846d5231f1e9101ff7113f6e8f4b8 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 18:13:07 +1300 Subject: [PATCH 57/73] some error changes --- backend/Cargo.lock | 16 ++++++++--- backend/Cargo.toml | 2 ++ backend/src/api/auth/mod.rs | 1 + backend/src/api/error.rs | 50 ++++++++++++++++++++++++++--------- backend/src/api/middleware.rs | 31 ++++++++++++++-------- 5 files changed, 73 insertions(+), 27 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 2c0cce7..1d54db4 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "argon2" version = "0.5.1" @@ -450,6 +456,7 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" name = "domus" version = "0.1.0" dependencies = [ + "anyhow", "argon2", "axum", "base62", @@ -464,6 +471,7 @@ dependencies = [ "pasetors", "serde", "serde_json", + "thiserror", "tokio", "toml 0.8.1", "tower", @@ -1647,18 +1655,18 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d534a66..a10ce4f 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -33,4 +33,6 @@ argon2 = "0.5.1" pasetors = { version = "0.6.7", features = ["paserk"] } config = "0.13.3" toml = "0.8.0" +anyhow = "1.0.75" +thiserror = "1.0.49" diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs index d7a3579..fb17b96 100644 --- a/backend/src/api/auth/mod.rs +++ b/backend/src/api/auth/mod.rs @@ -1,3 +1,4 @@ +use crate::api::middleware; use crate::AppState; use axum::routing::{get, post}; use axum::Router; diff --git a/backend/src/api/error.rs b/backend/src/api/error.rs index 203d417..2d554ab 100644 --- a/backend/src/api/error.rs +++ b/backend/src/api/error.rs @@ -5,25 +5,40 @@ use const_format::concatcp; use serde::{Serialize, Serializer}; use serde_json::Value; use std::collections::HashMap; +use thiserror::Error; use tracing::error; use utoipa::ToSchema; const ERROR_URI: &str = "tag:domus@jacksonc.dev,2023:errors/"; -#[allow(dead_code)] // TODO: Remove this +#[derive(Error, Debug)] pub enum ErrorType { + #[error("An unknown error has occurred.")] Unknown, + + #[error("An unknown error has occurred.")] + ForeignError(#[from] anyhow::Error), + + #[error("Your request is not valid.")] ValidationError, + + #[error("A user with that email already exists.")] UserAlreadyExists, + + #[error("Login Incorrect.")] LoginIncorrect, + + #[error("You have not been authorized to perform this action.")] Unauthorized, + + #[error("You are not allowed to perform this action.")] Forbidden, } impl ErrorType { pub fn get_type(&self) -> &'static str { match self { - ErrorType::Unknown => "about:blank", + ErrorType::Unknown | ErrorType::ForeignError(_) => "about:blank", ErrorType::ValidationError => concatcp!(ERROR_URI, "validation-error"), ErrorType::UserAlreadyExists => concatcp!(ERROR_URI, "user-already-exists"), ErrorType::LoginIncorrect => concatcp!(ERROR_URI, "login-incorrect"), @@ -32,20 +47,13 @@ impl ErrorType { } } - pub fn get_title(&self) -> &'static str { - match self { - ErrorType::Unknown => "An unknown error has occurred.", - ErrorType::ValidationError => "Your request is not valid.", - ErrorType::UserAlreadyExists => "A user with that email already exists.", - ErrorType::LoginIncorrect => "Login Incorrect.", - ErrorType::Unauthorized => "You have not been authorized to perform this action.", - ErrorType::Forbidden => "You are not allowed to perform this action.", - } + pub fn get_title(&self) -> String { + self.to_string() } pub fn get_status(&self) -> StatusCode { match self { - ErrorType::Unknown => StatusCode::INTERNAL_SERVER_ERROR, + ErrorType::Unknown | ErrorType::ForeignError(_) => StatusCode::INTERNAL_SERVER_ERROR, ErrorType::ValidationError => StatusCode::BAD_REQUEST, ErrorType::UserAlreadyExists => StatusCode::CONFLICT, ErrorType::LoginIncorrect => StatusCode::UNAUTHORIZED, @@ -99,6 +107,24 @@ pub struct APIError { extra: Option>, } +impl From for APIError { + fn from(value: ErrorType) -> Self { + if let ErrorType::ForeignError(e) = &value { + return APIErrorBuilder::error(value) + .with_field("cause", format!("{}", e).into()) + .build(); + } + + return APIErrorBuilder::error(value).build(); + } +} + +impl From for APIError { + fn from(error: anyhow::Error) -> Self { + APIError::from(ErrorType::ForeignError(error)) + } +} + impl IntoResponse for APIError { fn into_response(self) -> Response { let resp = Response::builder() diff --git a/backend/src/api/middleware.rs b/backend/src/api/middleware.rs index b9b018e..178dc02 100644 --- a/backend/src/api/middleware.rs +++ b/backend/src/api/middleware.rs @@ -1,25 +1,20 @@ use super::error::APIError; use crate::api::error::APIErrorBuilder; -use crate::api::error::ErrorType::Unauthorized; +use crate::api::error::ErrorType::{ForeignError, Unauthorized}; +use crate::config::Auth; use crate::AppState; use axum::extract::State; use axum::http::{header, Request}; use axum::middleware::Next; use axum::response::IntoResponse; use pasetors::claims::ClaimsValidationRules; +use pasetors::keys::AsymmetricPublicKey; use pasetors::token::UntrustedToken; use pasetors::version4::V4; use pasetors::{public, Public}; -const VALIDATION_RULES: ClaimsValidationRules = { - let mut rules = ClaimsValidationRules::new(); - rules.validate_issuer_with("domus-api.jacksonc.dev"); - rules.validate_audience_with("domus.jacksonc.dev"); - rules -}; - -pub async fn auth( - State(state): State, +async fn auth( + State(state): &State, req: Request, next: Next, ) -> Result { @@ -46,7 +41,21 @@ pub async fn auth( .build(), ))?; - // let trusted_token = public::verify(&kp.public, &untrusted_token, &VALIDATION_RULES, None, None)?; + let mut rules = ClaimsValidationRules::new(); + rules.validate_issuer_with("domus-api.jacksonc.dev"); + rules.validate_audience_with("domus.jacksonc.dev"); + + let Auth { public_key, .. } = &state.settings.auth; + let key = AsymmetricPublicKey::::try_from(public_key.as_str()).map_err(|_| { + APIErrorBuilder::error(Unauthorized) + .detail("The token you provided is invalid.") + .build() + })?; + let trusted_token = public::verify(&key, &untrusted_token, &rules, None, None).or(Err( + APIErrorBuilder::error(Unauthorized) + .detail("The token you provided is not trusted.") + .build(), + ))?; Ok(next.run(req).await) } From 910de2c3f4adafa36407cd1ce58fa285088b1698 Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 18:33:28 +1300 Subject: [PATCH 58/73] add cause to errors --- backend/src/api/auth/controllers.rs | 13 +++++++------ backend/src/api/error.rs | 24 ++++++++++++++++++++---- backend/src/api/utils/db.rs | 3 +-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index bca46be..42cf9d4 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -40,7 +40,7 @@ pub async fn register( let hashed_password = hash_password(&payload.password).map_err(|err| { error!(error = %err, "failed to hash password"); - APIErrorBuilder::error(Unknown).build() + APIErrorBuilder::from_error(err).build() })?; let new_user = NewUser { @@ -57,7 +57,8 @@ pub async fn register( .await .map_err(|e| { warn!(email = new_user.email, "failed to register new user: {}", e); - APIErrorBuilder::error(UserAlreadyExists) + APIErrorBuilder::new(UserAlreadyExists) + .cause(e) .detail("If you already have an account, try logging in.") .with_field("email", new_user.email.into()) .build() @@ -104,14 +105,14 @@ pub async fn login( if !password_matches { info!(email = payload.email, "failed to login"); - return Err(APIErrorBuilder::error(LoginIncorrect) + return Err(APIErrorBuilder::new(LoginIncorrect) .with_field("email", payload.email.into()) .build()); } let unwrapped_user = user.map_err(|e| { warn!(error = %e, "database error when logging in. This should never happen."); - APIErrorBuilder::error(Unknown).build() + APIErrorBuilder::from_error(e).build() })?; let refresh_token = diesel::insert_into(crate::db::schema::refresh_tokens::table) @@ -122,7 +123,7 @@ pub async fn login( .await .map_err(|e| { error!(error = %e, "failed to insert refresh token"); - APIErrorBuilder::error(Unknown).build() + APIErrorBuilder::from_error(e).build() })?; Ok(( @@ -130,7 +131,7 @@ pub async fn login( Json(AuthResponse { access_token: generate_auth_token(&unwrapped_user).map_err(|e| { error!(error = %e, "failed to generate auth token"); - APIErrorBuilder::error(Unknown).build() + APIErrorBuilder::from_error(e).build() })?, refresh_token: refresh_token.id.to_string(), }), diff --git a/backend/src/api/error.rs b/backend/src/api/error.rs index 2d554ab..908070e 100644 --- a/backend/src/api/error.rs +++ b/backend/src/api/error.rs @@ -1,3 +1,4 @@ +use crate::api::error::ErrorType::Unknown; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; @@ -5,6 +6,7 @@ use const_format::concatcp; use serde::{Serialize, Serializer}; use serde_json::Value; use std::collections::HashMap; +use std::fmt::Display; use thiserror::Error; use tracing::error; use utoipa::ToSchema; @@ -110,12 +112,12 @@ pub struct APIError { impl From for APIError { fn from(value: ErrorType) -> Self { if let ErrorType::ForeignError(e) = &value { - return APIErrorBuilder::error(value) + return APIErrorBuilder::new(value) .with_field("cause", format!("{}", e).into()) .build(); } - return APIErrorBuilder::error(value).build(); + return APIErrorBuilder::new(value).build(); } } @@ -157,7 +159,7 @@ impl APIErrorBuilder { /// Create a new APIErrorBuilder with the given error type. /// /// This is the recommended way to create an APIErrorBuilder. - pub fn error(error: ErrorType) -> Self { + pub fn new(error: ErrorType) -> Self { Self { error_type: error, detail: None, @@ -166,6 +168,20 @@ impl APIErrorBuilder { } } + /// Create a new unknown error from the given error. + /// + /// This is a shorthand for `APIErrorBuilder::new(Unknown).cause(error)`. + pub fn from_error(error: impl Display) -> Self { + Self::new(Unknown).cause(error) + } + + /// Adds a cause field to the error. + /// + /// Shorthand for `with_field("cause", error.to_string().into())`. + pub fn cause(self, error: impl Display) -> Self { + self.with_field("cause", error.to_string().into()) + } + /// Add additional information to the error. /// /// RFC 9457: @@ -219,7 +235,7 @@ impl APIErrorBuilder { impl Default for APIErrorBuilder { fn default() -> Self { - Self::error(ErrorType::Unknown) + Self::new(ErrorType::Unknown) } } diff --git a/backend/src/api/utils/db.rs b/backend/src/api/utils/db.rs index 40ca703..ee27134 100644 --- a/backend/src/api/utils/db.rs +++ b/backend/src/api/utils/db.rs @@ -1,4 +1,3 @@ -use crate::api::error::ErrorType::Unknown; use crate::api::error::{APIError, APIErrorBuilder}; use crate::db::database::{Connection, ConnectionPool}; use tracing::error; @@ -6,7 +5,7 @@ use tracing::error; pub(crate) async fn get_db_connection(pool: &ConnectionPool) -> Result { let connection: Connection = pool.get().await.map_err(|err| { error!(error = %err, "failed to get database connection"); - APIErrorBuilder::error(Unknown).build() + APIErrorBuilder::from_error(err).build() })?; Ok(connection) From 1cf12e53bcf258667f5fb2d457b96e91f20d32fc Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 18:47:15 +1300 Subject: [PATCH 59/73] linting fixes --- backend/src/api/auth/controllers.rs | 2 +- backend/src/api/auth/mod.rs | 1 - backend/src/api/error.rs | 10 ++++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/src/api/auth/controllers.rs b/backend/src/api/auth/controllers.rs index 42cf9d4..4265f01 100644 --- a/backend/src/api/auth/controllers.rs +++ b/backend/src/api/auth/controllers.rs @@ -1,7 +1,7 @@ use super::models::{RegisterNewUserRequest, UserResponse}; use crate::api::auth::models::{AuthResponse, LoginUserRequest}; use crate::api::auth::utils::{generate_auth_token, hash_password, verify_password}; -use crate::api::error::ErrorType::{LoginIncorrect, Unknown, UserAlreadyExists}; +use crate::api::error::ErrorType::{LoginIncorrect, UserAlreadyExists}; use crate::api::error::{APIError, APIErrorBuilder}; use crate::api::utils::db::get_db_connection; use crate::db::refresh_token::{NewRefreshToken, RefreshToken}; diff --git a/backend/src/api/auth/mod.rs b/backend/src/api/auth/mod.rs index fb17b96..d7a3579 100644 --- a/backend/src/api/auth/mod.rs +++ b/backend/src/api/auth/mod.rs @@ -1,4 +1,3 @@ -use crate::api::middleware; use crate::AppState; use axum::routing::{get, post}; use axum::Router; diff --git a/backend/src/api/error.rs b/backend/src/api/error.rs index 908070e..9c9208b 100644 --- a/backend/src/api/error.rs +++ b/backend/src/api/error.rs @@ -1,4 +1,3 @@ -use crate::api::error::ErrorType::Unknown; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; @@ -13,6 +12,7 @@ use utoipa::ToSchema; const ERROR_URI: &str = "tag:domus@jacksonc.dev,2023:errors/"; +#[allow(dead_code)] #[derive(Error, Debug)] pub enum ErrorType { #[error("An unknown error has occurred.")] @@ -112,12 +112,10 @@ pub struct APIError { impl From for APIError { fn from(value: ErrorType) -> Self { if let ErrorType::ForeignError(e) = &value { - return APIErrorBuilder::new(value) - .with_field("cause", format!("{}", e).into()) - .build(); + return APIErrorBuilder::from_error(e).build(); } - return APIErrorBuilder::new(value).build(); + APIErrorBuilder::new(value).build() } } @@ -172,7 +170,7 @@ impl APIErrorBuilder { /// /// This is a shorthand for `APIErrorBuilder::new(Unknown).cause(error)`. pub fn from_error(error: impl Display) -> Self { - Self::new(Unknown).cause(error) + Self::new(ErrorType::Unknown).cause(error) } /// Adds a cause field to the error. From 80b2f6e2a6ee76f594f6c3a26b5f2e1fcb12678e Mon Sep 17 00:00:00 2001 From: Jackson Chadfield Date: Sat, 30 Sep 2023 21:11:57 +1300 Subject: [PATCH 60/73] refactor auth middleware --- .idea/runConfigurations/Backend.xml | 22 +++++ .idea/runConfigurations/Backend_Watch.xml | 4 +- backend/src/api/error.rs | 4 +- backend/src/api/middleware.rs | 112 +++++++++++++++------- 4 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 .idea/runConfigurations/Backend.xml diff --git a/.idea/runConfigurations/Backend.xml b/.idea/runConfigurations/Backend.xml new file mode 100644 index 0000000..0afd32d --- /dev/null +++ b/.idea/runConfigurations/Backend.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Backend_Watch.xml b/.idea/runConfigurations/Backend_Watch.xml index 81be16a..90d630e 100644 --- a/.idea/runConfigurations/Backend_Watch.xml +++ b/.idea/runConfigurations/Backend_Watch.xml @@ -9,7 +9,9 @@