Skip to content

Commit

Permalink
Merge pull request #5 from MiSArch/authentication
Browse files Browse the repository at this point in the history
Initial authentication setup
  • Loading branch information
legendofa authored Mar 20, 2024
2 parents ea35618 + 521a5da commit f90d376
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 74 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ mongodb-cursor-pagination = "0.3.2"
tonic = "0.8"
json = "0.12.4"
log = "0.4.20"
simple_logger = "4.3.3"
simple_logger = "4.3.3"
serde_json = "1.0.113"
79 changes: 79 additions & 0 deletions src/authentication.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use async_graphql::{Context, Error, Result};
use axum::http::HeaderMap;
use bson::Uuid;
use serde::Deserialize;

/// Authorized-User HTTP header.
#[derive(Deserialize, Debug)]
pub struct AuthorizedUserHeader {
id: Uuid,
roles: Vec<Role>,
}

/// Extraction of AuthorizedUserHeader from HeaderMap.
impl TryFrom<&HeaderMap> for AuthorizedUserHeader {
type Error = Error;

/// Tries to extract the AuthorizedUserHeader from a HeaderMap.
///
/// Returns a GraphQL Error if the extraction fails.
fn try_from(header_map: &HeaderMap) -> Result<Self, Self::Error> {
if let Some(authenticate_user_header_value) = header_map.get("Authorized-User") {
if let Ok(authenticate_user_header_str) = authenticate_user_header_value.to_str() {
let authenticate_user_header: AuthorizedUserHeader =
serde_json::from_str(authenticate_user_header_str)?;
return Ok(authenticate_user_header);
}
}
Err(Error::new("Authorized-User header could not be parsed."))
}
}

/// Role of user.
#[derive(Deserialize, Debug, PartialEq, Clone, Copy)]
#[serde(rename_all = "snake_case")]
enum Role {
Buyer,
Admin,
Employee,
}

impl Role {
/// Defines if user has a permissive role.
fn is_permissive(self) -> bool {
match self {
Self::Buyer => false,
Self::Admin => true,
Self::Employee => true,
}
}
}

/// Authenticate user of UUID for a Context.
pub fn authenticate_user(ctx: &Context, id: Uuid) -> Result<()> {
match ctx.data::<AuthorizedUserHeader>() {
Ok(authenticate_user_header) => check_permissions(&authenticate_user_header, id),
Err(_) => Err(Error::new("Authorized-User header could not be parsed.")),
}
}

