1use std::sync::Arc;
7
8use axum::extract::State;
9use axum::http::StatusCode;
10use axum::response::IntoResponse;
11use axum::routing::{get, post};
12use axum::{Json, Router};
13use serde::{Deserialize, Serialize};
14
15use crate::error::AppError;
16use crate::state::AppState;
17use zvault_core::token::CreateTokenParams;
18
19pub fn router() -> Router<Arc<AppState>> {
21 Router::new()
22 .route("/init", post(init))
23 .route("/unseal", post(unseal))
24 .route("/seal", post(seal))
25 .route("/seal-status", get(seal_status))
26 .route("/health", get(health))
27 .route("/audit-log", get(audit_log))
28 .route("/license", get(license_status))
29 .route("/backup", get(backup))
30 .route("/restore", post(restore))
31}
32
33#[derive(Debug, Deserialize)]
37pub struct InitRequest {
38 pub shares: u8,
40 pub threshold: u8,
42}
43
44#[derive(Debug, Serialize)]
46pub struct InitResponse {
47 pub unseal_shares: Vec<String>,
49 pub root_token: String,
51}
52
53#[derive(Debug, Deserialize)]
55pub struct UnsealRequest {
56 pub share: String,
58}
59
60#[derive(Debug, Serialize)]
62pub struct UnsealResponse {
63 pub sealed: bool,
65 pub threshold: u8,
67 pub progress: u8,
69}
70
71#[derive(Debug, Serialize)]
73pub struct SealStatusResponse {
74 pub initialized: bool,
76 pub sealed: bool,
78 pub threshold: u8,
80 pub shares: u8,
82 pub progress: u8,
84}
85
86async fn init(
93 State(state): State<Arc<AppState>>,
94 Json(body): Json<InitRequest>,
95) -> Result<(StatusCode, Json<InitResponse>), AppError> {
96 let result = state.seal_manager.init(body.shares, body.threshold).await?;
97
98 for share in &result.unseal_shares {
102 let progress = state.seal_manager.submit_unseal_share(share).await?;
103 if progress.is_none() {
104 break; }
106 }
107
108 state
110 .token_store
111 .create_with_token(
112 &result.root_token,
113 CreateTokenParams {
114 policies: vec!["root".to_owned()],
115 ttl: None,
116 max_ttl: None,
117 renewable: false,
118 parent_hash: None,
119 metadata: std::collections::HashMap::new(),
120 display_name: "root".to_owned(),
121 },
122 )
123 .await
124 .map_err(|e| AppError::Internal(format!("failed to store root token: {e}")))?;
125
126 state.seal_manager.seal().await?;
128
129 Ok((
130 StatusCode::OK,
131 Json(InitResponse {
132 unseal_shares: result.unseal_shares,
133 root_token: result.root_token,
134 }),
135 ))
136}
137
138async fn unseal(
143 State(state): State<Arc<AppState>>,
144 Json(body): Json<UnsealRequest>,
145) -> Result<Json<UnsealResponse>, AppError> {
146 let progress = state.seal_manager.submit_unseal_share(&body.share).await?;
147
148 match progress {
149 Some(p) => Ok(Json(UnsealResponse {
150 sealed: true,
151 threshold: p.threshold,
152 progress: p.submitted,
153 })),
154 None => Ok(Json(UnsealResponse {
155 sealed: false,
156 threshold: 0,
157 progress: 0,
158 })),
159 }
160}
161
162async fn seal(State(state): State<Arc<AppState>>) -> Result<StatusCode, AppError> {
164 state.seal_manager.seal().await?;
165 Ok(StatusCode::NO_CONTENT)
166}
167
168async fn seal_status(
170 State(state): State<Arc<AppState>>,
171) -> Result<Json<SealStatusResponse>, AppError> {
172 let status = state.seal_manager.status().await?;
173 Ok(Json(SealStatusResponse {
174 initialized: status.initialized,
175 sealed: status.sealed,
176 threshold: status.threshold,
177 shares: status.shares,
178 progress: status.progress,
179 }))
180}
181
182async fn health(State(state): State<Arc<AppState>>) -> impl IntoResponse {
186 let status = state.seal_manager.status().await;
187
188 match status {
189 Ok(s) if !s.initialized => {
190 let body = SealStatusResponse {
191 initialized: false,
192 sealed: true,
193 threshold: 0,
194 shares: 0,
195 progress: 0,
196 };
197 (StatusCode::NOT_IMPLEMENTED, Json(body))
198 }
199 Ok(s) if s.sealed => {
200 let body = SealStatusResponse {
201 initialized: s.initialized,
202 sealed: true,
203 threshold: s.threshold,
204 shares: s.shares,
205 progress: s.progress,
206 };
207 (StatusCode::SERVICE_UNAVAILABLE, Json(body))
208 }
209 Ok(s) => {
210 let body = SealStatusResponse {
211 initialized: s.initialized,
212 sealed: s.sealed,
213 threshold: s.threshold,
214 shares: s.shares,
215 progress: s.progress,
216 };
217 (StatusCode::OK, Json(body))
218 }
219 Err(_) => {
220 let body = SealStatusResponse {
221 initialized: false,
222 sealed: true,
223 threshold: 0,
224 shares: 0,
225 progress: 0,
226 };
227 (StatusCode::INTERNAL_SERVER_ERROR, Json(body))
228 }
229 }
230}
231
232#[derive(Debug, Deserialize)]
236pub struct AuditLogQuery {
237 pub limit: Option<usize>,
239}
240
241#[derive(Debug, Serialize)]
243pub struct AuditLogResponse {
244 pub entries: Vec<serde_json::Value>,
246 pub count: usize,
248}
249
250async fn audit_log(
257 State(state): State<Arc<AppState>>,
258 axum::extract::Query(query): axum::extract::Query<AuditLogQuery>,
259) -> Result<Json<AuditLogResponse>, AppError> {
260 let limit = query.limit.unwrap_or(100).min(1000);
261
262 let Some(ref audit_path) = state.audit_file_path else {
263 return Ok(Json(AuditLogResponse {
264 entries: Vec::new(),
265 count: 0,
266 }));
267 };
268
269 let content = match tokio::fs::read_to_string(audit_path).await {
271 Ok(c) => c,
272 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
273 return Ok(Json(AuditLogResponse {
274 entries: Vec::new(),
275 count: 0,
276 }));
277 }
278 Err(e) => {
279 return Err(AppError::Internal(format!(
280 "failed to read audit log: {e}"
281 )));
282 }
283 };
284
285 let mut entries: Vec<serde_json::Value> = content
287 .lines()
288 .filter(|line| !line.trim().is_empty())
289 .filter_map(|line| serde_json::from_str(line).ok())
290 .collect();
291
292 entries.reverse();
294 entries.truncate(limit);
295
296 let count = entries.len();
297 Ok(Json(AuditLogResponse { entries, count }))
298}
299
300#[derive(Debug, Serialize)]
304pub struct LicenseResponse {
305 pub tier: String,
307 pub licensed: bool,
309 pub license_id: Option<String>,
311 pub email: Option<String>,
313 pub expires_at: Option<String>,
315}
316
317async fn license_status(
321 State(_state): State<Arc<AppState>>,
322) -> Json<LicenseResponse> {
323 Json(LicenseResponse {
328 tier: "community".to_owned(),
329 licensed: false,
330 license_id: None,
331 email: None,
332 expires_at: None,
333 })
334}
335
336#[derive(Debug, Serialize)]
344pub struct BackupResponse {
345 pub snapshot: String,
347 pub entry_count: usize,
349 pub created_at: String,
351 pub version: String,
353}
354
355#[derive(Debug, Serialize, Deserialize)]
357struct BackupEntry {
358 key: String,
359 value: String,
361}
362
363async fn backup(
369 State(state): State<Arc<AppState>>,
370) -> Result<Json<BackupResponse>, AppError> {
371 let keys = state.barrier.list("").await?;
373
374 let mut entries = Vec::with_capacity(keys.len());
375 for key in &keys {
376 if let Ok(Some(data)) = state.barrier.get_raw(key).await {
377 entries.push(BackupEntry {
378 key: key.clone(),
379 value: base64::Engine::encode(
380 &base64::engine::general_purpose::STANDARD,
381 &data,
382 ),
383 });
384 }
385 }
386
387 let entry_count = entries.len();
388 let snapshot_json = serde_json::to_vec(&entries)
389 .map_err(|e| AppError::Internal(format!("backup serialization failed: {e}")))?;
390
391 let snapshot = base64::Engine::encode(
392 &base64::engine::general_purpose::STANDARD,
393 &snapshot_json,
394 );
395
396 let created_at = chrono::Utc::now().to_rfc3339();
397
398 Ok(Json(BackupResponse {
399 snapshot,
400 entry_count,
401 created_at,
402 version: env!("CARGO_PKG_VERSION").to_owned(),
403 }))
404}
405
406#[derive(Debug, Deserialize)]
408pub struct RestoreRequest {
409 pub snapshot: String,
411}
412
413#[derive(Debug, Serialize)]
415pub struct RestoreResponse {
416 pub entry_count: usize,
418 pub success: bool,
420}
421
422async fn restore(
427 State(state): State<Arc<AppState>>,
428 Json(body): Json<RestoreRequest>,
429) -> Result<Json<RestoreResponse>, AppError> {
430 let snapshot_bytes = base64::Engine::decode(
431 &base64::engine::general_purpose::STANDARD,
432 &body.snapshot,
433 )
434 .map_err(|_| AppError::BadRequest("invalid base64 snapshot".to_owned()))?;
435
436 let entries: Vec<BackupEntry> = serde_json::from_slice(&snapshot_bytes)
437 .map_err(|e| AppError::BadRequest(format!("invalid snapshot format: {e}")))?;
438
439 let entry_count = entries.len();
440
441 for entry in &entries {
442 let value = base64::Engine::decode(
443 &base64::engine::general_purpose::STANDARD,
444 &entry.value,
445 )
446 .map_err(|_| {
447 AppError::BadRequest(format!("invalid base64 value for key: {}", entry.key))
448 })?;
449
450 state.barrier.put_raw(&entry.key, &value).await?;
451 }
452
453 Ok(Json(RestoreResponse {
454 entry_count,
455 success: true,
456 }))
457}