diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7bd5b4b..0a96f0e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -23,6 +23,18 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "atoi" version = "2.0.0" @@ -96,13 +108,43 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "form_urlencoded", + "futures-core", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "serde_core", + "serde_html_form", + "serde_path_to_error", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backend" version = "0.1.0" dependencies = [ + "argon2", "axum", + "axum-extra", "chrono", "dotenv", + "jsonwebtoken", "serde", "serde_json", "sqlx", @@ -130,6 +172,15 @@ dependencies = [ "serde_core", ] +[[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" @@ -202,6 +253,17 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -278,6 +340,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -544,6 +615,30 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.5.0" @@ -824,6 +919,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "base64", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -956,6 +1067,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -972,6 +1093,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + [[package]] name = "num-integer" version = "0.1.46" @@ -1081,6 +1208,27 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1144,6 +1292,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1354,6 +1508,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_html_form" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.149" @@ -1428,6 +1595,18 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -1749,6 +1928,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d8a3fe1..e211d01 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,9 +5,12 @@ edition = "2024" [dependencies] axum = "0.8.9" +axum-extra = { version = "0.12.6", features = ["cookie", "typed-header", "form"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls", "chrono"] } tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros"] } dotenv = "0.15.0" chrono = { version = "0.4.44", features = ["serde"] } +jsonwebtoken = "10.3.0" +argon2 = "0.5.3" diff --git a/backend/src/cookie/jwt.rs b/backend/src/cookie/jwt.rs new file mode 100644 index 0000000..47b84f9 --- /dev/null +++ b/backend/src/cookie/jwt.rs @@ -0,0 +1,37 @@ +use axum::{Json, http::StatusCode}; +use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; +use serde::{Deserialize, Serialize}; + +use crate::models::Claims; + +#[derive(Debug, Deserialize, Serialize)] +pub struct Error { + pub status: &'static str, + pub message: String, +} + +pub fn encode_token(header: &Header, id: String, key: &EncodingKey) -> String { + let now = chrono::Utc::now(); + let issued = now.timestamp() as usize; + let expires = (now + chrono::Duration::minutes(90)).timestamp() as usize; + let claims: Claims = Claims { + subject: id, + issued: issued, + expires: expires, + }; + let token = encode(header, &claims, key); + return token.expect("token return failed"); +} + +pub fn decode_token(token: String, key: &DecodingKey) -> Result)> { + let claims = decode::(&token, key, &Validation::default()) + .map_err(|_| { + let error = Error { + status: "error", + message: "Invalid Token".to_string(), + }; + (StatusCode::UNAUTHORIZED, Json(error)) + })? + .claims; + return Ok(claims); +} diff --git a/backend/src/cookie/mod.rs b/backend/src/cookie/mod.rs new file mode 100644 index 0000000..5b383e3 --- /dev/null +++ b/backend/src/cookie/mod.rs @@ -0,0 +1 @@ +mod jwt; diff --git a/backend/src/main.rs b/backend/src/main.rs index 9db9406..814b13e 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ #![allow(unused_imports)] +mod cookie; mod handlers; mod models; mod router; diff --git a/migrations/20260422094717_user_table.down.sql b/migrations/20260422094717_user_table.down.sql new file mode 100644 index 0000000..cc1f647 --- /dev/null +++ b/migrations/20260422094717_user_table.down.sql @@ -0,0 +1 @@ +DROP TABLE users;