diff --git a/crates/contextforge-gateway-rs-lib/src/common.rs b/crates/contextforge-gateway-rs-lib/src/common.rs index 59f31549..4816ed24 100644 --- a/crates/contextforge-gateway-rs-lib/src/common.rs +++ b/crates/contextforge-gateway-rs-lib/src/common.rs @@ -59,7 +59,8 @@ pub struct ContextForgeClaims { pub exp: u64, pub teams: Option>, pub user: User, - pub scopes: Scopes, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scopes: Option, } pub type RedisClient = redis::Client; diff --git a/crates/contextforge-gateway-rs-lib/src/layers/claims_id.rs b/crates/contextforge-gateway-rs-lib/src/layers/claims_id.rs index d2279fec..a1a13a5b 100644 --- a/crates/contextforge-gateway-rs-lib/src/layers/claims_id.rs +++ b/crates/contextforge-gateway-rs-lib/src/layers/claims_id.rs @@ -119,12 +119,14 @@ mod test { .full_name("API Token User".to_owned()) .is_admin(true) .build(), - scopes: Scopes::builder() - .server_id(Some("my_id".to_owned())) - .ip_restrictions(vec!["192.169.1.0/24".to_owned()]) - .permissions(vec!["tools.read".to_owned(), "servers.use".to_owned()]) - .time_restrictions(None) - .build(), + scopes: Some( + Scopes::builder() + .server_id(Some("my_id".to_owned())) + .ip_restrictions(vec!["192.169.1.0/24".to_owned()]) + .permissions(vec!["tools.read".to_owned(), "servers.use".to_owned()]) + .time_restrictions(None) + .build(), + ), } } @@ -180,6 +182,41 @@ mod test { assert_eq!(res.status(), StatusCode::OK); } + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[allow(clippy::items_after_statements)] + async fn claim_test_missing_scopes_is_allowed() { + CRYPTO.call_once(|| { + _ = rustls::crypto::ring::default_provider().install_default(); + }); + + async fn handle(_: HeaderMap) -> Response { + Response::builder().status(StatusCode::OK).body(Body::empty()).expect("Expecting this to work") + } + + let mut claims = active_test_claims(); + claims.scopes = None; + let token = get_hmac_token_for_claims(&claims); + + let decoding_key = DecodingKey::from_secret(HMAC_SECRET); + + let state = ContextForgeGatewayAppState { + jwt_token_decoding_keys: JwtTokenDecoders { rs: None, hmac_sha: Some(decoding_key) }, + config_store: Arc::new(MockedUserConfigStore {}), + config: Config::default(), + }; + let http_requst = Request::builder() + .header("Authorization", format!("Bearer {token}")) + .method("GET") + .body(Body::empty()) + .expect("This should work"); + + let app = + Router::new().route("/", get(handle)).layer(middleware::from_fn_with_state(state.clone(), claims_layer)); + + let res = app.oneshot(http_requst).await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[allow(clippy::items_after_statements)] async fn claim_test_expired_token() { diff --git a/crates/contextforge-gateway-rs-lib/src/tools.rs b/crates/contextforge-gateway-rs-lib/src/tools.rs index bb612509..0c29fffd 100644 --- a/crates/contextforge-gateway-rs-lib/src/tools.rs +++ b/crates/contextforge-gateway-rs-lib/src/tools.rs @@ -38,12 +38,14 @@ impl ContextForgeClaims { .full_name("API Token User".to_owned()) .is_admin(true) .build(), - scopes: Scopes::builder() - .server_id(Some("my_id".to_owned())) - .ip_restrictions(vec!["192.169.1.0/24".to_owned()]) - .permissions(vec!["tools.read".to_owned(), "servers.use".to_owned()]) - .time_restrictions(None) - .build(), + scopes: Some( + Scopes::builder() + .server_id(Some("my_id".to_owned())) + .ip_restrictions(vec!["192.169.1.0/24".to_owned()]) + .permissions(vec!["tools.read".to_owned(), "servers.use".to_owned()]) + .time_restrictions(None) + .build(), + ), } } }