Improve extractors and extract some funcs

This commit is contained in:
Sofía Aritz 2024-10-14 23:02:27 +02:00
parent 546e883a9c
commit fe62b28a03
Signed by: sofia
GPG key ID: 90B5116E3542B28F
7 changed files with 103 additions and 58 deletions

View file

@ -0,0 +1,19 @@
use crate::env;
use jsonwebtoken::{TokenData, Header, Validation};
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtUser {
pub uid: String,
pub email: String,
pub name: String,
pub exp: u64,
}
pub fn encode_jwt(claims: &JwtUser) -> jsonwebtoken::errors::Result<String> {
jsonwebtoken::encode(&Header::new(*env::jwt_alg()), claims, &env::jwt_secret().0)
}
pub fn decode_jwt(jwt: &str) -> jsonwebtoken::errors::Result<TokenData<JwtUser>> {
jsonwebtoken::decode::<JwtUser>(jwt, &env::jwt_secret().1, &Validation::new(*env::jwt_alg()))
}

View file

@ -0,0 +1,20 @@
use diesel::{SqliteConnection, r2d2::{ConnectionManager, PooledConnection}, RunQueryDsl, QueryDsl, SelectableHelper, ExpressionMethods, OptionalExtension};
use crate::database::models::User;
pub fn user(user_id: &str, conn: &mut PooledConnection<ConnectionManager<SqliteConnection>>) -> diesel::result::QueryResult<User> {
use crate::database::schema::users::dsl::users;
users
.find(user_id)
.select(User::as_select())
.first(conn)
}
pub fn user_by_email(email: &str, conn: &mut PooledConnection<ConnectionManager<SqliteConnection>>) -> diesel::result::QueryResult<Option<User>> {
use crate::database::schema::users::dsl as users;
users::users
.filter(users::email.eq(email))
.limit(1)
.select(User::as_select())
.first(conn)
.optional()
}

View file

