diff options
| author | Santo Cariotti <santo@dcariotti.me> | 2024-08-21 17:34:58 +0200 | 
|---|---|---|
| committer | Santo Cariotti <santo@dcariotti.me> | 2024-08-21 17:34:58 +0200 | 
| commit | a76200bb7adb6189d84e0e98d6233a470ebeee98 (patch) | |
| tree | f0c1dd5aba99ff33d01a5780f695cf65a9dcc411 /src/graphql | |
| parent | a92fb07d23fb2268a6f4e650c5cbd00ad993e760 (diff) | |
Authentication for endpoints
Diffstat (limited to 'src/graphql')
| -rw-r--r-- | src/graphql/query.rs | 2 | ||||
| -rw-r--r-- | src/graphql/routes.rs | 9 | ||||
| -rw-r--r-- | src/graphql/types/jwt.rs | 55 | ||||
| -rw-r--r-- | src/graphql/types/user.rs | 41 | 
4 files changed, 79 insertions, 28 deletions
| diff --git a/src/graphql/query.rs b/src/graphql/query.rs index ed83a9f..254dab6 100644 --- a/src/graphql/query.rs +++ b/src/graphql/query.rs @@ -1,4 +1,4 @@ -use crate::graphql::types::user; +use crate::{errors::AppError, graphql::types::user};  use async_graphql::{Context, Object};  pub struct Query; diff --git a/src/graphql/routes.rs b/src/graphql/routes.rs index e15267e..a566c65 100644 --- a/src/graphql/routes.rs +++ b/src/graphql/routes.rs @@ -2,11 +2,14 @@ use crate::graphql::mutation::Mutation;  use crate::graphql::query::Query;  use async_graphql::{EmptySubscription, Schema};  use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; -use std::sync::Arc; +use axum::extract::Extension; + +use super::types::jwt::Authentication;  pub async fn graphql_handler( -    schema: Arc<Schema<Query, Mutation, EmptySubscription>>, +    schema: Extension<Schema<Query, Mutation, EmptySubscription>>, +    auth: Authentication,      req: GraphQLRequest,  ) -> GraphQLResponse { -    schema.execute(req.into_inner()).await.into() +    schema.execute(req.0.data(auth)).await.into()  } diff --git a/src/graphql/types/jwt.rs b/src/graphql/types/jwt.rs index 932f7fd..c118622 100644 --- a/src/graphql/types/jwt.rs +++ b/src/graphql/types/jwt.rs @@ -1,5 +1,10 @@  use crate::errors::AppError;  use async_graphql::{InputObject, SimpleObject}; +use axum::{async_trait, extract::FromRequestParts, http::request::Parts}; +use axum_extra::{ +    headers::{authorization::Bearer, Authorization}, +    typed_header::TypedHeader, +};  use chrono::{Duration, Local};  use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};  use once_cell::sync::Lazy; @@ -7,7 +12,7 @@ use serde::{Deserialize, Serialize};  struct Keys {      encoding: EncodingKey, -    _decoding: DecodingKey, +    decoding: DecodingKey,  }  static KEYS: Lazy<Keys> = Lazy::new(|| { @@ -19,20 +24,25 @@ impl Keys {      fn new(secret: &[u8]) -> Self {          Self {              encoding: EncodingKey::from_secret(secret), -            _decoding: DecodingKey::from_secret(secret), +            decoding: DecodingKey::from_secret(secret),          }      }  } -/// Claims struct -#[derive(Serialize, Deserialize)] +/// Claims struct. +#[derive(Serialize, Deserialize, Debug, Clone)]  pub struct Claims { -    /// ID from the user model -    pub user_id: i32, -    /// Expiration timestamp +    user_id: i32,      exp: usize,  } +/// Authentication enum +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Authentication { +    Logged(Claims), +    NotLogged, +} +  impl Claims {      /// Create a new Claim using the `user_id` and the current timestamp + 2 days      pub fn new(user_id: i32) -> Self { @@ -77,3 +87,34 @@ impl AuthBody {          }      }  } + +#[async_trait] +impl<S> FromRequestParts<S> for Authentication +where +    S: Send + Sync, +{ +    type Rejection = AppError; + +    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> { +        // Extract the Authorization header +        match TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, &()).await { +            Ok(TypedHeader(Authorization(bearer))) => { +                // Decode the token +                let token_data = +                    decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default()) +                        .map_err(|err| match err.kind() { +                            jsonwebtoken::errors::ErrorKind::ExpiredSignature => { +                                AppError::InvalidToken +                            } +                            _ => { +                                eprintln!("{err:?}"); +                                return AppError::Unauthorized; +                            } +                        })?; + +                Ok(Self::Logged(token_data.claims)) +            } +            Err(_) => Ok(Self::NotLogged), +        } +    } +} diff --git a/src/graphql/types/user.rs b/src/graphql/types/user.rs index bf9080f..b675f1f 100644 --- a/src/graphql/types/user.rs +++ b/src/graphql/types/user.rs @@ -2,6 +2,8 @@ use crate::state::AppState;  use async_graphql::{Context, Object};  use serde::{Deserialize, Serialize}; +use super::jwt::Authentication; +  #[derive(Clone, Debug, Serialize, Deserialize)]  pub struct User {      pub id: i32, @@ -32,21 +34,26 @@ impl User {  pub async fn get_users<'ctx>(ctx: &Context<'ctx>) -> Result<Option<Vec<User>>, String> {      let state = ctx.data::<AppState>().expect("Can't connect to db");      let client = &*state.client; - -    let rows = client -        .query("SELECT id, email, password, is_admin FROM users", &[]) -        .await -        .unwrap(); - -    let users: Vec<User> = rows -        .iter() -        .map(|row| User { -            id: row.get("id"), -            email: row.get("email"), -            password: row.get("password"), -            is_admin: row.get("is_admin"), -        }) -        .collect(); - -    Ok(Some(users)) +    let auth: &Authentication = ctx.data().unwrap(); +    match auth { +        Authentication::NotLogged => Err("Unauthorized".to_string()), +        Authentication::Logged(_claims) => { +            let rows = client +                .query("SELECT id, email, password, is_admin FROM users", &[]) +                .await +                .unwrap(); + +            let users: Vec<User> = rows +                .iter() +                .map(|row| User { +                    id: row.get("id"), +                    email: row.get("email"), +                    password: row.get("password"), +                    is_admin: row.get("is_admin"), +                }) +                .collect(); + +            Ok(Some(users)) +        } +    }  } | 
