From eedc536c23c3230c6ebeac8b495b2cfef0317830 Mon Sep 17 00:00:00 2001 From: Santo Cariotti Date: Tue, 23 Aug 2022 18:10:18 +0200 Subject: Auth login --- server/Cargo.lock | 67 ++++++++++++++++++++++++++++++++++++++++++++--- server/Cargo.toml | 1 + server/src/errors.rs | 7 +++++ server/src/main.rs | 4 ++- server/src/models/auth.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++ server/src/models/mod.rs | 1 + server/src/models/user.rs | 22 +++++++++++++++- server/src/routes/auth.rs | 22 ++++++++++++++++ server/src/routes/mod.rs | 1 + 9 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 server/src/models/auth.rs create mode 100644 server/src/routes/auth.rs (limited to 'server') diff --git a/server/Cargo.lock b/server/Cargo.lock index 9148b0d..514f558 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -212,6 +221,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.44", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.3" @@ -413,7 +443,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -564,6 +594,19 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "idna" version = "0.2.3" @@ -675,6 +718,7 @@ version = "0.1.0" dependencies = [ "async-trait", "axum", + "chrono", "http-body", "jsonwebtoken", "once_cell", @@ -745,7 +789,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1218,7 +1262,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time", + "time 0.3.13", ] [[package]] @@ -1409,6 +1453,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.13" @@ -1750,6 +1805,12 @@ 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" diff --git a/server/Cargo.toml b/server/Cargo.toml index 8527fdb..f5cf075 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -19,3 +19,4 @@ tower-http = { version = "0.3.4", features = ["trace", "compression-br", "propag sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres" ] } sha256 = "1.0.3" validator = { version = "0.16.0", features = ["derive"] } +chrono = "0.4" diff --git a/server/src/errors.rs b/server/src/errors.rs index 9221fea..d991132 100644 --- a/server/src/errors.rs +++ b/server/src/errors.rs @@ -9,6 +9,8 @@ pub enum AppError { Generic, Database, BadRequest(String), + NotFound, + TokenCreation, } impl IntoResponse for AppError { @@ -23,6 +25,11 @@ impl IntoResponse for AppError { "Error with database connection".to_string(), ), AppError::BadRequest(value) => (StatusCode::BAD_REQUEST, value), + AppError::NotFound => (StatusCode::NOT_FOUND, "Element not found".to_string()), + AppError::TokenCreation => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Token creation error".to_string(), + ), }; let body = Json(json!({ diff --git a/server/src/main.rs b/server/src/main.rs index e2536ca..3436e2c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -31,7 +31,9 @@ async fn create_app() -> Router { logger::setup(); let _ = db::setup().await; - let api_routes = Router::new().nest("/users", routes::user::create_route()); + let api_routes = Router::new() + .nest("/users", routes::user::create_route()) + .nest("/auth", routes::auth::create_route()); Router::new() .nest("/v1", api_routes) diff --git a/server/src/models/auth.rs b/server/src/models/auth.rs new file mode 100644 index 0000000..03b198b --- /dev/null +++ b/server/src/models/auth.rs @@ -0,0 +1,63 @@ +use crate::errors::AppError; +use chrono::{Duration, Local}; +use jsonwebtoken::{encode, DecodingKey, EncodingKey, Header, Validation}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +struct Keys { + encoding: EncodingKey, + decoding: DecodingKey, +} + +#[derive(Serialize, Deserialize)] +pub struct Claims { + user_id: i32, + exp: usize, +} + +#[derive(Serialize)] +pub struct AuthBody { + access_token: String, + token_type: String, +} + +static KEYS: Lazy = Lazy::new(|| { + let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); + Keys::new(secret.as_bytes()) +}); + +impl Keys { + fn new(secret: &[u8]) -> Self { + Self { + encoding: EncodingKey::from_secret(secret), + decoding: DecodingKey::from_secret(secret), + } + } +} + +impl Claims { + pub fn new(user_id: i32) -> Self { + let expiration = Local::now() + Duration::days(2); + + Self { + user_id, + exp: expiration.timestamp() as usize, + } + } + + pub fn get_token(&self) -> Result { + let token = encode(&Header::default(), &self, &KEYS.encoding) + .map_err(|_| AppError::TokenCreation)?; + + Ok(token) + } +} + +impl AuthBody { + pub fn new(access_token: String) -> Self { + Self { + access_token, + token_type: "Bearer".to_string(), + } + } +} diff --git a/server/src/models/mod.rs b/server/src/models/mod.rs index 22d12a3..f9bae3d 100644 --- a/server/src/models/mod.rs +++ b/server/src/models/mod.rs @@ -1 +1,2 @@ +pub mod auth; pub mod user; diff --git a/server/src/models/user.rs b/server/src/models/user.rs index 964f04a..38ec121 100644 --- a/server/src/models/user.rs +++ b/server/src/models/user.rs @@ -16,7 +16,7 @@ pub struct User { #[derive(Deserialize, Serialize)] pub struct UserList { - id: i32, + pub id: i32, email: String, is_staff: Option, } @@ -61,6 +61,26 @@ impl User { Ok(rec) } + pub async fn find(user: User) -> Result { + let pool = unsafe { get_client() }; + + let crypted_password = sha256::digest(user.password); + + let rec = sqlx::query_as!( + UserList, + r#" + SELECT id, email, is_staff FROM "users" + WHERE email = $1 AND password = $2 + "#, + user.email, + crypted_password + ) + .fetch_one(pool) + .await?; + + Ok(rec) + } + pub async fn list() -> Result, AppError> { let pool = unsafe { get_client() }; let rows = sqlx::query_as!(UserList, r#"SELECT id, email, is_staff FROM users"#) diff --git a/server/src/routes/auth.rs b/server/src/routes/auth.rs new file mode 100644 index 0000000..629ed33 --- /dev/null +++ b/server/src/routes/auth.rs @@ -0,0 +1,22 @@ +use crate::errors::AppError; +use crate::models::{ + auth::{AuthBody, Claims}, + user::{User, UserCreate}, +}; +use axum::{routing::post, Json, Router}; + +pub fn create_route() -> Router { + Router::new().route("/login", post(make_login)) +} + +async fn make_login(Json(payload): Json) -> Result, AppError> { + let user = User::new(payload.email, payload.password); + match User::find(user).await { + Ok(user) => { + let claims = Claims::new(user.id); + let token = claims.get_token()?; + Ok(Json(AuthBody::new(token))) + } + Err(_) => Err(AppError::NotFound), + } +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 22d12a3..f9bae3d 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -1 +1,2 @@ +pub mod auth; pub mod user; -- cgit v1.2.3-18-g5258