From 8a1d0cf0bb734c744f475d273d61a0cee81962a2 Mon Sep 17 00:00:00 2001 From: Franek Date: Thu, 17 Apr 2025 13:16:03 +0200 Subject: [PATCH] add support for users/JWT --- Cargo.lock | 372 +++++++++++++++++- Cargo.toml | 11 +- .../2025-04-17-092741_create_user/down.sql | 1 + .../2025-04-17-092741_create_user/up.sql | 11 + src/config.rs | 1 + src/database/models/mod.rs | 3 +- src/database/models/user.rs | 22 ++ src/main.rs | 3 +- src/schema.rs | 17 + src/utils/jwt.rs | 90 +++++ src/utils/mod.rs | 1 + src/v1/auth.rs | 63 +++ src/v1/mod.rs | 4 +- 13 files changed, 593 insertions(+), 6 deletions(-) create mode 100644 migrations/2025-04-17-092741_create_user/down.sql create mode 100644 migrations/2025-04-17-092741_create_user/up.sql create mode 100644 src/database/models/user.rs create mode 100644 src/utils/jwt.rs create mode 100644 src/utils/mod.rs create mode 100644 src/v1/auth.rs diff --git a/Cargo.lock b/Cargo.lock index 9c72d1e..9e764a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,7 +29,7 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "base64", + "base64 0.22.1", "bitflags", "brotli", "bytes", @@ -224,17 +224,48 @@ dependencies = [ "alloc-no-stdlib", ] +[[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 = "api" version = "1.0.0" dependencies = [ "actix-web", + "chrono", "diesel", + "jsonwebtoken", + "rust-argon2", "serde", + "serde_json", "tokio", "toml", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "autocfg" version = "1.4.0" @@ -256,6 +287,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -268,6 +305,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -298,6 +346,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "byteorder" version = "1.5.0" @@ -336,6 +390,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "cookie" version = "0.16.2" @@ -347,6 +422,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -448,6 +529,7 @@ checksum = "470eb10efc8646313634c99bb1593f402a6434cbd86e266770c6e39219adb86a" dependencies = [ "bitflags", "byteorder", + "chrono", "diesel_derives", "itoa", "pq-sys", @@ -603,6 +685,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.1" @@ -675,6 +770,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -851,6 +970,31 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -935,12 +1079,40 @@ dependencies = [ "windows-sys", ] +[[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-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -979,6 +1151,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1084,7 +1266,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.1", ] [[package]] @@ -1131,12 +1313,43 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rust-argon2" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" +dependencies = [ + "base64 0.21.7", + "blake2b_simd", + "constant_time_eq", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.20" @@ -1237,6 +1450,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -1296,6 +1521,26 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.39" @@ -1463,6 +1708,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -1513,6 +1764,123 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 7684633..437901c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["edition2024"] + [package] name = "api" description = "API for sador.me website" @@ -7,6 +9,13 @@ edition = "2024" [dependencies] tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } -diesel = { version = "2.1", features = ["postgres", "r2d2"] } +diesel = { version = "2.1", features = ["postgres", "r2d2", "chrono"] } +rust-argon2 = "2" +jsonwebtoken = "9" +serde_json = "1" actix-web = "4" toml = "0.8" + +[dependencies.chrono] +version = "0.4" +features = ["serde"] \ No newline at end of file diff --git a/migrations/2025-04-17-092741_create_user/down.sql b/migrations/2025-04-17-092741_create_user/down.sql new file mode 100644 index 0000000..441087a --- /dev/null +++ b/migrations/2025-04-17-092741_create_user/down.sql @@ -0,0 +1 @@ +DROP TABLE users; \ No newline at end of file diff --git a/migrations/2025-04-17-092741_create_user/up.sql b/migrations/2025-04-17-092741_create_user/up.sql new file mode 100644 index 0000000..ae30b8b --- /dev/null +++ b/migrations/2025-04-17-092741_create_user/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username TEXT NOT NULL, + password TEXT NOT NULL, + power_level SMALLINT NOT NULL, + is_owner BOOLEAN NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP +); + +CREATE INDEX idx_username ON users(username); \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index dfb7db7..65093e3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use serde::Deserialize; pub struct Config { pub database_url: String, pub api_version: String, + pub jwt_secret: String, pub host: String, pub port: u16 } diff --git a/src/database/models/mod.rs b/src/database/models/mod.rs index f66bdac..99546ae 100644 --- a/src/database/models/mod.rs +++ b/src/database/models/mod.rs @@ -1 +1,2 @@ -pub mod post; \ No newline at end of file +pub mod post; +pub mod user; \ No newline at end of file diff --git a/src/database/models/user.rs b/src/database/models/user.rs new file mode 100644 index 0000000..a862c4d --- /dev/null +++ b/src/database/models/user.rs @@ -0,0 +1,22 @@ +use serde::{Serialize, Deserialize}; + +use chrono::NaiveDateTime; +use diesel::prelude::*; + +#[derive(Queryable, Serialize, Deserialize)] +pub struct User { + pub id: i32, + pub username: String, + pub password: String, + pub power_level: i16, + pub is_owner: bool, + pub created_at: NaiveDateTime, + pub updated_at: Option +} + +#[derive(Insertable)] +#[diesel(table_name = crate::schema::users)] +pub struct NewUser<'a> { + pub username: &'a str, + pub password: &'a str +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 66eca56..d1e4af9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,11 @@ use actix_web::middleware::NormalizePath; mod database; mod config; mod schema; +mod utils; mod v1; #[derive(Clone)] -struct State { +pub(crate) struct State { db: database::DbPool, config: config::Config } diff --git a/src/schema.rs b/src/schema.rs index cc46608..78e1bd1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -8,3 +8,20 @@ diesel::table! { author_id -> Int4, } } + +diesel::table! { + users (id) { + id -> Int4, + username -> Text, + password -> Text, + power_level -> Int2, + is_owner -> Bool, + created_at -> Timestamp, + updated_at -> Nullable, + } +} + +diesel::allow_tables_to_appear_in_same_query!( + posts, + users, +); diff --git a/src/utils/jwt.rs b/src/utils/jwt.rs new file mode 100644 index 0000000..0b1a20a --- /dev/null +++ b/src/utils/jwt.rs @@ -0,0 +1,90 @@ +use actix_web::{ + error::{ErrorBadRequest, ErrorInternalServerError}, + web::Data, HttpRequest, dev::Payload, + FromRequest, Error +}; + +use jsonwebtoken::{Header, EncodingKey, DecodingKey, Validation}; +use serde::{Serialize, Deserialize}; +use std::future::{self, ready}; +use chrono::Utc; + +use crate::State; + +static WEEK: i64 = 60 * 60 * 24 * 7; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub sub: String, + pub exp: usize, +} + +impl Claims { + pub fn generate_token(secret: &str, id: &str) -> String { + let now = Utc::now().timestamp(); + + let payload = Claims { + exp: (now + WEEK) as usize, + sub: id.to_string(), + }; + + jsonwebtoken::encode( + &Header::default(), + &payload, + &EncodingKey::from_secret(secret.as_ref()) + ).unwrap() + } +} + +#[derive(Deserialize, Debug, Serialize)] +pub struct AuthorizedUser { + pub token: String, + pub sub: String, +} + +impl FromRequest for AuthorizedUser { + type Error = Error; + type Future = std::future::Ready>; + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let state = req.app_data::>(); + let header = req.headers().get("Authorization"); + if header.is_none() { + return future::ready(Err(ErrorBadRequest("no Authorization header specified"))) + } + + if state.is_none() { + return future::ready(Err(ErrorInternalServerError("cannot retrieve JWT token"))) + } + + let state = state.unwrap(); + match header.unwrap().to_str() { + Ok(v) => { + let parts: Vec<&str> = v.split(" ").collect(); + + if parts.len() != 2 { + return ready(Err(ErrorBadRequest("Not Authorized"))); + } + + let claims = jsonwebtoken::decode::( + parts[1], + &DecodingKey::from_secret(state.config.jwt_secret.as_ref()), + &Validation::default() + ); + + if claims.is_err() { + return ready(Err(ErrorBadRequest("Not Authorized"))); + } + + let claims = claims.unwrap().claims; + let authorized_user = AuthorizedUser { + sub: claims.sub, + token: v.to_string() + }; + + ready(Ok(authorized_user)) + } + Err(e) => ready(Err(ErrorBadRequest(e))), + } + } +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..6dbefcf --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod jwt; \ No newline at end of file diff --git a/src/v1/auth.rs b/src/v1/auth.rs new file mode 100644 index 0000000..ad326b8 --- /dev/null +++ b/src/v1/auth.rs @@ -0,0 +1,63 @@ +use actix_web::{ + web::{ServiceConfig, Data, Json, scope}, + HttpResponse, Responder, post +}; +use argon2::verify_encoded; +use diesel::{ExpressionMethods, QueryDsl, OptionalExtension, RunQueryDsl}; +use serde::{Serialize, Deserialize}; + +use crate::{database::models::user::User, utils::jwt::Claims}; +use crate::schema::users::dsl::*; +use crate::State; + +#[derive(Serialize, Deserialize)] +pub struct UserBody { + pub username: String, + pub password: String +} + +#[derive(Serialize)] +struct TokenResponse { + ok: bool, + token: String +} + +#[post("/login")] +async fn login( + state: Data, + login_data: Json, +) -> impl Responder { + let mut conn = match state.db.get() { + Ok(conn) => conn, + Err(why) => return HttpResponse::InternalServerError() + .body(format!("database connection error: {}", why)), + }; + + let secret = &state.config.jwt_secret; + let user_result = users + .filter(username.eq(&login_data.username)) + .first::(&mut conn) + .optional(); + + match user_result { + Ok(Some(user)) => match verify_encoded(&user.password, login_data.password.as_bytes()) { + Ok(true) => { + let token = Claims::generate_token(secret.as_str(), &user.id.to_string()); + + HttpResponse::Ok().json(TokenResponse { + ok: true, + token + }) + } + Ok(false) => HttpResponse::Unauthorized().body("Invalid username or password"), + Err(err) => HttpResponse::InternalServerError().body(format!("Error verifying password: {}", err)), + }, + Ok(None) | Err(_) => HttpResponse::Unauthorized().body("Invalid username or password"), + } +} + +pub fn init_routes(cfg: &mut ServiceConfig) { + cfg.service(scope("/auth") + .service(login) + ); +} \ No newline at end of file diff --git a/src/v1/mod.rs b/src/v1/mod.rs index 639a76c..5dbe307 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -3,6 +3,7 @@ use serde::Serialize; use crate::State; mod posts; +mod auth; #[derive(Serialize)] struct PingResponse { @@ -23,5 +24,6 @@ pub fn init_routes(cfg: &mut ServiceConfig) { .service(index) .service(scope("/api/v1") .configure(posts::init_routes) + .configure(auth::init_routes) ); -} +} \ No newline at end of file