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
// This file contains all the logic for setting up a serverless system for queries/mutations
// This exposes more generic primitives that the serverless systems not derived from AWS Lambda can depend on
// If a user wants to use some fancy new serverless system that doesn't even have requests, they can use this as long as they have a request body and auth header!

use async_graphql::{ObjectType, Request, SubscriptionType};
use std::any::Any;

use crate::auth::middleware::{get_auth_verdict, get_token_state_from_header, AuthVerdict};
use crate::graphql::get_schema_without_subscriptions;
use crate::options::Options;

/// The response from a generic serverless request invocation. This is the primitive you'll likely process into some kind of custom response
/// if you're not using AWS Lambda or one of its derivatives (e.g. Netlify). If you are, use [run_aws_req](crate::run_aws_req) and ignore this!
pub enum ServerlessResponse {
    /// The request was successful and the response is attached.
    /// Return a 200.
    Success(String),
    /// The request was blocked (unauthorized).
    /// Return a 403.
    Blocked,
    /// An error occurred on the server side. Any GraphQL errors will be encapsulated in the `Success` variant's payload.
    /// Return a 500.
    Error,
}

// Runs the given GraphQL query/mutation
// Needs the user's query/mutation and their authentication token
// This function handles all possible errors internally and will gracefully return an instance of `ServerlessResponse` every time
// If you're using AWS or a derivative (like Netlify), you can use `run_lambda_req` instead for greater convenience

/// Runs a GraphQL query/mutation in a serverless function. This is deliberately as general as possible so we support basically every serverless
/// function provider. If you're using AWS Lambda or one of its derivatives (e.g. Netlify), you can use [run_aws_req](crate::run_aws_req) instead for greater
/// convenience.
/// There are no examples for the usage of this function because it's a primitive. Just provide a request body (which should contain the query,
/// variables, etc.) and the HTTP `Authorization` header. You don't need to do any further processing, this function will do the rest.
pub async fn run_serverless_req<C, Q, M, S>(
    body: String,
    raw_auth_header: Option<&str>,
    opts: Options<C, Q, M, S>,
) -> ServerlessResponse
where
    C: Any + Send + Sync + Clone,
    Q: Clone + ObjectType + 'static,
    M: Clone + ObjectType + 'static,
    S: Clone + SubscriptionType + 'static,
{
    // Get the schema (this also creates a publisher to the subscriptions server and inserts context)
    // We deal with any errors directly with the serverless response enum
    let schema =
        get_schema_without_subscriptions(opts.schema, opts.subscriptions_server_data, opts.ctx);
    let schema = match schema {
        Ok(schema) => schema,
        Err(_) => return ServerlessResponse::Error,
    };

    // Get a verdict on whether or not the user should be allowed through
    let token_state = get_token_state_from_header(raw_auth_header, opts.jwt_secret);
    let verdict = get_auth_verdict(token_state, opts.authentication_block_state);

    match verdict {
        AuthVerdict::Allow(auth_data) => {
            // Deserialise that raw JSON request into an actual request with variables etc.
            let gql_req = serde_json::from_str::<Request>(&body);
            let mut gql_req = match gql_req {
                Ok(gql_req) => gql_req,
                Err(_) => return ServerlessResponse::Error,
            };
            // Insert the authentication data directly into that
            gql_req = gql_req.data(auth_data);
            // Run the request
            let res = schema.execute(gql_req).await;
            // Serialise that response into a string (the response bodies all have to be of the same type)
            let res_str = serde_json::to_string(&res);
            let res_str = match res_str {
                Ok(res_str) => res_str,
                Err(_) => return ServerlessResponse::Error,
            };

            ServerlessResponse::Success(res_str)
        }
        AuthVerdict::Block => ServerlessResponse::Blocked,
        AuthVerdict::Error => ServerlessResponse::Error,
    }
}