Skip to content

Commit 090e2b9

Browse files
committed
Adding CF claims and HMAC signing verification.4
Signed-off-by: Dawid Nowak <nowakd@gmail.com>
1 parent 00e0a7b commit 090e2b9

3 files changed

Lines changed: 102 additions & 15 deletions

File tree

crates/contextforge-gateway-rs-lib/src/layers/claims_id.rs

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,117 @@ pub async fn claims_layer(
7474
#[cfg(test)]
7575
mod test {
7676

77+
use std::sync::{Arc, Once};
78+
79+
use async_trait::async_trait;
80+
use axum::{Router, body::Body, middleware, response::Response, routing::get};
81+
use contextforge_gateway_rs_apis::{User, user_store::UserConfig};
82+
use http::{HeaderMap, Request, StatusCode};
7783
use jsonwebtoken::{DecodingKey, Validation};
84+
use tower::ServiceExt;
7885

7986
use crate::{
80-
common::ContextForgeClaims,
87+
Config,
88+
common::{ContextForgeClaims, ContextForgeGatewayAppState, JwtTokenDecoders},
8189
const_values::{CONTEXT_FORGE_GATEWAY_AUDIENCE, CONTEXT_FORGE_GATEWAY_ISSUER},
90+
layers::claims_id::claims_layer,
91+
tests,
92+
user_config_store::{ConfigStoreError, UserConfigStore},
8293
};
8394

84-
#[test]
85-
fn claim_test() {
86-
rustls::crypto::ring::default_provider().install_default().expect("Failed to install rustls crypto provider");
95+
static CRYPTO: Once = Once::new();
96+
97+
struct MockedUserConfigStore;
98+
#[async_trait]
99+
impl UserConfigStore for MockedUserConfigStore {
100+
async fn get_config<'a>(&self, _: &'a User) -> Result<UserConfig, ConfigStoreError> {
101+
Err(ConfigStoreError::InvalidConnection)
102+
}
103+
104+
async fn set_config<'a>(&self, _: &'a User, _: &'a UserConfig) -> Result<(), ConfigStoreError> {
105+
Err(ConfigStoreError::InvalidConnection)
106+
}
107+
}
108+
109+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
110+
#[allow(clippy::items_after_statements)]
111+
async fn claim_test_valid_hmac() {
112+
CRYPTO.call_once(|| {
113+
rustls::crypto::ring::default_provider()
114+
.install_default()
115+
.expect("Failed to install rustls crypto provider");
116+
});
117+
118+
async fn handle(_: HeaderMap) -> Response {
119+
Response::builder().status(StatusCode::OK).body(Body::empty()).expect("Expecting this to work")
120+
}
87121

88122
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsImp0aSI6Ijc1ZWYwZTZjLTZkZWMtNGExNy1hNzU3LWFlYmYzZjk1N2Q1NSIsInRva2VuX3VzZSI6ImFwaSIsImlhdCI6MTc3ODg2NTE2OCwiaXNzIjoibWNwZ2F0ZXdheSIsImF1ZCI6Im1jcGdhdGV3YXktYXBpIiwidXNlciI6eyJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZnVsbF9uYW1lIjoiQVBJIFRva2VuIFVzZXIiLCJpc19hZG1pbiI6dHJ1ZSwiYXV0aF9wcm92aWRlciI6ImFwaV90b2tlbiJ9LCJ0ZWFtcyI6bnVsbCwic2NvcGVzIjp7InNlcnZlcl9pZCI6bnVsbCwicGVybWlzc2lvbnMiOltdLCJpcF9yZXN0cmljdGlvbnMiOltdLCJ0aW1lX3Jlc3RyaWN0aW9ucyI6e319LCJleHAiOjE3ODE0NTcxNjh9.9d2-iLOHL2dJRFTSbOxHzuD6zLxupqK0ZkCG-3GZABU";
89123

90-
let Ok(header) = jsonwebtoken::decode_header(token) else {
91-
panic!();
124+
let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);
125+
validation.set_audience(&[CONTEXT_FORGE_GATEWAY_AUDIENCE]);
126+
validation.set_issuer(&[CONTEXT_FORGE_GATEWAY_ISSUER]);
127+
validation.validate_exp = false;
128+
129+
let decoding_key = DecodingKey::from_secret("my-test-key-but-now-longer-than-32-bytes".as_bytes());
130+
131+
let state = ContextForgeGatewayAppState {
132+
jwt_token_decoding_keys: JwtTokenDecoders { rs: None, hmac_sha: Some(decoding_key) },
133+
config_store: Arc::new(MockedUserConfigStore {}),
134+
config: Config::default(),
92135
};
136+
let http_requst = Request::builder()
137+
.header("Authorization", format!("Bearer {token}"))
138+
.method("GET")
139+
.body(Body::empty())
140+
.expect("This should work");
141+
142+
let app =
143+
Router::new().route("/", get(handle)).layer(middleware::from_fn_with_state(state.clone(), claims_layer));
144+
145+
let res = app.oneshot(http_requst).await.unwrap();
146+
assert_eq!(res.status(), StatusCode::OK);
147+
}
148+
149+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
150+
#[allow(clippy::items_after_statements)]
151+
async fn claim_test_expired_token() {
152+
CRYPTO.call_once(|| {
153+
rustls::crypto::ring::default_provider()
154+
.install_default()
155+
.expect("Failed to install rustls crypto provider");
156+
});
157+
158+
async fn handle(_: HeaderMap) -> Response {
159+
Response::builder().status(StatusCode::OK).body(Body::empty()).expect("Expecting this to work")
160+
}
93161

94-
let mut validation = Validation::new(header.alg);
162+
let mut claims = ContextForgeClaims::new("blah@blah.com");
163+
claims.exp = 0;
164+
let token = tests::gateway_end_to_end::get_token_for_claims(&claims);
165+
166+
let mut validation = Validation::new(jsonwebtoken::Algorithm::RS256);
95167
validation.set_audience(&[CONTEXT_FORGE_GATEWAY_AUDIENCE]);
96168
validation.set_issuer(&[CONTEXT_FORGE_GATEWAY_ISSUER]);
97-
validation.validate_exp = false;
169+
validation.validate_exp = true;
98170

99171
let decoding_key = DecodingKey::from_secret("my-test-key-but-now-longer-than-32-bytes".as_bytes());
100172

101-
let maybe_valid = jsonwebtoken::decode::<ContextForgeClaims>(token, &decoding_key, &validation);
102-
maybe_valid.expect("token invalid");
173+
let state = ContextForgeGatewayAppState {
174+
jwt_token_decoding_keys: JwtTokenDecoders { rs: None, hmac_sha: Some(decoding_key) },
175+
config_store: Arc::new(MockedUserConfigStore {}),
176+
config: Config::default(),
177+
};
178+
let http_requst = Request::builder()
179+
.header("Authorization", format!("Bearer {token}"))
180+
.method("GET")
181+
.body(Body::empty())
182+
.expect("This should work");
183+
184+
let app =
185+
Router::new().route("/", get(handle)).layer(middleware::from_fn_with_state(state.clone(), claims_layer));
186+
187+
let res = app.oneshot(http_requst).await.unwrap();
188+
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
103189
}
104190
}

