Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code review improvements #11

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker-compose-base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ services:
interval: 10s
timeout: 5s
retries: 3
command: --quiet
review-dapr:
image: "daprio/daprd:edge"
command:
Expand Down
79 changes: 0 additions & 79 deletions src/authentication.rs

This file was deleted.

95 changes: 95 additions & 0 deletions src/authorization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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 `Authorized-User` header from header map.
impl TryFrom<&HeaderMap> for AuthorizedUserHeader {
type Error = Error;

/// Tries to extract the `Authorized-User` header from a header map.
///
/// Returns a GraphQL error if the extraction fails.
fn try_from(header_map: &HeaderMap) -> Result<Self, Self::Error> {
if let Some(authorized_user_header_value) = header_map.get("Authorized-User") {
if let Ok(authorized_user_header_str) = authorized_user_header_value.to_str() {
let authorized_user_header: AuthorizedUserHeader =
serde_json::from_str(authorized_user_header_str)?;
return Ok(authorized_user_header);
}
}
Err(Error::new(
"Authorization failed. Authorized-User header is not set or 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,
}
}
}

/// Authorize user of UUID for a context.
///
/// * `context` - GraphQL context containing the `Authorized-User` header.
/// * `id` - Option of UUID of the user to authorize.
pub fn authorize_user(ctx: &Context, id: Option<Uuid>) -> Result<()> {
match ctx.data::<AuthorizedUserHeader>() {
Ok(authorized_user_header) => check_permissions(&authorized_user_header, id),
Err(_) => Err(Error::new(
"Authentication failed. Authorized-User header is not set or could not be parsed.",
)),
}
}

/// Check if user of UUID has a valid permission according to the `Authorized-User` header.
///
/// 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.
///
/// * `authorized_user_header` - `Authorized-User` header containing the users UUID and role.
/// * `id` - Option of UUID of the user to authorize.
pub fn check_permissions(
authorized_user_header: &AuthorizedUserHeader,
id: Option<Uuid>,
) -> Result<()> {
let id_contained_in_header = id
.and_then(|id| Some(authorized_user_header.id == id))
.unwrap_or(false);
if authorized_user_header
.roles
.iter()
.any(|role| role.is_permissive())
|| id_contained_in_header
{
return Ok(());
} else {
let message = format!(
"Authentication failed for user of UUID: `{}`. Operation not permitted.",
authorized_user_header.id
);
return Err(Error::new(message));
}
}
21 changes: 17 additions & 4 deletions src/http_event_service.rs → src/event/http_event_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use log::info;
use mongodb::Collection;
use serde::{Deserialize, Serialize};

use crate::{product::Product, product_variant::ProductVariant, user::User};
use crate::graphql::model::{product::Product, product_variant::ProductVariant, user::User};

/// Data to send to Dapr in order to describe a subscription.
#[derive(Serialize)]
Expand All @@ -28,21 +28,22 @@ impl Default for TopicEventResponse {
}
}

/// Relevant part of Dapr event wrapped in a CloudEnvelope.
/// Relevant part of Dapr event wrapped in a cloud envelope.
#[derive(Deserialize, Debug)]
pub struct Event<T> {
pub topic: String,
pub data: T,
}

