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/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 + 7 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 server/src/models/auth.rs create mode 100644 server/src/routes/auth.rs (limited to 'server/src') 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