Skip to content

Commit

Permalink
dynamic length api key
Browse files Browse the repository at this point in the history
  • Loading branch information
0xForerunner committed Jan 19, 2024
1 parent 1887340 commit 1c5268c
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 30 deletions.
98 changes: 77 additions & 21 deletions src/api_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,45 @@ 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<u8>,
}

impl ApiKey {
pub fn new(relayer_id: impl ToString, secret: [u8; SECRET_LEN]) -> Self {
pub fn new(
relayer_id: impl ToString,
secret: Vec<u8>,
) -> eyre::Result<Self> {
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 {
let relayer_id = relayer_id.to_string();

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
}
}

Expand Down Expand Up @@ -63,33 +75,34 @@ impl FromStr for ApiKey {
fn from_str(s: &str) -> Result<Self, Self::Err> {
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::<Vec<_>>();

let encoded = base64::prelude::BASE64_URL_SAFE.encode(buffer);
let encoded = base64::prelude::BASE64_URL_SAFE.encode(bytes);

write!(f, "{}", encoded)
}
Expand All @@ -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]
Expand All @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ impl App {
api_token: &ApiKey,
) -> eyre::Result<bool> {
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
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/server/routes/relayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down Expand Up @@ -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 }))
Expand Down
8 changes: 4 additions & 4 deletions src/server/routes/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand All @@ -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 =
Expand Down
4 changes: 2 additions & 2 deletions src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand Down

0 comments on commit 1c5268c

Please sign in to comment.