@ -23,6 +23,7 @@ use crate::env;
pub mod models;
pub mod schema;
pub mod list;
pub mod actions;
pub fn create_connection_pool() -> Result<Pool<ConnectionManager<SqliteConnection>>, r2d2::Error> {

View file

@ -117,12 +117,12 @@ pub struct SessionKey {
#[diesel(table_name = schema::users)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct User {
id: String,
created_at: NaiveDateTime,
last_connected_at: NaiveDateTime,
email: String,
password: String,
name: String,
limits: String,
assets: String,
pub id: String,
pub created_at: NaiveDateTime,
pub last_connected_at: NaiveDateTime,
pub email: String,
pub password: String,
pub name: String,
pub limits: String,
pub assets: String,
}

View file

@ -1,21 +1,12 @@
use axum::{async_trait, extract::FromRequestParts, http::{header::AUTHORIZATION, request::Parts, StatusCode}};
use jsonwebtoken::Validation;
use serde::{Serialize, Deserialize};
use tracing::warn;
use crate::env;
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtUser {
pub uid: String,
pub email: String,
pub name: String,
pub exp: u64,
}
pub struct ExtractUser(pub JwtUser);
use tracing::{warn, error};
use crate::database::{actions, models::User};
use crate::AppState;
use crate::auth::JwtUser;
pub struct ExtractJwtUser(pub JwtUser);
#[async_trait]
impl<S> FromRequestParts<S> for ExtractUser
impl<S> FromRequestParts<S> for ExtractJwtUser
where
S: Send + Sync,
{
@ -25,7 +16,7 @@ where
if let Some(authorization) = parts.headers.get(AUTHORIZATION) {
if let Ok(authorization) = authorization.to_str() {
let token = authorization.replacen("Bearer ", "", 1);
match jsonwebtoken::decode::<JwtUser>(&token, &env::jwt_secret().1, &Validation::new(*env::jwt_alg())) {
match crate::auth::decode_jwt(&token) {
Ok(claims) => Ok(Self(claims.claims)),
Err(err) => {
warn!("token couldn't be decoded: {:?}", err);
@ -37,8 +28,32 @@ where
Err((StatusCode::BAD_REQUEST, "Invalid `AUTHORIZATION` header"))
}
} else {
warn!("missin authorization header");
warn!("missing authorization header");
Err((StatusCode::BAD_REQUEST, "Missing `AUTHORIZATION` header"))
}
}
}
pub struct ExtractUser(pub User);
#[async_trait]
impl FromRequestParts<AppState> for ExtractUser
{
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, Self::Rejection> {
let jwt_user = ExtractJwtUser::from_request_parts(parts, state).await?;
if let Ok(mut conn) = state.pool.get() {
if let Ok(user) = actions::user(&jwt_user.0.uid, &mut conn) {
Ok(Self(user))
} else {
error!("JWT user does not exist in database");
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
}
} else {
error!("failed to obtain pooled connection");
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
}
}
}

View file

@ -1,42 +1,36 @@
use std::time::SystemTime;
use argon2::{Argon2, PasswordHasher, password_hash::{rand_core::OsRng, SaltString}};
use axum::{extract::State, http::StatusCode, routing::{get, post}, Json, Router};
use chrono::Utc;
use diesel::{/*query_dsl::methods::{FindDsl, SelectDsl, FilterDsl},*/ SelectableHelper, RunQueryDsl, ExpressionMethods, QueryDsl, OptionalExtension};
use jsonwebtoken::Header;
use chrono::{Utc, NaiveDateTime};
use diesel::{RunQueryDsl, ExpressionMethods};
use tracing::{error, info};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::{database::models::User, env, http::extractors::auth::{ExtractUser, JwtUser}, AppState};
use crate::{database::actions, http::extractors::auth::ExtractUser, auth::JwtUser, AppState};
pub fn auth_router() -> Router<AppState> {
Router::new()
.route("/auth/account", get(account))
.route("/auth/register", post(register))
}
async fn account(ExtractUser(jwt_user): ExtractUser, State(state): State<AppState>) -> Result<Json<User>, StatusCode> {
use crate::database::schema::users::dsl::users;
#[derive(Debug, Serialize)]
struct AccountResponse {
id: String,
created_at: NaiveDateTime,
last_connected_at: NaiveDateTime,
email: String,
name: String,
}
if let Ok(mut conn) = state.pool.get() {
let user = users
.find(jwt_user.uid)
.select(User::as_select())
.first(&mut conn);
if let Ok(user) = user {
Ok(Json(user))
} else {
error!("JWT user does not exist in database");
Err(StatusCode::INTERNAL_SERVER_ERROR)
}
} else {
error!("failed to obtain pooled connection");
Err(StatusCode::INTERNAL_SERVER_ERROR)
}
async fn account(ExtractUser(user): ExtractUser) -> Result<Json<AccountResponse>, StatusCode> {
Ok(Json(AccountResponse {
id: user.id,
created_at: user.created_at,
last_connected_at: user.last_connected_at,
email: user.email,
name: user.name,
}))
}
#[derive(Debug, Deserialize)]
@ -56,12 +50,7 @@ async fn register(State(state): State<AppState>, Json(req): Json<RegisterRequest
use crate::database::schema::limits::dsl as limits;
if let Ok(mut conn) = state.pool.get() {
let user = users::users
.filter(users::email.eq(&req.email))
.limit(1)
.select(User::as_select())
.first(&mut conn)
.optional();
let user = actions::user_by_email(&req.email, &mut conn);
if user.is_err() {
error!("failed to retrieve potential existing user from database: {}, error: {:?}", &req.email, user.err());
@ -112,7 +101,7 @@ async fn register(State(state): State<AppState>, Json(req): Json<RegisterRequest
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
match jsonwebtoken::encode(&Header::new(*env::jwt_alg()), &JwtUser {
match crate::auth::encode_jwt(&JwtUser {
uid: user_id,
email: req.email,
name: req.name,
@ -120,7 +109,7 @@ async fn register(State(state): State<AppState>, Json(req): Json<RegisterRequest
.duration_since(SystemTime::UNIX_EPOCH)
.expect("time went backwards")
.as_secs() + 180 * 24 * 3600,
}, &env::jwt_secret().0) {
}) {
Ok(token) => Ok(Json(RegisterResponse { token })),
Err(err) => {
error!("token couldn't be encoded: {:?}", err);

View file

@ -28,6 +28,7 @@ use tokio::time::Duration;
mod database;
mod env;
mod http;
mod auth;
#[derive(Clone)]
struct AppState {