Skip to content

Commit 9c61ec6

Browse files
committed
Added admin endpoints for admin delete of pins
1 parent d228d6a commit 9c61ec6

9 files changed

Lines changed: 718 additions & 3 deletions

File tree

.env.example

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,36 @@ JWT_SECRET=your-secret-key-here-change-in-production
8080
# OAUTH_ISSUER=https://your-oauth-provider.com
8181
# OAUTH_AUDIENCE=fula-storage
8282

83+
# ============================================
84+
# Admin API (Optional)
85+
# ============================================
86+
#
87+
# The Admin API provides endpoints for managing user data, unpinning CIDs,
88+
# and garbage collection. It uses a SEPARATE JWT secret from user authentication.
89+
#
90+
# WARNING: Admin API grants powerful capabilities. Keep the secret secure!
91+
#
92+
# To enable:
93+
# 1. Set FULA_ADMIN_API=true
94+
# 2. Set ADMIN_JWT_SECRET to a strong, random string (different from JWT_SECRET)
95+
#
96+
# Admin tokens must include:
97+
# - "scope": "admin" (or "*") in the JWT claims
98+
# - Valid "exp" (expiration) claim
99+
#
100+
# Endpoints (when enabled):
101+
# - GET /admin/users/{user_id}/buckets - List user's buckets
102+
# - DELETE /admin/users/{user_id} - Delete all user data
103+
# - DELETE /admin/pins/{cid} - Unpin a specific CID
104+
# - POST /admin/gc - Trigger garbage collection scan
105+
#
106+
# Enable admin API (default: false)
107+
FULA_ADMIN_API=false
108+
109+
# Admin JWT secret (MUST be different from JWT_SECRET)
110+
# Generate with: openssl rand -base64 32
111+
# ADMIN_JWT_SECRET=your-admin-secret-here-change-in-production
112+
83113
# ============================================
84114
# Rate Limiting
85115
# ============================================

crates/fula-cli/src/auth.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,63 @@ pub fn extract_token_from_header(auth_header: &str, headers: &HeaderMap) -> Resu
340340
))
341341
}
342342

343+
// ═══════════════════════════════════════════════════════════════════════════════
344+
// ADMIN AUTHENTICATION
345+
// ═══════════════════════════════════════════════════════════════════════════════
346+
347+
/// Admin JWT claims structure
348+
#[derive(Debug, Serialize, Deserialize)]
349+
pub struct AdminClaims {
350+
/// Subject (admin user ID)
351+
pub sub: String,
352+
/// Expiration time (required for admin tokens)
353+
pub exp: Option<i64>,
354+
/// Issued at
355+
pub iat: Option<i64>,
356+
/// Scope (must include "admin" or "*")
357+
#[serde(default)]
358+
pub scope: String,
359+
}
360+
361+
impl AdminClaims {
362+
/// Check if this token has admin privileges
363+
pub fn is_valid_admin(&self) -> bool {
364+
self.scope
365+
.split_whitespace()
366+
.any(|s| s == "admin" || s == "*")
367+
}
368+
}
369+
370+
/// Validate an admin JWT token and extract claims
371+
pub fn validate_admin_token(token: &str, secret: &str) -> Result<AdminClaims, ApiError> {
372+
let key = DecodingKey::from_secret(secret.as_bytes());
373+
let mut validation = Validation::new(Algorithm::HS256);
374+
validation.validate_exp = true; // Admin tokens MUST have valid expiration
375+
validation.leeway = 60; // 1 minute leeway for clock skew
376+
377+
let claims = decode::<AdminClaims>(token, &key, &validation)
378+
.map(|data| data.claims)
379+
.map_err(|e| {
380+
tracing::debug!("Admin token validation failed: {}", e);
381+
ApiError::s3(S3ErrorCode::InvalidToken, "Invalid or expired admin token")
382+
})?;
383+
384+
// Verify admin scope
385+
if !claims.is_valid_admin() {
386+
tracing::warn!(
387+
admin_id = %claims.sub,
388+
scope = %claims.scope,
389+
"Admin token missing required 'admin' scope"
390+
);
391+
return Err(ApiError::s3(
392+
S3ErrorCode::AccessDenied,
393+
"Token does not have admin privileges",
394+
));
395+
}
396+
397+
Ok(claims)
398+
}
399+
343400
/// Hash a user ID for storage (privacy)
344401
pub fn hash_user_id(user_id: &str) -> String {
345402
use fula_crypto::hashing::hash;

crates/fula-cli/src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ pub struct GatewayConfig {
3939
pub registry_cid_path: Option<String>,
4040
/// Storage API URL for balance/quota checking before uploads
4141
pub storage_api_url: Option<String>,
42+
/// Admin JWT secret for admin API authentication (separate from user JWT)
43+
pub admin_jwt_secret: Option<String>,
44+
/// Enable admin API endpoints
45+
pub admin_api_enabled: bool,
4246
}
4347

4448
impl Default for GatewayConfig {
@@ -61,6 +65,8 @@ impl Default for GatewayConfig {
6165
cors_origins: vec!["*".to_string()],
6266
registry_cid_path: Some("/var/lib/fula-gateway/registry.cid".to_string()),
6367
storage_api_url: None,
68+
admin_jwt_secret: None,
69+
admin_api_enabled: false,
6470
}
6571
}
6672
}

0 commit comments

Comments
 (0)