diff --git a/src/api_key.rs b/src/api_key.rs index 6a70b1d..02881f4 100644 --- a/src/api_key.rs +++ b/src/api_key.rs @@ -7,20 +7,28 @@ use rand::Rng; use serde::Serialize; use sha3::{Digest, Sha3_256}; -const SECRET_LEN: usize = 16; +const DEFAULT_SECRET_LEN: usize = 16; +const MIN_SECRET_LEN: usize = 16; +const MAX_SECRET_LEN: usize = 32; const UUID_LEN: usize = 16; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApiKey { - pub relayer_id: String, - pub secret: [u8; SECRET_LEN], + relayer_id: String, + secret: Vec, } impl ApiKey { - pub fn new(relayer_id: impl ToString, secret: [u8; SECRET_LEN]) -> Self { + pub fn new( + relayer_id: impl ToString, + secret: Vec, + ) -> eyre::Result { + if secret.len() < MIN_SECRET_LEN || secret.len() > MAX_SECRET_LEN { + eyre::bail!("invalid api key"); + } let relayer_id = relayer_id.to_string(); - Self { relayer_id, secret } + Ok(Self { relayer_id, secret }) } pub fn random(relayer_id: impl ToString) -> Self { @@ -28,12 +36,16 @@ impl ApiKey { Self { relayer_id, - secret: OsRng.gen(), + secret: OsRng.gen::<[u8; DEFAULT_SECRET_LEN]>().into(), } } - pub fn api_key_hash(&self) -> [u8; 32] { - Sha3_256::digest(self.secret).into() + pub fn api_key_secret_hash(&self) -> [u8; 32] { + Sha3_256::digest(self.secret.clone()).into() + } + + pub fn relayer_id(&self) -> &str { + &self.relayer_id } } @@ -63,33 +75,34 @@ impl FromStr for ApiKey { fn from_str(s: &str) -> Result { let buffer = base64::prelude::BASE64_URL_SAFE.decode(s)?; - if buffer.len() != UUID_LEN + SECRET_LEN { - return Err(eyre::eyre!("invalid api key")); + if buffer.len() < UUID_LEN + MIN_SECRET_LEN + || buffer.len() > UUID_LEN + MAX_SECRET_LEN + { + eyre::bail!("invalid api key"); } let relayer_id = uuid::Uuid::from_slice(&buffer[..UUID_LEN])?; let relayer_id = relayer_id.to_string(); - let api_key = buffer[UUID_LEN..].try_into()?; + let secret = buffer[UUID_LEN..].into(); - Ok(Self { - relayer_id, - secret: api_key, - }) + Ok(Self { relayer_id, secret }) } } impl std::fmt::Display for ApiKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut buffer = [0u8; 32]; - let relayer_id = uuid::Uuid::parse_str(&self.relayer_id) .map_err(|_| std::fmt::Error)?; - buffer[..UUID_LEN].copy_from_slice(relayer_id.as_bytes()); - buffer[UUID_LEN..].copy_from_slice(&self.secret); + let bytes = relayer_id + .as_bytes() + .iter() + .cloned() + .chain(self.secret.iter().cloned()) + .collect::>(); - let encoded = base64::prelude::BASE64_URL_SAFE.encode(buffer); + let encoded = base64::prelude::BASE64_URL_SAFE.encode(bytes); write!(f, "{}", encoded) } @@ -102,7 +115,29 @@ mod tests { use super::*; fn random_api_key() -> ApiKey { - ApiKey::new(uuid::Uuid::new_v4().to_string(), OsRng.gen()) + ApiKey::new( + uuid::Uuid::new_v4().to_string(), + OsRng.gen::<[u8; DEFAULT_SECRET_LEN]>().into(), + ) + .unwrap() + } + + fn invalid_short_api_key() -> ApiKey { + let mut buf = [0u8; MAX_SECRET_LEN + 1]; + OsRng.fill(&mut buf[..]); + ApiKey { + relayer_id: uuid::Uuid::new_v4().to_string(), + secret: buf.into(), + } + } + + fn invalid_long_api_key() -> ApiKey { + let mut buf = [0u8; MAX_SECRET_LEN + 1]; + OsRng.fill(&mut buf[..]); + ApiKey { + relayer_id: uuid::Uuid::new_v4().to_string(), + secret: buf.into(), + } } #[test] @@ -118,6 +153,27 @@ mod tests { assert_eq!(api_key, api_key_parsed); } + #[test] + fn assert_api_key_length_validation() { + let long_api_key = invalid_long_api_key(); + let _ = ApiKey::new( + long_api_key.relayer_id.clone(), + long_api_key.secret.clone(), + ) + .expect_err("long api key should be invalid"); + let _ = ApiKey::from_str(long_api_key.to_string().as_str()) + .expect_err("long api key should be invalid"); + + let short_api_key = invalid_short_api_key(); + let _ = ApiKey::new( + short_api_key.relayer_id.clone(), + short_api_key.secret.clone(), + ) + .expect_err("short api key should be invalid"); + let _ = ApiKey::from_str(short_api_key.to_string().as_str()) + .expect_err("short api key should be invalid"); + } + #[test] fn from_to_serde_json() { let api_key = random_api_key(); diff --git a/src/app.rs b/src/app.rs index 712423a..f3a1c49 100644 --- a/src/app.rs +++ b/src/app.rs @@ -81,7 +81,10 @@ impl App { api_token: &ApiKey, ) -> eyre::Result { self.db - .is_api_key_valid(&api_token.relayer_id, api_token.api_key_hash()) + .is_api_key_valid( + api_token.relayer_id(), + api_token.api_key_secret_hash(), + ) .await } } diff --git a/src/server/routes/relayer.rs b/src/server/routes/relayer.rs index 88d923e..52b5b77 100644 --- a/src/server/routes/relayer.rs +++ b/src/server/routes/relayer.rs @@ -131,7 +131,7 @@ pub async fn relayer_rpc( return Err(ApiError::Unauthorized); } - let relayer_info = app.db.get_relayer(&api_token.relayer_id).await?; + let relayer_info = app.db.get_relayer(api_token.relayer_id()).await?; // TODO: Cache? let http_provider = app.http_provider(relayer_info.chain_id).await?; @@ -161,7 +161,7 @@ pub async fn create_relayer_api_key( let api_key = ApiKey::random(&relayer_id); app.db - .create_api_key(&relayer_id, api_key.api_key_hash()) + .create_api_key(&relayer_id, api_key.api_key_secret_hash()) .await?; Ok(Json(CreateApiKeyResponse { api_key })) diff --git a/src/server/routes/transaction.rs b/src/server/routes/transaction.rs index 75dceec..46d46e2 100644 --- a/src/server/routes/transaction.rs +++ b/src/server/routes/transaction.rs @@ -98,7 +98,7 @@ pub async fn send_tx( req.value, req.gas_limit, req.priority, - &api_token.relayer_id, + api_token.relayer_id(), ) .await?; @@ -120,13 +120,13 @@ pub async fn get_txs( let txs = match query.status { Some(GetTxResponseStatus::TxStatus(status)) => { app.db - .read_txs(&api_token.relayer_id, Some(Some(status))) + .read_txs(api_token.relayer_id(), Some(Some(status))) .await? } Some(GetTxResponseStatus::Unsent(_)) => { - app.db.read_txs(&api_token.relayer_id, Some(None)).await? + app.db.read_txs(api_token.relayer_id(), Some(None)).await? } - None => app.db.read_txs(&api_token.relayer_id, None).await?, + None => app.db.read_txs(api_token.relayer_id(), None).await?, }; let txs = diff --git a/src/service.rs b/src/service.rs index f08b663..94260ef 100644 --- a/src/service.rs +++ b/src/service.rs @@ -125,8 +125,8 @@ async fn initialize_predefined_values( app.db .create_api_key( - &predefined.relayer.api_key.relayer_id, - predefined.relayer.api_key.api_key_hash(), + predefined.relayer.api_key.relayer_id(), + predefined.relayer.api_key.api_key_secret_hash(), ) .await?;