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
// This file contains the core authentication logic that will be used regardless of integration

use crate::auth::auth_state::{AuthState, AuthToken};
use crate::auth::jwt::{get_jwt_secret, validate_and_decode_jwt};
use crate::errors::*;

/// An enum for the level of blocking imposed on a particular endpoint.
/// Your choice on this should be carefully evaluated based on your threat model. Please choose wisely!
#[derive(Debug, Clone, Copy)]
pub enum AuthBlockLevel {
    /// Allows anything through.
    /// - Valid token   -> allow
    /// - Invalid token -> allow
    /// - Missing token -> allow
    AllowAll,
    /// Blocks eveything except requests with valid tokens.
    /// Note that, with this setting, introspection will be impossible in the GraphiQL playground. You may want to use `AllowMissing` in development
    /// and then this in production (see the book).
    /// - Valid token   -> allow
    /// - Invalid token -> block
    /// - Missing token -> block
    BlockUnauthenticated,
    /// Allows requests with valid tokens or no token at all. Only blocks requests that specify an invalid token.
    /// This is mostly useful for development to enable introspection in the GraphiQL playground (see the book).
    /// - Valid token   -> allow
    /// - Invalid token -> block
    /// - Missing token -> allow
    AllowMissing,
}

// Extracts an authentication state from the given Option<String> token
// This is exposed as a primitive for serverful and serverless authentication logic
pub fn get_token_state_from_header(
    auth_header: Option<&str>,
    secret_str: String,
) -> Result<AuthState> {
    // Get the bearer token from the header if it exists
    let bearer_token = match auth_header {
        Some(header) => header
            .split("Bearer")
            .collect::<Vec<&str>>()
            .get(1) // Get everything apart from that first element
            .map(|token| token.trim()),
        None => None,
    };

    // Decode the bearer token into an authentication state
    match bearer_token {
        Some(token) => {
            let jwt_secret = get_jwt_secret(secret_str)?;
            let decoded_jwt = validate_and_decode_jwt(&token, &jwt_secret);

            match decoded_jwt {
                Some(claims) => Ok(AuthState::Authorised(AuthToken(claims))),
                None => Ok(AuthState::InvalidToken), // The token is invalid
            }
        }
        None => Ok(AuthState::NoToken), // No token exists
    }
}

/// This represents the decision as to whether or not a use is allowed through to an endpoint. You should only have to deal with this if you're
/// developing middleware for a custom integration.
#[derive(Clone, Debug)]
pub enum AuthVerdict {
    /// The user should be allowed through, and their decoded authentication data (JWT payload without metadata) is attached.
    Allow(AuthState),
    /// The user should be blocked.
    Block,
    /// Some internal error occurred, the body of which is attached.
    Error(String),
}

// Compares the given token's authentication state (as a raw result) to a given block-level to arrive at a verdict
pub fn get_auth_verdict(
    token_state: Result<AuthState>,
    block_state: AuthBlockLevel,
) -> AuthVerdict {
    match token_state {
        // We hold `token_state` as the AuthState variant so we don't pointlessly insert a Result into the request extensions
        Ok(token_state @ AuthState::Authorised(_)) => AuthVerdict::Allow(token_state),
        Ok(token_state @ AuthState::InvalidToken) => {
            if let AuthBlockLevel::AllowAll = block_state {
                AuthVerdict::Allow(token_state)
            } else {
                AuthVerdict::Block
            }
        }
        Ok(token_state @ AuthState::NoToken) => {
            if let AuthBlockLevel::AllowAll | AuthBlockLevel::AllowMissing = block_state {
                AuthVerdict::Allow(token_state)
            } else {
                AuthVerdict::Block
            }
        }
        Err(err) => AuthVerdict::Error(err.to_string()),
    }
}