Skip to main content

ironflow_api/routes/secrets/
list.rs

1//! `GET /api/v1/secrets` -- List secrets (admin only, never exposes values).
2
3use axum::extract::{Query, State};
4use axum::response::IntoResponse;
5use serde::Deserialize;
6
7use ironflow_auth::extractor::Authenticated;
8
9use crate::entities::SecretResponse;
10use crate::error::ApiError;
11use crate::response::ok_paged;
12use crate::state::AppState;
13
14/// Query parameters for listing secrets.
15#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))]
16#[derive(Debug, Deserialize)]
17pub struct ListSecretsQuery {
18    /// Filter by key prefix (e.g. `workflows/inbox/`).
19    pub prefix: Option<String>,
20    /// Page number (1-based, default 1).
21    pub page: Option<u32>,
22    /// Items per page (default 50, max 100).
23    pub per_page: Option<u32>,
24}
25
26/// List secret metadata. Admin only.
27///
28/// Returns key, id, and timestamps. Never returns encrypted or decrypted values.
29#[cfg_attr(
30    feature = "openapi",
31    utoipa::path(
32        get,
33        path = "/api/v1/secrets",
34        tags = ["secrets"],
35        params(ListSecretsQuery),
36        responses(
37            (status = 200, description = "Secrets listed", body = Vec<SecretResponse>),
38            (status = 401, description = "Unauthorized"),
39            (status = 403, description = "Forbidden")
40        ),
41        security(("Bearer" = []))
42    )
43)]
44pub async fn list_secrets(
45    auth: Authenticated,
46    State(state): State<AppState>,
47    Query(query): Query<ListSecretsQuery>,
48) -> Result<impl IntoResponse, ApiError> {
49    if !auth.is_admin() {
50        return Err(ApiError::Forbidden);
51    }
52
53    let prefix = query.prefix.as_deref().unwrap_or("");
54    let page = query.page.unwrap_or(1).max(1);
55    let per_page = query.per_page.unwrap_or(50).clamp(1, 100);
56
57    let result = state.store.list_secrets(prefix, page, per_page).await?;
58
59    let data: Vec<SecretResponse> = result.items.into_iter().map(SecretResponse::from).collect();
60
61    Ok(ok_paged(data, result.page, result.per_page, result.total))
62}