Skip to content

Commit 6365890

Browse files
authored
feat(gateway-framework): remove unused deployment id authorization checks (#741)
Signed-off-by: Lorenzo Delgado <[email protected]>
1 parent f10bda1 commit 6365890

File tree

8 files changed

+105
-135
lines changed

8 files changed

+105
-135
lines changed

gateway-framework/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ serde_json = { workspace = true, features = ["raw_value"] }
3737
serde_with.workspace = true
3838
siphasher.workspace = true
3939
tap_core = { git = "https://github.com/semiotic-ai/timeline-aggregation-protocol", rev = "c179dfe" }
40-
thegraph-core = { workspace = true, features = ["subgraph-client"] }
40+
thegraph-core = { workspace = true, features = ["subgraph-client", "subscriptions"] }
4141
thegraph-graphql-http.workspace = true
4242
thiserror.workspace = true
4343
tokio.workspace = true

gateway-framework/src/auth/mod.rs renamed to gateway-framework/src/auth.rs

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
pub mod context;
2-
pub mod methods;
3-
4-
use std::sync::Arc;
5-
6-
use methods::{api_keys, subscriptions};
71
use ordered_float::NotNan;
8-
use thegraph_core::types::{DeploymentId, SubgraphId};
2+
use thegraph_core::types::SubgraphId;
93

104
pub use self::context::AuthContext;
11-
use crate::topology::network::Deployment;
5+
use self::methods::{api_keys, subscriptions};
6+
7+
pub mod context;
8+
pub mod methods;
129

1310
/// User query settings typically associated with an auth token.
1411
#[derive(Clone, Debug, Default)]
@@ -33,35 +30,25 @@ impl AuthToken {
3330
}
3431
}
3532

36-
/// Check if the given deployment is authorized for this auth token.
37-
pub fn is_deployment_authorized(&self, deployment: &DeploymentId) -> bool {
33+
/// Check if ANY of the given deployment subgraphs are authorized for this auth token.
34+
pub fn is_any_deployment_subgraph_authorized(&self, subgraphs: &[&SubgraphId]) -> bool {
3835
match self {
39-
AuthToken::ApiKey(auth) => auth.is_deployment_authorized(deployment),
40-
AuthToken::SubscriptionsAuthToken(auth) => auth.is_deployment_authorized(deployment),
36+
AuthToken::ApiKey(auth) => subgraphs
37+
.iter()
38+
.any(|subgraph| auth.is_subgraph_authorized(subgraph)),
39+
AuthToken::SubscriptionsAuthToken(auth) => subgraphs
40+
.iter()
41+
.any(|subgraph| auth.is_subgraph_authorized(subgraph)),
4142
}
4243
}
4344

44-
/// Check if the given origin domain is authorized for this auth token.
45+
/// Check if the given origin domain is authorized for this auth token.
4546
pub fn is_domain_authorized(&self, domain: &str) -> bool {
4647
match self {
4748
AuthToken::ApiKey(auth) => auth.is_domain_authorized(domain),
4849
AuthToken::SubscriptionsAuthToken(auth) => auth.is_domain_authorized(domain),
4950
}
5051
}
51-
52-
pub fn are_subgraphs_authorized(&self, deployments: &[Arc<Deployment>]) -> bool {
53-
match self {
54-
AuthToken::ApiKey(auth) => auth.are_subgraphs_authorized(deployments),
55-
AuthToken::SubscriptionsAuthToken(auth) => auth.are_subgraphs_authorized(deployments),
56-
}
57-
}
58-
59-
pub fn are_deployments_authorized(&self, deployments: &[Arc<Deployment>]) -> bool {
60-
match self {
61-
AuthToken::ApiKey(auth) => auth.are_deployments_authorized(deployments),
62-
AuthToken::SubscriptionsAuthToken(auth) => auth.are_deployments_authorized(deployments),
63-
}
64-
}
6552
}
6653