/// Check if user of UUID has a valid permission according to the AuthorizedUserHeader.
///
/// Permission is valid if the user has `Role::Buyer` and the same UUID as provided in the function parameter.
/// Permission is valid if the user has a permissive role: `user.is_permissive() == true`, regardless of the users UUID.
pub fn check_permissions(authenticate_user_header: &AuthorizedUserHeader, id: Uuid) -> Result<()> {
if authenticate_user_header
.roles
.iter()
.any(|r| r.is_permissive())
|| authenticate_user_header.id == id
{
return Ok(());
} else {
let message = format!(
"Authentication failed for user of UUID: `{}`. Operation not permitted.",
authenticate_user_header.id
);
return Err(Error::new(message));
}
}
2 changes: 1 addition & 1 deletion src/http_event_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub struct Pubsub {
/// Reponse data to send to Dapr when receiving an event.
#[derive(Serialize)]
pub struct TopicEventResponse {
pub status: i32,
pub status: u8,
}

/// Default status is `0` -> Ok, according to Dapr specs.
Expand Down
27 changes: 25 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use std::{collections::HashSet, env, fs::File, io::Write};
use async_graphql::{
extensions::Logger, http::GraphiQLSource, EmptySubscription, SDLExportOptions, Schema,
};
use async_graphql_axum::GraphQL;
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use authentication::AuthorizedUserHeader;
use axum::{
extract::State,
http::HeaderMap,
response::{self, IntoResponse},
routing::{get, post},
Router, Server,
Expand All @@ -29,6 +32,8 @@ use mutation::Mutation;
mod user;
use user::User;

mod authentication;

mod http_event_service;
use http_event_service::{list_topic_subscriptions, on_topic_event, HttpEventServiceState};

Expand Down Expand Up @@ -119,6 +124,22 @@ async fn main() -> std::io::Result<()> {
Ok(())
}

/// Describes the handler for GraphQL requests.
///
/// Parses the "Authenticate-User" header and writes it in the context data of the specfic request.
/// Then executes the GraphQL schema with the request.
async fn graphql_handler(
State(schema): State<Schema<Query, Mutation, EmptySubscription>>,
headers: HeaderMap,
req: GraphQLRequest,
) -> GraphQLResponse {
let mut req = req.into_inner();
if let Ok(authenticate_user_header) = AuthorizedUserHeader::try_from(&headers) {
req = req.data(authenticate_user_header);
}
schema.execute(req).await.into()
}

/// Starts shoppingcart service on port 8000.
async fn start_service() {
let client = db_connection().await;
Expand All @@ -130,7 +151,9 @@ async fn start_service() {
.enable_federation()
.finish();

let graphiql = Router::new().route("/", get(graphiql).post_service(GraphQL::new(schema)));
let graphiql = Router::new()
.route("/", get(graphiql).post(graphql_handler))
.with_state(schema);
let dapr_router = build_dapr_router(db_client).await;
let app = Router::new().merge(graphiql).merge(dapr_router);

Expand Down
30 changes: 19 additions & 11 deletions src/mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ use mongodb::{
Collection, Database,
};

use crate::mutation_input_structs::ShoppingCartItemInput;
use crate::mutation_input_structs::UpdateShoppingCartItemInput;
use crate::query::query_shoppingcart_item;
use crate::query::query_shoppingcart_item_by_product_variant_id_and_user_id;
use crate::shoppingcart_item::ShoppingCartItem;
use crate::{mutation_input_structs::AddShoppingCartItemInput, query::query_user};
use crate::{authentication::authenticate_user, mutation_input_structs::ShoppingCartItemInput};
use crate::{mutation_input_structs::CreateShoppingCartItemInput, query::query_user};
use crate::{
mutation_input_structs::UpdateShoppingCartItemInput, query::query_shoppingcart_item_user,
};

use crate::user::User;
use crate::{
Expand All @@ -34,7 +36,8 @@ impl Mutation {
ctx: &Context<'a>,
#[graphql(desc = "UpdateShoppingCartInput")] input: UpdateShoppingCartInput,
) -> Result<ShoppingCart> {
let db_client = ctx.data_unchecked::<Database>();
authenticate_user(&ctx, input.id)?;
let db_client = ctx.data::<Database>()?;
let collection: Collection<User> = db_client.collection::<User>("users");
let product_variant_collection: Collection<ProductVariant> =
db_client.collection::<ProductVariant>("product_variants");
Expand All @@ -53,12 +56,13 @@ impl Mutation {
/// Adds shoppingcart item to a shopping cart.
///
/// Queries for existing item, otherwise adds new shoppingcart item.
async fn add_shoppingcart_item<'a>(
async fn create_shoppingcart_item<'a>(
&self,
ctx: &Context<'a>,
#[graphql(desc = "AddShoppingCartItemInput")] input: AddShoppingCartItemInput,
#[graphql(desc = "CreateShoppingCartItemInput")] input: CreateShoppingCartItemInput,
) -> Result<ShoppingCartItem> {
let db_client = ctx.data_unchecked::<Database>();
authenticate_user(&ctx, input.id)?;
let db_client = ctx.data::<Database>()?;
let collection: Collection<User> = db_client.collection::<User>("users");
let product_variant_collection: Collection<ProductVariant> =
db_client.collection::<ProductVariant>("product_variants");
Expand All @@ -85,8 +89,10 @@ impl Mutation {
ctx: &Context<'a>,
#[graphql(desc = "UpdateShoppingCartItemInput")] input: UpdateShoppingCartItemInput,
) -> Result<ShoppingCartItem> {
let db_client = ctx.data_unchecked::<Database>();
let db_client = ctx.data::<Database>()?;
let collection: Collection<User> = db_client.collection::<User>("users");
let user = query_shoppingcart_item_user(&collection, input.id).await?;
authenticate_user(&ctx, user._id)?;
if let Err(_) = collection
.update_one(
doc! {"shoppingcart.internal_shoppingcart_items._id": input.id },
Expand All @@ -111,8 +117,10 @@ impl Mutation {
ctx: &Context<'a>,
#[graphql(desc = "UUID of shoppingcart item to delete.")] id: Uuid,
) -> Result<bool> {
let db_client = ctx.data_unchecked::<Database>();
let db_client = ctx.data::<Database>()?;
let collection: Collection<User> = db_client.collection::<User>("users");
let user = query_shoppingcart_item_user(&collection, id).await?;
authenticate_user(&ctx, user._id)?;
if let Err(_) = collection
.update_one(
doc! {"shoppingcart.internal_shoppingcart_items._id": id },
Expand Down Expand Up @@ -203,10 +211,10 @@ async fn validate_shopping_cart_items(
/// Adds shoppingcart item to MongoDB collection.
///
/// * `collection` - MongoDB collection to add the shoppingcart item to.
/// * `input` - `AddShoppingCartItemInput`.
/// * `input` - `CreateShoppingCartItemInput`.
async fn add_shoppingcart_item_to_monogdb(
collection: &Collection<User>,
input: AddShoppingCartItemInput,
input: CreateShoppingCartItemInput,
) -> Result<ShoppingCartItem> {
let current_timestamp = DateTime::now();
let shoppingcart_item = ShoppingCartItem {
Expand Down
2 changes: 1 addition & 1 deletion src/mutation_input_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct ShoppingCartItemInput {
}

#[derive(SimpleObject, InputObject)]
pub struct AddShoppingCartItemInput {
pub struct CreateShoppingCartItemInput {
/// UUID of user owning the shopping cart.
pub id: Uuid,
/// ShoppingCartItem in shoppingcart to update
Expand Down
Loading

0 comments on commit f90d376

Please sign in to comment.