crates/contextforge-gateway-rs-lib/src/tests/gateway_end_to_end.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,17 @@ async fn create_axum_tls_servers(ports: &[u16], router: axum::Router) -> Vec<Box
104104
.collect()
105105
}
106106

107-
pub fn get_token(user_id: &str) -> String {
107+
pub fn get_token_for_claims(claims: &ContextForgeClaims) -> String {
108108
let key = EncodingKey::from_rsa_pem(&fs::read("../../assets/jwt.key").expect("Expecting this to work"))
109109
.expect("Expecting this to work");
110110
let mut header = Header::new(Algorithm::RS256);
111111
header.kid = Some("test".to_owned());
112112

113-
let claims = ContextForgeClaims::new(user_id);
113+
encode::<ContextForgeClaims>(&header, claims, &key).expect("Expecting this to work")
114+
}
114115

115-
encode::<ContextForgeClaims>(&header, &claims, &key).expect("Expecting this to work")
116+
pub fn get_token(user_id: &str) -> String {
117+
get_token_for_claims(&ContextForgeClaims::new(user_id))
116118
}
117119

118120
struct TestSettings {
@@ -161,7 +163,6 @@ async fn create_gateway_with_four_counters(user: &str, config: Config) -> crate:
161163

162164
let gateway = Gateway::builder()
163165
.with_config(config.clone())
164-
//.with_user_config_store(Arc::new(mocked_user_config_store))
165166
.with_session_manager(Arc::new(LocalSessionManager::default()))
166167
.with_user_config_store_type(crate::UserConfigStoreType::Test(Arc::new(mocked_user_config_store)))
167168
.build();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
mod gateway_end_to_end;
1+
pub mod gateway_end_to_end;
22
mod mock_counter;
33
mod mocked_user_config_store;

0 commit comments

Comments
 (0)