6754
impl From<api_keys::AuthToken> for AuthToken {
File renamed without changes.

gateway-framework/src/auth/methods/api_keys.rs

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ use alloy_primitives::Address;
77
use ordered_float::NotNan;
88
use serde::Deserialize;
99
use serde_with::serde_as;
10-
use thegraph_core::types::{DeploymentId, SubgraphId};
10+
use thegraph_core::types::SubgraphId;
1111
use tokio::sync::watch;
1212

1313
use super::common;
14-
use crate::{
15-
auth::QuerySettings, http::middleware::RateLimitSettings, topology::network::Deployment,
16-
};
14+
use crate::{auth::QuerySettings, http::middleware::RateLimitSettings};
1715

16+
// TODO: This type MUST NOT implement the `Deserialize` trait.
17+
// Decouple the API keys fetch types from the API keys types.
1818
#[serde_as]
1919
#[derive(Clone, Debug, Default, Deserialize)]
2020
pub struct APIKey {
@@ -25,13 +25,13 @@ pub struct APIKey {
2525
#[serde(rename = "max_budget")]
2626
pub max_budget_usd: Option<NotNan<f64>>,
2727
#[serde(default)]
28-
pub deployments: Vec<DeploymentId>,
29-
#[serde(default)]
3028
pub subgraphs: Vec<SubgraphId>,
3129
#[serde(default)]
3230
pub domains: Vec<String>,
3331
}
3432

33+
// TODO: This type MUST NOT implement the `Deserialize` trait.
34+
// Decouple the API keys fetch types from the API keys types.
3535
#[derive(Clone, Copy, Debug, Default, Deserialize)]
3636
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
3737
pub enum QueryStatus {
@@ -109,22 +109,6 @@ impl AuthToken {
109109
let allowed_subgraphs = &self.api_key.subgraphs;
110110
common::is_subgraph_authorized(allowed_subgraphs, subgraph)
111111
}
112-
113-
/// Check if the given deployment is authorized by the API key.
114-
pub fn is_deployment_authorized(&self, deployment: &DeploymentId) -> bool {
115-
let allowed_deployments = &self.api_key.deployments;
116-
common::is_deployment_authorized(allowed_deployments, deployment)
117-
}
118-
119-
pub fn are_subgraphs_authorized(&self, deployments: &[Arc<Deployment>]) -> bool {
120-
let allowed_subgraphs = &self.api_key.subgraphs;
121-
common::are_subgraphs_authorized(allowed_subgraphs, deployments)
122-
}
123-
124-
pub fn are_deployments_authorized(&self, deployments: &[Arc<Deployment>]) -> bool {
125-
let allowed_deployments = &self.api_key.deployments;
126-
common::are_deployments_authorized(allowed_deployments, deployments)
127-
}
128112
}
129113

130114
impl std::fmt::Display for AuthToken {
@@ -160,7 +144,7 @@ impl AuthContext {
160144
}
161145
}
162146

163-
/// Parse the bearer token as a API key and retrieve the associated API key.
147+
/// Parse the bearer token as an API key and retrieve the associated API key.
164148
pub fn parse_auth_token(
165149
ctx: &AuthContext,
166150
token: &str,

gateway-framework/src/auth/methods/common.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,44 @@
1-
use std::sync::Arc;
2-
31
use thegraph_core::types::{DeploymentId, SubgraphId};
42

5-
use crate::topology::network::Deployment;
3+
/// Check if the given deployment is authorized.
4+
///
5+
/// It checks if the given deployment is contained in the authorized set. If the authorized set is
6+
/// empty, any deployment is considered authorized.
7+
pub fn is_deployment_authorized(authorized: &[DeploymentId], deployment: &DeploymentId) -> bool {
8+
authorized.is_empty() || authorized.contains(deployment)
9+
}
610

7-
/// Check if the given deployments are authorized by the given authorized deployments.
11+
/// Check if ALL the deployments are authorized.
812
///
9-
/// If the authorized deployments set is empty, all deployments are considered authorized.
13+
/// It checks if all deployments are contained in the authorized set. If the authorized set is
14+
/// empty, any deployment is considered authorized.
1015
pub fn are_deployments_authorized(
1116
authorized: &[DeploymentId],
12-
deployments: &[Arc<Deployment>],
17+
deployments: &[DeploymentId],
1318
) -> bool {
1419
authorized.is_empty()
1520
|| deployments
1621
.iter()
17-
.any(|deployment| authorized.contains(&deployment.id))
22+
.all(|deployment| authorized.contains(deployment))
1823
}
1924

20-
/// Check if the given deployment is authorized by the given authorized deployments.
21-
pub fn is_deployment_authorized(authorized: &[DeploymentId], deployment: &DeploymentId) -> bool {
22-
authorized.is_empty() || authorized.contains(deployment)
25+
/// Check if the given subgraph is authorized.
26+
///
27+
/// It checks if the given subgraph is contained in the authorized set. If the authorized set is
28+
/// empty, any subgraph is considered authorized.
29+
pub fn is_subgraph_authorized(authorized: &[SubgraphId], subgraph: &SubgraphId) -> bool {
30+
authorized.is_empty() || authorized.contains(subgraph)
2331
}
2432

25-
/// Check if any of the given deployments are authorized by the given authorized subgraphs.
33+
/// Check if ALL the subgraphs are authorized.
2634
///
27-
/// If the authorized subgraphs set is empty, all deployments are considered authorized.
28-
pub fn are_subgraphs_authorized(
29-
authorized: &[SubgraphId],
30-
deployments: &[Arc<Deployment>],
31-
) -> bool {
35+
/// It checks if all subgraphs are contained in the authorized set. If the authorized set is empty,
36+
/// any subgraph is considered authorized.
37+
pub fn are_subgraphs_authorized(authorized: &[SubgraphId], subgraphs: &[SubgraphId]) -> bool {
3238
authorized.is_empty()
33-
|| deployments.iter().any(|deployment| {
34-
deployment
35-
.subgraphs
36-
.iter()
37-
.any(|subgraph_id| authorized.contains(subgraph_id))
38-
})
39-
}
40-
41-
/// Check if the given subgraph is authorized by the given authorized subgraphs.
42-
pub fn is_subgraph_authorized(authorized: &[SubgraphId], subgraph: &SubgraphId) -> bool {
43-
authorized.is_empty() || authorized.contains(subgraph)
39+
|| subgraphs
40+
.iter()
41+
.all(|subgraph| authorized.contains(subgraph))
4442
}
4543

4644
/// Check if the query origin domain is authorized.

gateway-framework/src/auth/methods/subscriptions.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ use thegraph_core::{
88
subscriptions::auth::{
99
parse_auth_token as parse_bearer_token, verify_auth_token_claims, AuthTokenClaims,
1010
},
11-
types::{DeploymentId, SubgraphId},
11+
types::SubgraphId,
1212
};
1313
use tokio::sync::watch;
1414

1515
use super::common;
1616
use crate::{
1717
auth::QuerySettings, http::middleware::RateLimitSettings, subscriptions::Subscription,
18-
topology::network::Deployment,
1918
};
2019

2120
/// Auth token wrapper around the Subscriptions auth token claims and the subscription.
@@ -68,22 +67,6 @@ impl AuthToken {
6867
let allowed_subgraphs = &self.claims.allowed_subgraphs;
6968
common::is_subgraph_authorized(allowed_subgraphs, subgraph)
7069
}
71-
72-
/// Check if the given deployment is authorized by the auth token claims.
73-
pub fn is_deployment_authorized(&self, deployment: &DeploymentId) -> bool {
74-
let allowed_deployments = &self.claims.allowed_deployments;
75-
common::is_deployment_authorized(allowed_deployments, deployment)
76-
}
77-
78-
pub fn are_subgraphs_authorized(&self, deployments: &[Arc<Deployment>]) -> bool {
79-
let allowed_subgraphs = &self.claims.allowed_subgraphs;
80-
common::are_subgraphs_authorized(allowed_subgraphs, deployments)
81-
}
82-
83-
pub fn are_deployments_authorized(&self, deployments: &[Arc<Deployment>]) -> bool {
84-
let allowed_deployments = &self.claims.allowed_deployments;
85-
common::are_deployments_authorized(allowed_deployments, deployments)
86-
}
8770
}
8871

8972
impl std::fmt::Display for AuthToken {

graph-gateway/src/client_query.rs

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ use gateway_framework::{
2424
blocks::Block,
2525
budgets::USD,
2626
chains::ChainReader,
27-
errors::{
28-
Error, IndexerError,
29-
UnavailableReason::{self, *},
30-
},
27+
errors::{Error, IndexerError, UnavailableReason},
3128
network::{
3229
discovery::Status,
3330
indexing_performance::{IndexingPerformance, Snapshot},
@@ -96,36 +93,38 @@ pub async fn handle_query(
9693
let start_time = Instant::now();
9794
let timestamp = unix_timestamp();
9895

99-
// Check if the query selector is authorized by the auth token
100-
match &selector {
96+
// Check if the query selector is authorized by the auth token and
97+
// resolve the subgraph deployments for the query.
98+
let (deployments, subgraph) = match &selector {
10199
QuerySelector::Subgraph(id) => {
100+
// If the subgraph is not authorized, return an error.
102101
if !auth.is_subgraph_authorized(id) {
103102
return Err(Error::Auth(anyhow!("Subgraph not authorized by user")));
104103
}
104+
105+
resolve_subgraph_deployments(&ctx.network, &selector)?
105106
}
106-
QuerySelector::Deployment(id) => {
107-
if !auth.is_deployment_authorized(id) {
107+
QuerySelector::Deployment(_) => {
108+
// Authorization is based on the "authorized subgraphs" allowlist. We need to resolve
109+
// the subgraph deployments to check if any of the deployment's subgraphs are
110+
// authorized, otherwise return an error.
111+
let (deployments, subgraph) = resolve_subgraph_deployments(&ctx.network, &selector)?;
112+
113+
// If none of the deployment's subgraphs are authorized, return an error.
114+
let deployment_subgraphs = deployments
115+
.iter()
116+
.flat_map(|d| d.subgraphs.iter())
117+
.collect::<Vec<_>>();
118+
if !auth.is_any_deployment_subgraph_authorized(&deployment_subgraphs) {
108119
return Err(Error::Auth(anyhow!("Deployment not authorized by user")));
109120
}
121+
122+
(deployments, subgraph)
110123
}
111-
}
124+
};
112125

113-
let (deployments, subgraph) = resolve_subgraph_deployments(&ctx.network, &selector)?;
114126
tracing::info!(deployments = ?deployments.iter().map(|d| d.id).collect::<Vec<_>>());
115127

116-
// Check authorization for the resolved deployments
117-
if !auth.are_deployments_authorized(&deployments) {
118-
return Err(Error::Auth(anyhow::anyhow!(
119-
"deployment not authorized by user"
120-
)));
121-
}
122-
123-
if !auth.are_subgraphs_authorized(&deployments) {
124-
return Err(Error::Auth(anyhow::anyhow!(
125-
"subgraph not authorized by user"
126-
)));
127-
}
128-
129128
if let Some(l2_url) = ctx.l2_gateway.as_ref() {
130129
// Forward query to L2 gateway if it's marked as transferred & there are no allocations.
131130
// abf62a6d-c071-4507-b528-ddc8e250127a
@@ -268,7 +267,10 @@ async fn handle_client_query_inner(
268267
.unwrap_or_default();
269268
available_indexers.retain(|candidate| {
270269
if blocklist.contains(candidate) || ctx.bad_indexers.contains(&candidate.indexer) {
271-
indexer_errors.insert(candidate.indexer, IndexerError::Unavailable(NoStatus));
270+
indexer_errors.insert(
271+
candidate.indexer,
272+
IndexerError::Unavailable(UnavailableReason::NoStatus),
273+
);
272274
return false;
273275
}
274276
true
@@ -720,7 +722,7 @@ async fn handle_indexer_query_inner(
720722
.iter()
721723
.try_for_each(|err| check_block_error(&err.message))
722724
.map_err(|block_error| ExtendedIndexerError {
723-
error: IndexerError::Unavailable(MissingBlock),
725+
error: IndexerError::Unavailable(UnavailableReason::MissingBlock),
724726
latest_block: block_error.latest_block,
725727
})?;
726728

@@ -793,8 +795,10 @@ pub fn indexer_fee(
793795
.map(|model| model.cost_with_context(context))
794796
{
795797
None => Ok(0),
796-
Some(Ok(fee)) => fee.to_u128().ok_or(IndexerError::Unavailable(NoFee)),
797-
Some(Err(_)) => Err(IndexerError::Unavailable(NoFee)),
798+
Some(Ok(fee)) => fee
799+
.to_u128()
800+
.ok_or(IndexerError::Unavailable(UnavailableReason::NoFee)),
801+
Some(Err(_)) => Err(IndexerError::Unavailable(UnavailableReason::NoFee)),
798802
}
799803
}
800804

0 commit comments

Comments
 (0)