Skip to content

Commit

Permalink
feat: add api key validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lok52 committed Oct 31, 2024
1 parent 6e89c43 commit 319b1cc
Show file tree
Hide file tree
Showing 25 changed files with 259 additions and 25 deletions.
2 changes: 2 additions & 0 deletions multichain-aggregator/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions multichain-aggregator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ config = "0.13"
env-collector = { git = "https://github.com/blockscout/blockscout-rs", version = "0.1.1" }
pretty_assertions = "1.3"
reqwest = "0.12"
thiserror = "1.0"
34 changes: 34 additions & 0 deletions multichain-aggregator/multichain-aggregator-entity/src/api_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "api_keys")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(unique)]
pub key: Uuid,
pub chain_id: i64,
pub created_at: DateTime,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::chains::Entity",
from = "Column::ChainId",
to = "super::chains::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Chains,
}

impl Related<super::chains::Entity> for Entity {
fn to() -> RelationDef {
Relation::Chains.def()
}
}

impl ActiveModelBehavior for ActiveModel {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct Model {
pub enum Relation {
#[sea_orm(has_many = "super::addresses::Entity")]
Addresses,
#[sea_orm(has_many = "super::api_keys::Entity")]
ApiKeys,
#[sea_orm(has_one = "super::block_ranges::Entity")]
BlockRanges,
#[sea_orm(has_many = "super::dapps::Entity")]
Expand All @@ -31,6 +33,12 @@ impl Related<super::addresses::Entity> for Entity {
}
}

impl Related<super::api_keys::Entity> for Entity {
fn to() -> RelationDef {
Relation::ApiKeys.def()
}
}

impl Related<super::block_ranges::Entity> for Entity {
fn to() -> RelationDef {
Relation::BlockRanges.def()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod prelude;

pub mod addresses;
pub mod api_keys;
pub mod block_ranges;
pub mod chains;
pub mod dapps;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
pub use super::{
addresses::Entity as Addresses, block_ranges::Entity as BlockRanges, chains::Entity as Chains,
dapps::Entity as Dapps, hashes::Entity as Hashes,
addresses::Entity as Addresses, api_keys::Entity as ApiKeys,
block_ranges::Entity as BlockRanges, chains::Entity as Chains, dapps::Entity as Dapps,
hashes::Entity as Hashes,
};
7 changes: 4 additions & 3 deletions multichain-aggregator/multichain-aggregator-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ name = "multichain-aggregator-logic"
version = "0.1.0"
edition = "2021"


[dependencies]
multichain-aggregator-entity = { workspace = true }
multichain-aggregator-proto = { workspace = true }
anyhow = { workspace = true }
blockscout-chains = { workspace = true }
tracing = { workspace = true }
sea-orm = { workspace = true }
multichain-aggregator-entity = { workspace = true }
multichain-aggregator-proto = { workspace = true }
alloy-primitives = { workspace = true }
thiserror = { workspace = true }
tonic = { workspace = true }

[dev-dependencies]
blockscout-service-launcher = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::{
repository::api_keys,
types::api_keys::{ApiKey, ApiKeyError},
};
use sea_orm::DatabaseConnection;

pub struct ApiKeyManager {
db: DatabaseConnection,
}

impl ApiKeyManager {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}

pub async fn validate_api_key(&self, api_key: ApiKey) -> Result<(), ApiKeyError> {
let api_key =
api_keys::find_by_key_and_chain_id(&self.db, api_key.key, api_key.chain_id).await?;
if api_key.is_none() {
return Err(ApiKeyError::InvalidToken("Invalid API key".to_string()));
}
Ok(())
}
}
50 changes: 50 additions & 0 deletions multichain-aggregator/multichain-aggregator-logic/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::types::api_keys::ApiKeyError;
use alloy_primitives::hex::FromHexError;
use sea_orm::{sqlx::types::uuid, DbErr};
use std::num::ParseIntError;
use thiserror::Error;
use tonic::Code;

#[derive(Error, Debug)]
pub enum ServiceError {
#[error("api key error: {0}")]
ApiKey(#[from] ApiKeyError),
#[error(transparent)]
Convert(#[from] ParseError),
#[error("internal error: {0}")]
Internal(#[from] anyhow::Error),
#[error("db error: {0}")]
Db(#[from] DbErr),
#[error("not found: {0}")]
NotFound(String),
}

#[derive(Error, Debug)]
pub enum ParseError {
#[error("parse error: invalid integer")]
ParseInt(#[from] ParseIntError),
#[error("parse error: invalid hex")]
ParseHex(#[from] FromHexError),
#[error("parse error: invalid uuid")]
ParseUuid(#[from] uuid::Error),
}

impl From<ServiceError> for tonic::Status {
fn from(err: ServiceError) -> Self {
let code = match &err {
ServiceError::ApiKey(err) => map_api_key_code(err),
ServiceError::Convert(_) => Code::InvalidArgument,
ServiceError::Internal(_) => Code::Internal,
ServiceError::NotFound(_) => Code::NotFound,
ServiceError::Db(_) => Code::Internal,
};
tonic::Status::new(code, err.to_string())
}
}

fn map_api_key_code(err: &ApiKeyError) -> Code {
match err {
ApiKeyError::InvalidToken(_) => Code::PermissionDenied,
ApiKeyError::Db(_) => Code::Internal,
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::{repository, types::batch_import_request::BatchImportRequest};
use crate::{error::ServiceError, repository, types::batch_import_request::BatchImportRequest};
use sea_orm::{DatabaseConnection, TransactionTrait};

pub async fn batch_import(
db: &DatabaseConnection,
request: BatchImportRequest,
) -> anyhow::Result<()> {
) -> Result<(), ServiceError> {
let tx = db.begin().await?;
repository::addresses::upsert_many(&tx, request.addresses).await?;
repository::block_ranges::upsert_many(&tx, request.block_ranges).await?;
Expand Down
4 changes: 3 additions & 1 deletion multichain-aggregator/multichain-aggregator-logic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod api_key_manager;
pub mod error;
mod import;
pub mod repository;
mod types;

pub use import::batch_import;
pub use types::batch_import_request::BatchImportRequest;
pub use types::{api_keys::ApiKey, batch_import_request::BatchImportRequest};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::types::addresses::Address;
use entity::addresses::{ActiveModel, Column, Entity, Model};
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, EntityTrait,
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, DbErr, EntityTrait,
Iterable,
};

pub async fn upsert_many<C>(db: &C, addresses: Vec<Address>) -> anyhow::Result<()>
pub async fn upsert_many<C>(db: &C, addresses: Vec<Address>) -> Result<(), DbErr>
where
C: ConnectionTrait,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::types::api_keys::ApiKey;
use entity::api_keys::{Column, Entity};
use sea_orm::{prelude::Uuid, ColumnTrait, DatabaseConnection, DbErr, EntityTrait, QueryFilter};

pub async fn find_by_key_and_chain_id(
db: &DatabaseConnection,
key: Uuid,
chain_id: i64,
) -> Result<Option<ApiKey>, DbErr> {
let api_key = Entity::find()
.filter(Column::Key.eq(key))
.filter(Column::ChainId.eq(chain_id))
.one(db)
.await?
.map(ApiKey::from);

Ok(api_key)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::types::block_ranges::BlockRange;
use entity::block_ranges::{ActiveModel, Column, Entity, Model};
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, EntityTrait,
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, DbErr, EntityTrait,
};

pub async fn upsert_many<C>(db: &C, block_ranges: Vec<BlockRange>) -> anyhow::Result<()>
pub async fn upsert_many<C>(db: &C, block_ranges: Vec<BlockRange>) -> Result<(), DbErr>
where
C: ConnectionTrait,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::types::chains::Chain;
use entity::chains::{ActiveModel, Column, Entity, Model};
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, EntityTrait,
prelude::Expr, sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, DbErr, EntityTrait,
};

pub async fn upsert_many<C>(db: &C, chains: Vec<Chain>) -> anyhow::Result<()>
pub async fn upsert_many<C>(db: &C, chains: Vec<Chain>) -> Result<(), DbErr>
where
C: ConnectionTrait,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::types::hashes::Hash;
use entity::hashes::{ActiveModel, Column, Entity, Model};
use sea_orm::{sea_query::OnConflict, ActiveValue::NotSet, ConnectionTrait, DbErr, EntityTrait};

pub async fn upsert_many<C>(db: &C, hashes: Vec<Hash>) -> anyhow::Result<()>
pub async fn upsert_many<C>(db: &C, hashes: Vec<Hash>) -> Result<(), DbErr>
where
C: ConnectionTrait,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod addresses;
pub mod api_keys;
pub mod block_ranges;
pub mod chains;
pub mod hashes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use super::ChainId;
use crate::error::{ParseError, ServiceError};
use entity::api_keys::Model;
use sea_orm::{entity::prelude::Uuid, DbErr};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApiKeyError {
#[error("invalid token: {0}")]
InvalidToken(String),
#[error("db error: {0}")]
Db(#[from] DbErr),
}

#[derive(Debug, Clone)]
pub struct ApiKey {
pub key: Uuid,
pub chain_id: ChainId,
}

impl TryFrom<(&str, &str)> for ApiKey {
type Error = ServiceError;

fn try_from(v: (&str, &str)) -> Result<Self, Self::Error> {
Ok(Self {
key: Uuid::parse_str(v.0).map_err(ParseError::from)?,
chain_id: v.1.parse().map_err(ParseError::from)?,
})
}
}

impl From<Model> for ApiKey {
fn from(v: Model) -> Self {
Self {
key: v.key,
chain_id: v.chain_id,
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{addresses::Address, block_ranges::BlockRange, hashes::Hash};
use crate::error::{ParseError, ServiceError};
use entity::sea_orm_active_enums as db_enum;
use multichain_aggregator_proto::blockscout::multichain_aggregator::v1::{
self, address_upsert as proto_address, hash_upsert as proto_hash,
Expand All @@ -12,10 +13,10 @@ pub struct BatchImportRequest {
}

impl TryFrom<v1::BatchImportRequest> for BatchImportRequest {
type Error = anyhow::Error;
type Error = ServiceError;

fn try_from(value: v1::BatchImportRequest) -> Result<Self, Self::Error> {
let chain_id = value.chain_id.parse()?;
let chain_id = value.chain_id.parse().map_err(ParseError::from)?;
Ok(Self {
block_ranges: value
.block_ranges
Expand All @@ -30,20 +31,20 @@ impl TryFrom<v1::BatchImportRequest> for BatchImportRequest {
.hashes
.into_iter()
.map(|h| {
let hash = h.hash.parse()?;
let hash = h.hash.parse().map_err(ParseError::from)?;
let hash_type = proto_hash_type_to_db_hash_type(h.hash_type());
Ok(Hash {
chain_id,
hash,
hash_type,
})
})
.collect::<anyhow::Result<Vec<_>>>()?,
.collect::<Result<Vec<_>, Self::Error>>()?,
addresses: value
.addresses
.into_iter()
.map(|a| {
let hash = a.hash.parse()?;
let hash = a.hash.parse().map_err(ParseError::from)?;
let token_type = proto_token_type_to_db_token_type(a.token_type());

Ok(Address {
Expand All @@ -58,7 +59,7 @@ impl TryFrom<v1::BatchImportRequest> for BatchImportRequest {
is_token: a.is_token.unwrap_or(false),
})
})
.collect::<anyhow::Result<Vec<_>>>()?,
.collect::<Result<Vec<_>, Self::Error>>()?,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod addresses;
pub mod api_keys;
pub mod batch_import_request;
pub mod block_ranges;
pub mod chains;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ pub use sea_orm_migration::prelude::*;
use sea_orm_migration::sea_orm::{Statement, TransactionTrait};

mod m20220101_000001_initial_tables;
mod m20241031_064924_add_api_keys;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220101_000001_initial_tables::Migration)]
vec![
Box::new(m20220101_000001_initial_tables::Migration),
Box::new(m20241031_064924_add_api_keys::Migration),
]
}
}

Expand Down
Loading

0 comments on commit 319b1cc

Please sign in to comment.