1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Utility functions for GraphQL resolvers
use std::sync::{Mutex, MutexGuard};
use tokio_stream::Stream;

use crate::auth::auth_state::AuthState;
use crate::errors::*;
use crate::pubsub::PubSub;

/// Checks to see if the given authentication state matches the series of given claims. This must be provided with the authentication state,
/// a series of claims to check against, and code to execute if the user is authenticated. This will call [`bail!`] with an [`ErrorKind::Unauthorised`](crate::errors::ErrorKind::Unauthorised)
/// error if the user is unauthenticated, so **that must be handled in your function's return type**!
/// # Example
/// This is a simplified version of the internal logic that publishes data to the subscriptions server.
/// ```
/// use diana::{
///     errors::{Result, GQLResult},
///     graphql_utils::get_auth_data_from_ctx,
///     async_graphql::{Object as GQLObject},
///     if_authed,
/// };
///
/// #[derive(Default, Clone)]
/// pub struct PublishMutation;
/// #[GQLObject]
/// impl PublishMutation {
///     async fn publish(
///         &self,
///         raw_ctx: &async_graphql::Context<'_>,
///         channel: String,
///         data: String,
///     ) -> Result<bool> {
///         let auth_state = get_auth_data_from_ctx(raw_ctx)?;
///         if_authed!(
///             auth_state,
///             {
///                 "role" => "graphql_server"
///             },
///             {
///                 // Your code here
///                 Ok(true)
///             }
///         )
///     }
/// }
///
/// # fn main() {}
/// ```
// TODO mark as deprecated
#[macro_export]
#[deprecated(
    since = "0.2.8",
    note = "please use `is_authed!` instead, it exposes a boolean and lets you use your own error logic"
)]
macro_rules! if_authed(
    ($auth_state:expr, { $($key:expr => $value:expr),+ }, $code:block) => {
        {
            // Create a HashMap out of the given test claims
            let mut test_claims: ::std::collections::HashMap<&str, &str> = ::std::collections::HashMap::new();
            $(
                test_claims.insert($key, $value);
            )+
            // Match the authentication state with those claims now
            if $auth_state.has_claims(test_claims) {
                $code
            } else {
                Err($crate::errors::ErrorKind::Unauthorised.into())
            }
        }
     };
);

/// Checks to see if the given authentication state matches the series of given claims. This must be provided with the authentication state,
/// a series of claims to check against. It will then return a boolean as to whether or not the user is authorized.
/// This should be used instead of [`if_authed!`].
/// # Example
/// This is a simplified version of the internal logic that publishes data to the subscriptions server.
/// ```
/// use diana::{
///     errors::{Result, GQLResult, bail, ErrorKind},
///     graphql_utils::get_auth_data_from_ctx,
///     async_graphql::{Object as GQLObject},
///     is_authed,
/// };
///
/// #[derive(Default, Clone)]
/// pub struct PublishMutation;
/// #[GQLObject]
/// impl PublishMutation {
///     async fn publish(
///         &self,
///         raw_ctx: &async_graphql::Context<'_>,
///         channel: String,
///         data: String,
///     ) -> Result<bool> {
///         if is_authed!(
///             get_auth_data_from_ctx(raw_ctx)?,
///             {
///                 "role" => "graphql_server"
///             }
///         ) {
///             // Your code here
///             Ok(true)
///         } else {
///             // Your error handling code here
///             bail!(ErrorKind::Unauthorised)
///         }
///     }
/// }
///
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! is_authed(
    ($auth_state:expr, { $($key:expr => $value:expr),+ }) => {
        {
            // Create a HashMap out of the given test claims
            let mut test_claims: ::std::collections::HashMap<&str, &str> = ::std::collections::HashMap::new();
            $(
                test_claims.insert($key, $value);
            )+
            // Match the authentication state with those claims now
            $auth_state.has_claims(test_claims)
        }
     };
);

/// Gets a subscription stream to events published on a particular channel from the context of a GraphQL resolver.
/// **This must only be used in subscriptions! It will not work anywhere else!**
/// This returns a pre-created stream which you should manipulate if necessary.
/// All data sent via the publisher from the queries/mutations system will land here **in string format**. Serialization is up to you.
/// # Example
/// ```
/// use diana::{
///     stream,
///     graphql_utils::get_stream_for_channel_from_ctx,
///     errors::GQLResult,
///     async_graphql::{Subscription as GQLSubscription, SimpleObject as GQLSimpleObject},
/// };
/// use tokio_stream::{Stream, StreamExt};
/// use serde::Deserialize;
///
/// #[derive(Deserialize, GQLSimpleObject)]
/// struct User {
///     username: String
/// }
///
/// #[derive(Default, Clone)]
/// pub struct Subscription;
/// #[GQLSubscription]
/// impl Subscription {
///     async fn new_users(
///         &self,
///         raw_ctx: &async_graphql::Context<'_>,
///     ) -> impl Stream<Item = GQLResult<User>> {
///         // Get a direct stream from the context on a certain channel
///         let stream_result = get_stream_for_channel_from_ctx("new_user", raw_ctx);
///
///         // We can manipulate the stream using the stream macro from async-stream
///         stream! {
///             let stream = stream_result?;
///             for await message in stream {
///                 // Serialise the data as a user
///                 let new_user: User = serde_json::from_str(&message).map_err(|_err| "couldn't serialize given data correctly".to_string())?;
///                 yield Ok(new_user);
///             }
///         }
///     }
/// }
/// # fn main() {}
/// ```
///
pub fn get_stream_for_channel_from_ctx(
    channel: &str,
    raw_ctx: &async_graphql::Context<'_>,
) -> Result<impl Stream<Item = String>> {
    // Get the PubSub mutably
    let mut pubsub = get_pubsub_from_ctx(raw_ctx)?;
    // Return a stream on the given channel
    Ok(pubsub.subscribe(channel))
}

/// Gets authentication data from the context of a GraphQL resolver.
/// This should only fail if the server is constructed without authentication middleware (which shouldn't be possible with the exposed API
/// surface of this crate).
pub fn get_auth_data_from_ctx<'a>(
    raw_ctx: &'a async_graphql::Context<'_>,
) -> Result<&'a AuthState> {
    let auth_state = raw_ctx
        .data::<AuthState>()
        .map_err(|_err| ErrorKind::GraphQLContextNotFound("auth_state".to_string()))?;

    Ok(auth_state)
}
/// Gets the internal PubSub from the context of a GraphQL resolver. You should never need to use this.
#[doc(hidden)]
pub fn get_pubsub_from_ctx<'a>(
    raw_ctx: &'a async_graphql::Context<'_>,
) -> Result<MutexGuard<'a, PubSub>> {
    // We store the PubSub instance as a Mutex because we need it sent/synced between threads as a mutable
    let pubsub_mutex = raw_ctx
        .data::<Mutex<PubSub>>()
        .map_err(|_err| ErrorKind::GraphQLContextNotFound("pubsub".to_string()))?;
    let pubsub = pubsub_mutex
        .lock()
        .map_err(|_err| ErrorKind::MutexPoisoned("pubsub".to_string()))?;

    Ok(pubsub)
}