use crate::container::App;
use crate::http::{HttpResponse, Request, Response};
use crate::middleware::{Middleware, Next};
use async_trait::async_trait;
use sha2::{Digest, Sha256};
use std::sync::Arc;
use subtle::ConstantTimeEq;
const BASE62: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
#[derive(Debug, Clone)]
pub struct GeneratedApiKey {
pub raw_key: String,
pub prefix: String,
pub hashed_key: String,
}
#[derive(Debug, Clone)]
pub struct ApiKeyInfo {
pub id: i64,
pub name: String,
pub scopes: Vec<String>,
}
#[async_trait]
pub trait ApiKeyProvider: Send + Sync + 'static {
async fn verify_key(&self, raw_key: &str) -> Result<ApiKeyInfo, ()>;
}
pub fn generate_api_key(environment: &str) -> GeneratedApiKey {
let prefix_str = format!("fe_{environment}_");
let mut rng = rand::thread_rng();
let random: String = (0..43)
.map(|_| {
let idx = rand::Rng::gen_range(&mut rng, 0..62);
BASE62[idx] as char
})
.collect();
let raw_key = format!("{prefix_str}{random}");
let prefix = raw_key[..16].to_string();
let hashed_key = hash_api_key(&raw_key);
GeneratedApiKey {
raw_key,
prefix,
hashed_key,
}
}
pub fn hash_api_key(raw_key: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(raw_key.as_bytes());
format!("{:x}", hasher.finalize())
}
pub fn verify_api_key_hash(raw_key: &str, stored_hash: &str) -> bool {
let incoming_hash = hash_api_key(raw_key);
incoming_hash
.as_bytes()
.ct_eq(stored_hash.as_bytes())
.into()
}
pub struct ApiKeyMiddleware {
required_scopes: Vec<String>,
}
impl ApiKeyMiddleware {
pub fn new() -> Self {
Self {
required_scopes: Vec::new(),
}
}
pub fn scopes(scopes: &[&str]) -> Self {
Self {
required_scopes: scopes.iter().map(|s| s.to_string()).collect(),
}
}
}
impl Default for ApiKeyMiddleware {
fn default() -> Self {
Self::new()
}
}
fn extract_bearer_token(request: &Request) -> Result<&str, HttpResponse> {
let header = request.header("Authorization").ok_or_else(|| {
HttpResponse::json(serde_json::json!({
"error": "API key required",
"hint": "Include Authorization: Bearer <key> header"
}))
.status(401)
})?;
let token = header.strip_prefix("Bearer ").ok_or_else(|| {
HttpResponse::json(serde_json::json!({
"error": "Invalid API key format"
}))
.status(401)
})?;
if token.is_empty() {
return Err(HttpResponse::json(serde_json::json!({
"error": "Invalid API key format"
}))
.status(401));
}
Ok(token)
}
#[async_trait]
impl Middleware for ApiKeyMiddleware {
async fn handle(&self, mut request: Request, next: Next) -> Response {
let raw_key = extract_bearer_token(&request)?;
let provider: Arc<dyn ApiKeyProvider> =
App::make::<dyn ApiKeyProvider>().ok_or_else(|| {
HttpResponse::json(serde_json::json!({
"error": "API key authentication not configured"
}))
.status(500)
})?;
let key_info = provider.verify_key(raw_key).await.map_err(|()| {
HttpResponse::json(serde_json::json!({
"error": "Invalid API key"
}))
.status(401)
})?;
if !self.required_scopes.is_empty() {
let has_wildcard = key_info.scopes.iter().any(|s| s == "*");
if !has_wildcard {
let missing: Vec<&String> = self
.required_scopes
.iter()
.filter(|required| !key_info.scopes.contains(required))
.collect();
if !missing.is_empty() {
return Err(HttpResponse::json(serde_json::json!({
"error": "Insufficient permissions",
"required": self.required_scopes,
"provided": key_info.scopes
}))
.status(403));
}
}
}
request.insert(key_info);
next(request).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_api_key_format() {
let key = generate_api_key("live");
assert!(key.raw_key.starts_with("fe_live_"));
assert_eq!(key.raw_key.len(), 51);
}
#[test]
fn generate_api_key_test_env() {
let key = generate_api_key("test");
assert!(key.raw_key.starts_with("fe_test_"));
assert_eq!(key.raw_key.len(), 51);
}
#[test]
fn generate_api_key_prefix_length() {
let key = generate_api_key("live");
assert_eq!(key.prefix.len(), 16);
assert!(key.prefix.starts_with("fe_live_"));
}
#[test]
fn generate_api_key_hash_is_sha256_hex() {
let key = generate_api_key("live");
assert_eq!(key.hashed_key.len(), 64);
assert!(key.hashed_key.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn generate_api_key_uniqueness() {
let key1 = generate_api_key("live");
let key2 = generate_api_key("live");
assert_ne!(key1.raw_key, key2.raw_key);
assert_ne!(key1.hashed_key, key2.hashed_key);
}
#[test]
fn generate_api_key_base62_chars_only() {
let key = generate_api_key("live");
let random_part = &key.raw_key[8..]; assert!(random_part.chars().all(|c| c.is_ascii_alphanumeric()));
}
#[test]
fn hash_api_key_deterministic() {
let hash1 = hash_api_key("fe_live_abc123");
let hash2 = hash_api_key("fe_live_abc123");
assert_eq!(hash1, hash2);
}
#[test]
fn hash_api_key_different_inputs() {
let hash1 = hash_api_key("fe_live_abc123");
let hash2 = hash_api_key("fe_live_xyz789");
assert_ne!(hash1, hash2);
}
#[test]
fn verify_api_key_hash_correct() {
let key = generate_api_key("live");
assert!(verify_api_key_hash(&key.raw_key, &key.hashed_key));
}
#[test]
fn verify_api_key_hash_wrong_key() {
let key = generate_api_key("live");
assert!(!verify_api_key_hash("wrong_key", &key.hashed_key));
}
#[test]
fn verify_api_key_hash_wrong_hash() {
let key = generate_api_key("live");
assert!(!verify_api_key_hash(
&key.raw_key,
"0000000000000000000000000000000000000000000000000000000000000000"
));
}
#[test]
fn verify_api_key_hash_manual() {
let raw = "fe_test_SomeRandomBase62String";
let hash = hash_api_key(raw);
assert!(verify_api_key_hash(raw, &hash));
}
}