/// Relevant part of Dapr event.data.
/// Relevant part of Dapr event data.
#[derive(Deserialize, Debug)]
pub struct EventData {
pub id: Uuid,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// Relevant part of product variant creation event data.
pub struct ProductVariantEventData {
/// Product variant UUID.
pub id: Uuid,
Expand Down Expand Up @@ -83,6 +84,9 @@ pub async fn list_topic_subscriptions() -> Result<Json<Vec<Pubsub>>, StatusCode>
}

/// HTTP endpoint to receive events.
///
/// * `state` - Service state containing database connections.
/// * `event` - Event handled by endpoint.
#[debug_handler(state = HttpEventServiceState)]
pub async fn on_topic_event(
State(state): State<HttpEventServiceState>,
Expand All @@ -101,6 +105,9 @@ pub async fn on_topic_event(
}

/// HTTP endpoint to product variant creation events.
///
/// * `state` - Service state containing database connections.
/// * `event` - Event handled by endpoint.
#[debug_handler(state = HttpEventServiceState)]
pub async fn on_product_variant_creation_event(
State(state): State<HttpEventServiceState>,
Expand All @@ -120,6 +127,9 @@ pub async fn on_product_variant_creation_event(
}

/// Add a newly created product variant to MongoDB.
///
/// * `collection` - MongoDB collection to add newly created product variant to.
/// * `product_variant` - Newly created product variant.
pub async fn add_product_variant_to_mongodb(
collection: &Collection<ProductVariant>,
product_variant: ProductVariant,
Expand All @@ -130,7 +140,10 @@ pub async fn add_product_variant_to_mongodb(
}
}

/// Create a new object: T in MongoDB.
/// Create a new object: `T` in MongoDB.
///
/// * `collection` - MongoDB collection to add newly created object to.
/// * `id` - UUID of newly created object.
pub async fn create_in_mongodb<T: Serialize + From<Uuid>>(
collection: &Collection<T>,
id: Uuid,
Expand Down
1 change: 1 addition & 0 deletions src/event/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod http_event_service;
4 changes: 4 additions & 0 deletions src/graphql/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod model;
pub mod mutation;
pub mod mutation_input_structs;
pub mod query;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use async_graphql::{OutputType, SimpleObject};
use mongodb_cursor_pagination::FindResult;

/// A base connection for an OutputType.
/// A base connection for an output type.
#[derive(SimpleObject)]
#[graphql(shareable)]
pub struct BaseConnection<T: OutputType> {
Expand All @@ -12,8 +13,6 @@ pub struct BaseConnection<T: OutputType> {
pub total_count: u64,
}

use mongodb_cursor_pagination::FindResult;

pub struct FindResultWrapper<Node>(pub FindResult<Node>);

/// Object that writes total count of items in a query, regardless of pagination.
Expand All @@ -22,7 +21,7 @@ pub struct AdditionalFields {
total_count: u64,
}

/// Implementation of conversion from MongoDB pagination to GraphQL Connection.
/// Implementation of conversion from MongoDB pagination to GraphQL connection.
impl<Node> From<FindResultWrapper<Node>> for BaseConnection<Node>
where
Node: OutputType,
Expand Down
2 changes: 2 additions & 0 deletions src/graphql/model/connection/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod base_connection;
pub mod review_connection;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use async_graphql::SimpleObject;

use crate::{base_connection::BaseConnection, review::Review};
use super::{super::review::Review, base_connection::BaseConnection};

/// A connection of Reviews.
/// A connection of reviews.
#[derive(Debug, SimpleObject, Clone)]
#[graphql(shareable)]
pub struct ReviewConnection {
Expand All @@ -14,7 +14,7 @@ pub struct ReviewConnection {
pub total_count: u64,
}

/// Implementation of conversion from BaseConnection<Review> to ReviewConnection.
/// Implementation of conversion from `BaseConnection<Review>` to `ReviewConnection`.
///
/// Prevents GraphQL naming conflicts.
impl From<BaseConnection<Review>> for ReviewConnection {
Expand Down
6 changes: 6 additions & 0 deletions src/graphql/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod connection;
pub mod order_datatypes;
pub mod product;
pub mod product_variant;
pub mod review;
pub mod user;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl Default for OrderDirection {
}
}

/// Implements conversion to i32 for MongoDB document sorting.
/// Implements conversion to `i32`` for MongoDB document sorting.
impl From<OrderDirection> for i32 {
fn from(value: OrderDirection) -> Self {
match value {
Expand Down Expand Up @@ -78,7 +78,7 @@ impl Default for ReviewOrderInput {

/// Describes the fields that a foreign types can be ordered by.
///
/// Only the Id valid at the moment.
/// Only the id valid at the moment.
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum CommonOrderField {
/// Orders by "id".
Expand Down
10 changes: 6 additions & 4 deletions src/product.rs → src/graphql/model/product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ use mongodb::{options::FindOptions, Collection, Database};
use mongodb_cursor_pagination::{error::CursorError, FindResult, PaginatedCursor};
use serde::{Deserialize, Serialize};

use crate::{
base_connection::{BaseConnection, FindResultWrapper},
use super::{
connection::{
base_connection::{BaseConnection, FindResultWrapper},
review_connection::ReviewConnection,
},
order_datatypes::ReviewOrderInput,
product_variant::calculate_average_rating,
review::Review,
review_connection::ReviewConnection,
};

#[derive(Debug, Serialize, Deserialize, PartialEq, Copy, Clone, SimpleObject)]
Expand Down Expand Up @@ -39,7 +41,7 @@ impl Product {
let sorting_doc = doc! {review_order.field.unwrap_or_default().as_str(): i32::from(review_order.direction.unwrap_or_default())};
let find_options = FindOptions::builder()
.skip(skip)
.limit(first.map(|v| i64::from(v)))
.limit(first.map(|definitely_first| i64::from(definitely_first)))
.sort(sorting_doc)
.build();
let document_collection = collection.clone_with_type::<Document>();
Expand Down
Loading
Loading