Skip to content

Commit

Permalink
auth: auth-server: Define key management API
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Oct 21, 2024
1 parent dd3a9e9 commit b983831
Show file tree
Hide file tree
Showing 21 changed files with 386 additions and 27 deletions.
52 changes: 52 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ members = [
"funds-manager/funds-manager-api",
"funds-manager/funds-manager-server",
"price-reporter",
"auth-server",
"auth/auth-server", "auth/auth-server-api",
]

[profile.bench]
Expand Down
8 changes: 8 additions & 0 deletions auth/auth-server-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
36 changes: 36 additions & 0 deletions auth/auth-server-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 /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,
}
13 changes: 12 additions & 1 deletion auth-server/Cargo.toml → auth/auth-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
File renamed without changes.
File renamed without changes.
File renamed without changes.
38 changes: 38 additions & 0 deletions auth/auth-server/src/error.rs
Original file line number Diff line number Diff line change
@@ -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<T: ToString>(msg: T) -> Self {

Check failure on line 23 in auth/auth-server/src/error.rs

View workflow job for this annotation

GitHub Actions / clippy

this argument is passed by value, but not consumed in the function body

error: this argument is passed by value, but not consumed in the function body --> auth/auth-server/src/error.rs:23:33 | 23 | pub fn db<T: ToString>(msg: T) -> Self { | ^ help: consider taking a reference instead: `&T` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value = note: requested on the command line with `-D clippy::needless-pass-by-value`
Self::DatabaseConnection(msg.to_string())
}

/// Create a new encryption error
pub fn encryption<T: ToString>(msg: T) -> Self {

Check failure on line 28 in auth/auth-server/src/error.rs

View workflow job for this annotation

GitHub Actions / clippy

this argument is passed by value, but not consumed in the function body

error: this argument is passed by value, but not consumed in the function body --> auth/auth-server/src/error.rs:28:41 | 28 | pub fn encryption<T: ToString>(msg: T) -> Self { | ^ help: consider taking a reference instead: `&T` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
Self::Encryption(msg.to_string())
}

/// Create a new decryption error
pub fn decryption<T: ToString>(msg: T) -> Self {

Check failure on line 33 in auth/auth-server/src/error.rs

View workflow job for this annotation

GitHub Actions / clippy

this argument is passed by value, but not consumed in the function body

error: this argument is passed by value, but not consumed in the function body --> auth/auth-server/src/error.rs:33:41 | 33 | pub fn decryption<T: ToString>(msg: T) -> Self { | ^ help: consider taking a reference instead: `&T` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
Self::Decryption(msg.to_string())
}
}

impl warp::reject::Reject for AuthServerError {}
45 changes: 40 additions & 5 deletions auth-server/src/main.rs → auth/auth-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
// -------
Expand Down Expand Up @@ -82,19 +90,43 @@ 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
let ping = warp::path("ping")
.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<Server>| async move { server.add_key(request).await });

// Expire an API key
let expire_api_key = warp::path(API_KEYS_PATH)
.and(warp::path::param::<Uuid>())
.and(warp::path(DEACTIVATE_API_KEY_PATH))
.and(warp::post())
.and(with_server(server.clone()))
.and_then(|id: Uuid, server: Arc<Server>| async move { server.expire_key(id).await });

// Proxy route
let proxy = warp::path::full()
.and(warp::method())
Expand All @@ -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;
}

Expand All @@ -122,8 +154,11 @@ fn with_server(
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Rejection> {
if let Some(api_error) = err.find::<ApiError>() {
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))
Expand Down
31 changes: 31 additions & 0 deletions auth/auth-server/src/models.rs
Original file line number Diff line number Diff line change
@@ -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 }
}
}
File renamed without changes.
35 changes: 35 additions & 0 deletions auth/auth-server/src/server/handle_key_management.rs
Original file line number Diff line number Diff line change
@@ -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<impl Reply, Rejection> {
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<impl Reply, Rejection> {
self.expire_key_query(key_id)
.await
.map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?;

Ok(empty_json_reply())
}
}
Loading

0 comments on commit b983831

Please sign in to comment.