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
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::config::primitives::absolute_path::AbsolutePath;
#[derive(Debug, Deserialize, Serialize, JsonSchema, Default)]
#[serde(deny_unknown_fields)]
pub struct WebSocketConfig {
/// Enables/disables WebSocket connections.
///
/// By default, WebSockets are disabled.
///
/// You can override this setting by setting the `WEBSOCKET_ENABLED` environment variable to `true` or `false`.
#[serde(default)]
pub enabled: bool,
/// The path to use for the WebSocket endpoint on the router.
///
/// Note to always provide the absolute path starting with a `/`, e.g., `/ws`.
///
/// By default, the WebSocket endpoint will be available at the `http.graphql_endpoint` (defaults to `/graphql`)
/// if no path is specified and the clients will connect using `ws://<router-url>/<graphql_endpoint>`.
#[serde(default)]
pub path: Option<AbsolutePath>,
/// Configuration for handling headers for WebSocket connections.
#[serde(default)]
pub headers: WebSocketHeadersConfig,
}
#[derive(Default, Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "lowercase")]
pub enum WebSocketHeadersSource {
/// Do not accept headers from any source inside WebSocket connections.
None,
/// Accept headers from the connection init payload. This is the default.
///
/// For example, if the client sends a connection init message like:
///
/// ```json
/// {
/// "type": "connection_init",
/// "payload": {
/// "Authorization": "Bearer abc123"
/// }
/// }
/// ```
///
/// The headers will be extracted and considered for the WebSocket connection to the subgraph
/// respecting the header propagation rules as well as validating against any
/// JWT rules defined in the configuration.
///
/// Note that there is no `headers` field in the payload, so all fields in the payload
/// will be treated as headers when this option is enabled.
#[default]
Connection,
/// Accept headers from the `headers` field from the GraphQL operation extensions.
///
/// For example, if the connected client sends a GraphQL operation like:
///
/// ```json
/// {
/// "query": "{ topProducts { name } }",
/// "extensions": {
/// "headers": {
/// "Authorization": "Bearer abc123"
/// }
/// }
/// }
/// ```
///
/// The headers will be extracted and considered for the subgraph respecting the header
/// propagation rules as well as validating against any JWT rules defined in the configuration.
Operation,
/// Accept headers from both the connection init payload and the operation extensions.
///
/// Headers from the operation extensions will take precedence over those from the connection init
/// payload when both are provided.
Both,
}
#[derive(Default, Deserialize, Serialize, JsonSchema, Debug)]
#[serde(deny_unknown_fields)]
pub struct WebSocketHeadersConfig {
/// The source(s) from which to accept headers for WebSocket connections.
pub source: WebSocketHeadersSource,
/// Whether to persist merged headers for the duration of the WebSocket connection
/// when using the `both` source (headers are accepted from multiple sources).
///
/// Only has effect when `source` is set to `both`.
///
/// This is useful when dealing with authentication using tokens that expire, where the
/// initial connection might use one token, but subsequent operations might need to
/// provide updated tokens in the operation extensions and then use that for further authentication.
///
/// For example:
///
/// 1. Client connects with connection init payload containing an Authorization header with a token.
/// 2. Client sends a subscription operation with an updated Authorization header in the operation extensions.
/// 3. If `persist` is enabled, the updated Authorization header will be stored and used for subsequent operations.
#[serde(default)]
pub persist: bool,
}
impl WebSocketHeadersConfig {
pub fn accepts_connection_headers(&self) -> bool {
matches!(
self.source,
WebSocketHeadersSource::Connection | WebSocketHeadersSource::Both
)
}
pub fn accepts_operation_headers(&self) -> bool {
matches!(
self.source,
WebSocketHeadersSource::Operation | WebSocketHeadersSource::Both
)
}
}