From 9634c7b6bb9b0945d72f1eb55fc4a986bc1946ec Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Sat, 14 Oct 2023 22:57:49 +0200 Subject: [PATCH] fix(api): add auth guard --- platform/api/src/api/v1/gql/guards/mod.rs | 32 ++++++++++ platform/api/src/api/v1/gql/mod.rs | 1 + platform/api/src/api/v1/gql/models/channel.rs | 26 ++------ platform/api/src/api/v1/gql/models/user.rs | 60 ++----------------- 4 files changed, 43 insertions(+), 76 deletions(-) create mode 100644 platform/api/src/api/v1/gql/guards/mod.rs diff --git a/platform/api/src/api/v1/gql/guards/mod.rs b/platform/api/src/api/v1/gql/guards/mod.rs new file mode 100644 index 000000000..b359d7c74 --- /dev/null +++ b/platform/api/src/api/v1/gql/guards/mod.rs @@ -0,0 +1,32 @@ +use async_graphql::Context; +use ulid::Ulid; + +use crate::database::RolePermission; + +use super::{ + error::{GqlError, Result}, + ext::ContextExt, +}; + +// This can't be replaced by async_graphql's field guards because of this: https://github.com/async-graphql/async-graphql/issues/1398 +// I don't see a better alternative than doing this for now. +pub async fn auth_guard( + ctx: &Context<'_>, + field_name: &'static str, + field_value: T, + user_id: Ulid, +) -> Result { + let request_context = ctx.get_req_context(); + + let auth = request_context.auth().await; + + if let Some(auth) = auth { + if Ulid::from(auth.session.user_id) == user_id + || auth.user_permissions.has_permission(RolePermission::Admin) + { + return Ok(field_value); + } + } + + Err(GqlError::Unauthorized { field: field_name }.into()) +} diff --git a/platform/api/src/api/v1/gql/mod.rs b/platform/api/src/api/v1/gql/mod.rs index cc96e977a..229b243b7 100644 --- a/platform/api/src/api/v1/gql/mod.rs +++ b/platform/api/src/api/v1/gql/mod.rs @@ -6,6 +6,7 @@ use routerify::Router; use crate::{api::error::ApiErrorInterface, global::GlobalState}; +pub mod guards; pub mod error; pub mod ext; pub mod handlers; diff --git a/platform/api/src/api/v1/gql/models/channel.rs b/platform/api/src/api/v1/gql/models/channel.rs index 0a298ba86..1f31b30fe 100644 --- a/platform/api/src/api/v1/gql/models/channel.rs +++ b/platform/api/src/api/v1/gql/models/channel.rs @@ -1,12 +1,9 @@ use async_graphql::{ComplexObject, Context, SimpleObject}; -use ulid::Ulid; use crate::api::v1::gql::error::ResultExt; -use crate::api::v1::gql::{ - error::{GqlError, Result}, - ext::ContextExt, -}; -use crate::database::{self, ChannelLink, RolePermission}; +use crate::api::v1::gql::guards::auth_guard; +use crate::api::v1::gql::{error::Result, ext::ContextExt}; +use crate::database::{self, ChannelLink}; use super::category::Category; use super::{date::DateRFC3339, ulid::GqlUlid}; @@ -49,22 +46,7 @@ impl Channel { } async fn stream_key(&self, ctx: &Context<'_>) -> Result<&Option> { - let request_context = ctx.get_req_context(); - - let auth = request_context.auth().await; - - if let Some(auth) = auth { - if Ulid::from(auth.session.user_id) == *self.id - || auth.user_permissions.has_permission(RolePermission::Admin) - { - return Ok(&self.stream_key_); - } - } - - Err(GqlError::Unauthorized { - field: "stream_key", - } - .into()) + auth_guard(ctx, "streamKey", &self.stream_key_, self.id.into()).await } async fn followers_count(&self, ctx: &Context<'_>) -> Result { diff --git a/platform/api/src/api/v1/gql/models/user.rs b/platform/api/src/api/v1/gql/models/user.rs index 4faf6de73..e48903f26 100644 --- a/platform/api/src/api/v1/gql/models/user.rs +++ b/platform/api/src/api/v1/gql/models/user.rs @@ -1,12 +1,8 @@ use async_graphql::{ComplexObject, Context, SimpleObject}; -use ulid::Ulid; use crate::{ - api::v1::gql::{ - error::{GqlError, Result}, - ext::ContextExt, - }, - database::{self, RolePermission, SearchResult}, + api::v1::gql::{error::Result, guards::auth_guard}, + database::{self, SearchResult}, }; use super::{channel::Channel, color::DisplayColor, date::DateRFC3339, ulid::GqlUlid}; @@ -44,62 +40,18 @@ pub struct User { pub last_login_at_: DateRFC3339, } -/// TODO: find a better way to check if a user is allowed to read a field. - #[ComplexObject] impl User { - async fn email(&self, ctx: &Context<'_>) -> Result<&str> { - let request_context = ctx.get_req_context(); - - let auth = request_context.auth().await; - - if let Some(auth) = auth { - if Ulid::from(auth.session.user_id) == *self.id - || auth.user_permissions.has_permission(RolePermission::Admin) - { - return Ok(&self.email_); - } - } - - Err(GqlError::Unauthorized { field: "email" }.into()) + async fn email(&self, ctx: &Context<'_>) -> Result<&String> { + auth_guard(ctx, "email", &self.email_, self.id.into()).await } async fn email_verified(&self, ctx: &Context<'_>) -> Result { - let request_context = ctx.get_req_context(); - - let auth = request_context.auth().await; - - if let Some(auth) = auth { - if Ulid::from(auth.session.user_id) == *self.id - || auth.user_permissions.has_permission(RolePermission::Admin) - { - return Ok(self.email_verified_); - } - } - - Err(GqlError::Unauthorized { - field: "emailVerified", - } - .into()) + auth_guard(ctx, "emailVerified", self.email_verified_, self.id.into()).await } async fn last_login_at(&self, ctx: &Context<'_>) -> Result<&DateRFC3339> { - let request_context = ctx.get_req_context(); - - let auth = request_context.auth().await; - - if let Some(auth) = auth { - if Ulid::from(auth.session.user_id) == *self.id - || auth.user_permissions.has_permission(RolePermission::Admin) - { - return Ok(&self.last_login_at_); - } - } - - Err(GqlError::Unauthorized { - field: "lastLoginAt", - } - .into()) + auth_guard(ctx, "lastLoginAt", &self.last_login_at_, self.id.into()).await } }