diff --git a/src/appwrite.rs b/src/appwrite.rs index b77b2e4..f834905 100644 --- a/src/appwrite.rs +++ b/src/appwrite.rs @@ -18,8 +18,8 @@ pub struct APVariables { impl APVariables { pub async fn check_session( &self, - session_id: String, - user_id: String, + session_id: &str, + user_id: &str, ) -> Result { let client = reqwest::Client::new(); let url = format!("{}/users/{}/sessions", self.endpoint, user_id); diff --git a/src/db/mod.rs b/src/db/mod.rs index b575347..6b9a24f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -29,7 +29,7 @@ macro_rules! find_element { use diesel::prelude::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Database { conn: Pool>, } diff --git a/src/graphql/context.rs b/src/graphql/context.rs new file mode 100644 index 0000000..4e96fb7 --- /dev/null +++ b/src/graphql/context.rs @@ -0,0 +1,49 @@ +use crate::appwrite::APVariables; +use crate::db::Database; + +use tracing::info; + +#[derive(Default, Debug, Clone)] +pub struct Context { + pub db: Database, + pub appwrite: APVariables, + pub user_auth: bool, +} + +impl Context { + /// HTTP header for a user's session + /// + /// This header `Authorization` must be a single string in the + /// form `userId;userSessionId` with `userId` and `userSessionId` + /// being variables given by Appwrite to users that are logged in. + pub async fn user_auth<'r>(&self, auth_token: Option<&'r str>) -> bool { + if let Some(token) = auth_token { + let key = token.split(';').collect::>(); + if key.len() == 2 { + let user_id = key[0]; + let session_id = key[1]; + match self.appwrite.check_session(session_id, user_id).await { + Ok(val) => val, + Err(e) => { + info!("Error checking user session: {:?}", e); + false + } + } + } else { + info!("Invalid session key: {}", token); + false + } + } else { + false + } + } + + pub async fn attach_auth<'r>(&self, auth_token: Option<&'r str>) -> Self { + let mut res = self.clone(); + res.user_auth = self.user_auth(auth_token).await; + res + } + +} + +impl juniper::Context for Context {} diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs index 3138045..8516fa8 100644 --- a/src/graphql/mod.rs +++ b/src/graphql/mod.rs @@ -1,31 +1,50 @@ +use rocket::request::{FromRequest, Outcome, Request}; use rocket::response::content::RawHtml; use rocket::State; +use tracing::debug; + use juniper::EmptySubscription; use juniper_rocket::{GraphQLRequest, GraphQLResponse}; -use crate::appwrite::APVariables; -use crate::db::Database; - -#[derive(Default, Debug)] -pub struct Context { - pub db: Database, - pub appwrite: APVariables, -} - -impl juniper::Context for Context {} - -mod query; -use query::Query; +pub mod context; +pub use context::Context; mod mutation; -use mutation::Mutation; +mod query; -type Schema = - juniper::RootNode<'static, Query, Mutation, EmptySubscription>; +#[derive(Copy, Clone, Debug)] +pub struct UserAuth<'r>(Option<&'r str>); + +#[derive(Debug)] +pub enum UserAuthError {} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for UserAuth<'r> { + type Error = UserAuthError; + async fn from_request( + request: &'r Request<'_>, + ) -> Outcome { + match request.headers().get_one("Authorization") { + None => Outcome::Success(UserAuth(None)), + Some(key) => Outcome::Success(UserAuth(Some(key))), + } + } +} + +pub type Schema = juniper::RootNode< + 'static, + query::Query, + mutation::Mutation, + EmptySubscription, +>; pub fn create_schema() -> Schema { - Schema::new(Query {}, Mutation {}, EmptySubscription::default()) + Schema::new( + query::Query {}, + mutation::Mutation {}, + EmptySubscription::default(), + ) } #[rocket::get("/")] @@ -37,18 +56,26 @@ pub fn graphiql() -> RawHtml { #[rocket::get("/graphql?")] pub async fn get_graphql_handler( context: &State, + user_auth: UserAuth<'_>, request: GraphQLRequest, schema: &State, ) -> GraphQLResponse { - request.execute(schema, context).await + debug!("Current context: {:?}", context); + request + .execute(schema, &(*context).attach_auth(user_auth.0).await) + .await } #[allow(clippy::needless_pass_by_value)] #[rocket::post("/graphql", data = "")] pub async fn post_graphql_handler( context: &State, + user_auth: UserAuth<'_>, request: GraphQLRequest, schema: &State, ) -> GraphQLResponse { - request.execute(schema, context).await + debug!("Current context: {:?}", context); + request + .execute(schema, &(*context).attach_auth(user_auth.0).await) + .await }