diff --git a/Cargo.lock b/Cargo.lock index 665acf5..1f69351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,20 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.8" @@ -262,7 +276,7 @@ dependencies = [ [[package]] name = "arbitrum-client" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -702,8 +716,12 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" name = "auth-server" version = "0.1.0" dependencies = [ + "aes-gcm", + "auth-server-api", + "base64 0.22.1", "bb8", "bytes", + "chrono", "clap", "diesel", "diesel-async", @@ -712,6 +730,7 @@ dependencies = [ "hyper 0.14.30", "native-tls", "postgres-native-tls", + "rand 0.8.5", "reqwest 0.11.27", "serde", "serde_json", @@ -719,9 +738,19 @@ dependencies = [ "tokio", "tokio-postgres", "tracing", + "util", + "uuid 1.10.0", "warp", ] +[[package]] +name = "auth-server-api" +version = "0.1.0" +dependencies = [ + "serde", + "uuid 1.10.0", +] + [[package]] name = "auto_impl" version = "1.2.0" @@ -1613,7 +1642,7 @@ dependencies = [ [[package]] name = "circuit-macros" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "itertools 0.10.5", "proc-macro2", @@ -1624,7 +1653,7 @@ dependencies = [ [[package]] name = "circuit-types" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-bn254", "ark-ec", @@ -1655,7 +1684,7 @@ dependencies = [ [[package]] name = "circuits" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-crypto-primitives", "ark-ec", @@ -1799,7 +1828,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-mpc", "async-trait", @@ -1864,7 +1893,7 @@ dependencies = [ [[package]] name = "config" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "arbitrum-client", "base64 0.13.1", @@ -1916,7 +1945,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constants" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-bn254", "ark-ec", @@ -2291,6 +2320,7 @@ dependencies = [ "bigdecimal 0.4.5", "bitflags 2.6.0", "byteorder", + "chrono", "diesel_derives", "itoa", "num-bigint", @@ -2971,7 +3001,7 @@ dependencies = [ [[package]] name = "external-api" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "base64 0.22.1", "circuit-types", @@ -3374,6 +3404,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug 0.3.1", + "polyval", +] + [[package]] name = "gimli" version = "0.31.0" @@ -3401,7 +3441,7 @@ dependencies = [ [[package]] name = "gossip-api" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "bincode", "circuit-types", @@ -4083,7 +4123,7 @@ dependencies = [ [[package]] name = "job-types" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-mpc", "circuit-types", @@ -5442,6 +5482,18 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.1", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.9.0" @@ -5546,7 +5598,7 @@ dependencies = [ [[package]] name = "price-reporter" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "async-trait", "atomic_float 0.1.0", @@ -6059,7 +6111,7 @@ dependencies = [ [[package]] name = "renegade-crypto" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-ec", "ark-ff 0.4.2", @@ -6123,7 +6175,7 @@ dependencies = [ [[package]] name = "renegade-metrics" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "atomic_float 1.1.0", "circuit-types", @@ -7375,7 +7427,7 @@ dependencies = [ [[package]] name = "system-bus" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "bus", "common", @@ -8164,7 +8216,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/renegade-fi/renegade.git#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" +source = "git+https://github.com/renegade-fi/renegade#99cc160e9db51ade72d0310b44cf3e1a9b7d9da8" dependencies = [ "ark-ec", "ark-serialize 0.4.2", diff --git a/Cargo.toml b/Cargo.toml index 0914efb..8516379 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [workspace] members = [ + "auth/auth-server", + "auth/auth-server-api", "compliance/compliance-server", "compliance/compliance-api", "dealer/renegade-dealer", @@ -7,7 +9,6 @@ members = [ "funds-manager/funds-manager-api", "funds-manager/funds-manager-server", "price-reporter", - "auth-server", ] [profile.bench] diff --git a/auth/auth-server-api/Cargo.toml b/auth/auth-server-api/Cargo.toml new file mode 100644 index 0000000..f5effe1 --- /dev/null +++ b/auth/auth-server-api/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "auth-server-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +uuid = "1.0" diff --git a/auth/auth-server-api/src/lib.rs b/auth/auth-server-api/src/lib.rs new file mode 100644 index 0000000..2c1ace6 --- /dev/null +++ b/auth/auth-server-api/src/lib.rs @@ -0,0 +1,36 @@ +//! API types for the auth server + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![deny(unsafe_code)] +#![deny(clippy::needless_pass_by_ref_mut)] +#![feature(trivial_bounds)] + +use serde::Deserialize; +use uuid::Uuid; + +// ---------------------- +// | API Key Management | +// ---------------------- + +/// The path to create a new API key +/// +/// POST /api-keys +pub const API_KEYS_PATH: &str = "api-keys"; +/// The path to mark an API key as inactive +/// +/// POST /api-keys/{id}/deactivate +pub const DEACTIVATE_API_KEY_PATH: &str = "deactivate"; + +/// A request to create a new API key +#[derive(Debug, Deserialize)] +pub struct CreateApiKeyRequest { + /// The API key id + pub id: Uuid, + /// The API key secret + /// + /// Expected as a base64 encoded string + pub secret: String, + /// The name of the API key + pub name: String, +} diff --git a/auth-server/Cargo.toml b/auth/auth-server/Cargo.toml similarity index 61% rename from auth-server/Cargo.toml rename to auth/auth-server/Cargo.toml index 9206473..b5cc0c4 100644 --- a/auth-server/Cargo.toml +++ b/auth/auth-server/Cargo.toml @@ -14,16 +14,27 @@ warp = "0.3" # === Database === # bb8 = "0.8" -diesel = { version = "2", features = ["postgres"] } +diesel = { version = "2", features = ["postgres", "chrono", "uuid"] } diesel-async = { version = "0.4", features = ["postgres", "bb8"] } tokio-postgres = "0.7" postgres-native-tls = "0.5" native-tls = "0.2" +# === Cryptography === # +aes-gcm = "0.10.1" +rand = "0.8.5" + +# === Renegade Dependencies === # +auth-server-api = { path = "../auth-server-api" } +renegade-utils = { package = "util", git = "https://github.com/renegade-fi/renegade" } + # === Misc Dependencies === # +base64 = "0.22.1" bytes = "1.0" +chrono = { version = "0.4", features = ["serde"] } futures-util = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" tracing = "0.1" +uuid = { version = "1.0", features = ["serde", "v4"] } diff --git a/auth-server/Dockerfile b/auth/auth-server/Dockerfile similarity index 100% rename from auth-server/Dockerfile rename to auth/auth-server/Dockerfile diff --git a/auth-server/diesel.toml b/auth/auth-server/diesel.toml similarity index 100% rename from auth-server/diesel.toml rename to auth/auth-server/diesel.toml diff --git a/auth-server/migrations/.keep b/auth/auth-server/migrations/.keep similarity index 100% rename from auth-server/migrations/.keep rename to auth/auth-server/migrations/.keep diff --git a/auth-server/migrations/00000000000000_diesel_initial_setup/down.sql b/auth/auth-server/migrations/00000000000000_diesel_initial_setup/down.sql similarity index 100% rename from auth-server/migrations/00000000000000_diesel_initial_setup/down.sql rename to auth/auth-server/migrations/00000000000000_diesel_initial_setup/down.sql diff --git a/auth-server/migrations/00000000000000_diesel_initial_setup/up.sql b/auth/auth-server/migrations/00000000000000_diesel_initial_setup/up.sql similarity index 100% rename from auth-server/migrations/00000000000000_diesel_initial_setup/up.sql rename to auth/auth-server/migrations/00000000000000_diesel_initial_setup/up.sql diff --git a/auth-server/migrations/2024-10-21-205301_add_api_keys_table/down.sql b/auth/auth-server/migrations/2024-10-21-205301_add_api_keys_table/down.sql similarity index 100% rename from auth-server/migrations/2024-10-21-205301_add_api_keys_table/down.sql rename to auth/auth-server/migrations/2024-10-21-205301_add_api_keys_table/down.sql diff --git a/auth-server/migrations/2024-10-21-205301_add_api_keys_table/up.sql b/auth/auth-server/migrations/2024-10-21-205301_add_api_keys_table/up.sql similarity index 100% rename from auth-server/migrations/2024-10-21-205301_add_api_keys_table/up.sql rename to auth/auth-server/migrations/2024-10-21-205301_add_api_keys_table/up.sql diff --git a/auth/auth-server/src/error.rs b/auth/auth-server/src/error.rs new file mode 100644 index 0000000..b2e9407 --- /dev/null +++ b/auth/auth-server/src/error.rs @@ -0,0 +1,38 @@ +//! Error types for the auth server + +use thiserror::Error; + +/// Custom error type for server errors +#[derive(Error, Debug)] +pub enum AuthServerError { + /// Database connection error + #[error("Database connection error: {0}")] + DatabaseConnection(String), + + /// Encryption error + #[error("Encryption error: {0}")] + Encryption(String), + + /// Decryption error + #[error("Decryption error: {0}")] + Decryption(String), +} + +impl AuthServerError { + /// Create a new database connection error + pub fn db(msg: T) -> Self { + Self::DatabaseConnection(msg.to_string()) + } + + /// Create a new encryption error + pub fn encryption(msg: T) -> Self { + Self::Encryption(msg.to_string()) + } + + /// Create a new decryption error + pub fn decryption(msg: T) -> Self { + Self::Decryption(msg.to_string()) + } +} + +impl warp::reject::Reject for AuthServerError {} diff --git a/auth-server/src/main.rs b/auth/auth-server/src/main.rs similarity index 72% rename from auth-server/src/main.rs rename to auth/auth-server/src/main.rs index 927a906..444f5b7 100644 --- a/auth-server/src/main.rs +++ b/auth/auth-server/src/main.rs @@ -11,21 +11,29 @@ #![deny(clippy::needless_pass_by_ref_mut)] #![feature(trivial_bounds)] +pub(crate) mod error; +pub(crate) mod models; #[allow(missing_docs, clippy::missing_docs_in_private_items)] pub(crate) mod schema; mod server; +use auth_server_api::{API_KEYS_PATH, DEACTIVATE_API_KEY_PATH}; use clap::Parser; +use renegade_utils::telemetry::configure_telemetry; use reqwest::StatusCode; use serde_json::json; use std::net::SocketAddr; use std::sync::Arc; use thiserror::Error; use tracing::{error, info}; +use uuid::Uuid; use warp::{Filter, Rejection, Reply}; use server::Server; +/// The default internal server error message +const DEFAULT_INTERNAL_SERVER_ERROR_MESSAGE: &str = "Internal Server Error"; + // ------- // | CLI | // ------- @@ -82,12 +90,21 @@ async fn main() { let args = Cli::parse(); let listen_addr: SocketAddr = ([0, 0, 0, 0], args.port).into(); + // Setup logging + configure_telemetry( + args.datadog_logging, // datadog_enabled + false, // otlp_enabled + false, // metrics_enabled + "".to_string(), // collector_endpoint + "", // statsd_host + 0, // statsd_port + ) + .expect("failed to setup telemetry"); + // Create the server let server = Server::new(args).await.expect("Failed to create server"); let server = Arc::new(server); - // TODO: Setup logging - // --- Routes --- // // Ping route @@ -95,6 +112,21 @@ async fn main() { .and(warp::get()) .map(|| warp::reply::with_status("PONG", StatusCode::OK)); + // Add an API key + let add_api_key = warp::path(API_KEYS_PATH) + .and(warp::post()) + .and(warp::body::json()) + .and(with_server(server.clone())) + .and_then(|request, server: Arc| async move { server.add_key(request).await }); + + // Expire an API key + let expire_api_key = warp::path(API_KEYS_PATH) + .and(warp::path::param::()) + .and(warp::path(DEACTIVATE_API_KEY_PATH)) + .and(warp::post()) + .and(with_server(server.clone())) + .and_then(|id: Uuid, server: Arc| async move { server.expire_key(id).await }); + // Proxy route let proxy = warp::path::full() .and(warp::method()) @@ -107,7 +139,7 @@ async fn main() { // Bind the server and listen info!("Starting auth server on port {}", listen_addr.port()); - let routes = ping.or(proxy).recover(handle_rejection); + let routes = ping.or(add_api_key).or(expire_api_key).or(proxy).recover(handle_rejection); warp::serve(routes).bind(listen_addr).await; } @@ -122,8 +154,11 @@ fn with_server( async fn handle_rejection(err: Rejection) -> Result { if let Some(api_error) = err.find::() { let (code, message) = match api_error { - ApiError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), - ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg), + ApiError::InternalError(e) => { + error!("Internal server error: {e}"); + (StatusCode::INTERNAL_SERVER_ERROR, DEFAULT_INTERNAL_SERVER_ERROR_MESSAGE) + }, + ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()), }; Ok(json_error(message, code)) diff --git a/auth/auth-server/src/models.rs b/auth/auth-server/src/models.rs new file mode 100644 index 0000000..0da2b5e --- /dev/null +++ b/auth/auth-server/src/models.rs @@ -0,0 +1,31 @@ +//! DB model types for the auth server +#![allow(missing_docs, clippy::missing_docs_in_private_items)] + +use crate::schema::api_keys; +use diesel::prelude::*; +use diesel::sql_types::Timestamp; +use uuid::Uuid; + +#[derive(Queryable)] +pub struct ApiKey { + pub id: Uuid, + pub encrypted_key: String, + pub username: String, + pub created_at: Timestamp, + pub is_active: bool, +} + +#[derive(Insertable)] +#[diesel(table_name = api_keys)] +pub struct NewApiKey { + pub id: Uuid, + pub encrypted_key: String, + pub username: String, +} + +impl NewApiKey { + /// Create a new API key + pub fn new(id: Uuid, encrypted_key: String, username: String) -> Self { + Self { id, encrypted_key, username } + } +} diff --git a/auth-server/src/schema.rs b/auth/auth-server/src/schema.rs similarity index 100% rename from auth-server/src/schema.rs rename to auth/auth-server/src/schema.rs diff --git a/auth/auth-server/src/server/handle_key_management.rs b/auth/auth-server/src/server/handle_key_management.rs new file mode 100644 index 0000000..5cc4b6e --- /dev/null +++ b/auth/auth-server/src/server/handle_key_management.rs @@ -0,0 +1,35 @@ +//! Handles key management requests + +use crate::models::NewApiKey; +use auth_server_api::CreateApiKeyRequest; +use uuid::Uuid; +use warp::{reject::Rejection, reply::Reply}; + +use crate::ApiError; + +use super::{ + helpers::{aes_encrypt, empty_json_reply}, + Server, +}; + +impl Server { + /// Add a new API key to the database + pub async fn add_key(&self, req: CreateApiKeyRequest) -> Result { + let encrypted_secret = aes_encrypt(&req.secret, &self.encryption_key)?; + let new_key = NewApiKey::new(req.id, encrypted_secret, req.name); + self.add_key_query(new_key) + .await + .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; + + Ok(empty_json_reply()) + } + + /// Expire an existing API key + pub async fn expire_key(&self, key_id: Uuid) -> Result { + self.expire_key_query(key_id) + .await + .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; + + Ok(empty_json_reply()) + } +} diff --git a/auth-server/src/server/handle_proxy.rs b/auth/auth-server/src/server/handle_proxy.rs similarity index 94% rename from auth-server/src/server/handle_proxy.rs rename to auth/auth-server/src/server/handle_proxy.rs index 2965911..30db2b0 100644 --- a/auth-server/src/server/handle_proxy.rs +++ b/auth/auth-server/src/server/handle_proxy.rs @@ -32,8 +32,7 @@ impl Server { let headers = resp.headers().clone(); let body = resp.bytes().await.map_err(|e| { warp::reject::custom(ApiError::InternalError(format!( - "Failed to read response body: {}", - e + "Failed to read response body: {e}" ))) })?; diff --git a/auth/auth-server/src/server/helpers.rs b/auth/auth-server/src/server/helpers.rs new file mode 100644 index 0000000..6101d41 --- /dev/null +++ b/auth/auth-server/src/server/helpers.rs @@ -0,0 +1,68 @@ +//! Helper methods for the auth server + +use aes_gcm::{ + aead::{Aead, KeyInit}, + AeadCore, Aes128Gcm, +}; +use base64::{engine::general_purpose, Engine as _}; +use rand::thread_rng; +use serde_json::json; +use warp::reply::Reply; + +use crate::error::AuthServerError; + +/// The nonce size for AES128-GCM +const NONCE_SIZE: usize = 12; // 12 bytes, 96 bits + +/// Construct empty json reply +pub fn empty_json_reply() -> impl Reply { + warp::reply::json(&json!({})) +} + +/// AES encrypt a value +/// +/// Returns a base64 encoded string of the format [nonce, ciphertext] +pub fn aes_encrypt(value: &str, key: &[u8]) -> Result { + let mut rng = thread_rng(); + let cipher = Aes128Gcm::new_from_slice(key).map_err(AuthServerError::encryption)?; + let nonce = Aes128Gcm::generate_nonce(&mut rng); + let ciphertext = + cipher.encrypt(&nonce, value.as_bytes()).map_err(AuthServerError::encryption)?; + + // Encode the [nonce, ciphertext] as a base64 string + let digest = [nonce.as_slice(), ciphertext.as_slice()].concat(); + let encoded = general_purpose::STANDARD.encode(digest); + Ok(encoded) +} + +/// AES decrypt a value +/// +/// Assumes that the input is a base64 encoded string of the format [nonce, +/// ciphertext] +pub fn aes_decrypt(value: &str, key: &[u8]) -> Result { + let decoded = general_purpose::STANDARD.decode(value).map_err(AuthServerError::decryption)?; + let (nonce, ciphertext) = decoded.split_at(NONCE_SIZE); + + let cipher = Aes128Gcm::new_from_slice(key).map_err(AuthServerError::decryption)?; + let plaintext_bytes = + cipher.decrypt(nonce.into(), ciphertext).map_err(AuthServerError::decryption)?; + let plaintext = String::from_utf8(plaintext_bytes).map_err(AuthServerError::decryption)?; + Ok(plaintext) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests AES encryption and decryption + #[test] + fn test_aes_encrypt_decrypt() { + let mut rng = thread_rng(); + let key = Aes128Gcm::generate_key(&mut rng); + let value = "test string"; + + let encrypted = aes_encrypt(value, &key).unwrap(); + let decrypted = aes_decrypt(&encrypted, &key).unwrap(); + assert_eq!(value, decrypted); + } +} diff --git a/auth-server/src/server/mod.rs b/auth/auth-server/src/server/mod.rs similarity index 73% rename from auth-server/src/server/mod.rs rename to auth/auth-server/src/server/mod.rs index c399911..a2d4414 100644 --- a/auth-server/src/server/mod.rs +++ b/auth/auth-server/src/server/mod.rs @@ -1,7 +1,13 @@ //! Defines the server struct and associated functions //! //! The server is a dependency injection container for the authentication server -use crate::Cli; +mod handle_key_management; +mod handle_proxy; +mod helpers; +mod queries; + +use crate::{error::AuthServerError, Cli}; +use base64::{engine::general_purpose, Engine}; use bb8::{Pool, PooledConnection}; use diesel::ConnectionError; use diesel_async::{ @@ -12,24 +18,13 @@ use native_tls::TlsConnector; use postgres_native_tls::MakeTlsConnector; use reqwest::Client; use std::sync::Arc; -use thiserror::Error; use tracing::error; -mod handle_proxy; - /// The DB connection type pub type DbConn<'a> = PooledConnection<'a, AsyncDieselConnectionManager>; /// The DB pool type pub type DbPool = Pool>; -/// Custom error type for server errors -#[derive(Error, Debug)] -pub enum ServerError { - /// Database connection error - #[error("Database connection error: {0}")] - DatabaseConnectionError(String), -} - /// The server struct that holds all the necessary components pub struct Server { /// The database connection pool @@ -38,33 +33,45 @@ pub struct Server { pub relayer_url: String, /// The admin key for the relayer pub relayer_admin_key: String, + /// The encryption key for storing API secrets + pub encryption_key: Vec, /// The HTTP client pub client: Client, } impl Server { /// Create a new server instance - pub async fn new(args: Cli) -> Result { + pub async fn new(args: Cli) -> Result { + // Setup the DB connection pool let db_pool = create_db_pool(&args.database_url).await?; + + // Parse the decryption key as a base64 encoded string + let encryption_key = general_purpose::STANDARD + .decode(&args.encryption_key) + .map_err(AuthServerError::encryption)?; + Ok(Self { db_pool: Arc::new(db_pool), relayer_url: args.relayer_url, relayer_admin_key: args.relayer_admin_key, + encryption_key, client: Client::new(), }) } + + /// Get a db connection from the pool + pub async fn get_db_conn(&self) -> Result { + self.db_pool.get().await.map_err(AuthServerError::db) + } } /// Create a database pool -pub async fn create_db_pool(db_url: &str) -> Result { +pub async fn create_db_pool(db_url: &str) -> Result { let mut conf = ManagerConfig::default(); conf.custom_setup = Box::new(move |url| Box::pin(establish_connection(url))); let manager = AsyncDieselConnectionManager::new_with_config(db_url, conf); - Pool::builder() - .build(manager) - .await - .map_err(|e| ServerError::DatabaseConnectionError(e.to_string())) + Pool::builder().build(manager).await.map_err(AuthServerError::db) } /// Establish a connection to the database diff --git a/auth/auth-server/src/server/queries.rs b/auth/auth-server/src/server/queries.rs new file mode 100644 index 0000000..92bc08d --- /dev/null +++ b/auth/auth-server/src/server/queries.rs @@ -0,0 +1,33 @@ +//! DB queries for the auth server + +use diesel::{ExpressionMethods, QueryDsl}; +use diesel_async::RunQueryDsl; +use uuid::Uuid; + +use crate::{models::NewApiKey, schema::api_keys}; + +use super::{AuthServerError, Server}; + +impl Server { + /// Add a new API key to the database + pub async fn add_key_query(&self, new_key: NewApiKey) -> Result<(), AuthServerError> { + let mut conn = self.get_db_conn().await?; + diesel::insert_into(api_keys::table) + .values(&new_key) + .execute(&mut conn) + .await + .map_err(AuthServerError::db) + .map(|_| ()) + } + + /// Expire an existing API key + pub async fn expire_key_query(&self, key_id: Uuid) -> Result<(), AuthServerError> { + let mut conn = self.get_db_conn().await?; + diesel::update(api_keys::table.filter(api_keys::id.eq(key_id))) + .set(api_keys::is_active.eq(false)) + .execute(&mut conn) + .await + .map_err(AuthServerError::db) + .map(|_| ()) + } +}