zlayer_types/api/secrets.rs
1//! Secrets management API DTOs.
2//!
3//! Wire types for the secrets endpoints. Secret values are never exposed
4//! through the API except via an explicit admin-only `?reveal=true` request —
5//! only metadata is returned for listing and retrieval.
6
7use serde::{Deserialize, Serialize};
8use utoipa::ToSchema;
9
10/// Request to create or update a secret.
11///
12/// `scope` is optional and only honored on the legacy code path — when set
13/// alongside `?environment=`, the request is rejected.
14#[derive(Debug, Deserialize, ToSchema)]
15pub struct CreateSecretRequest {
16 /// The name of the secret.
17 pub name: String,
18 /// The secret value (will be encrypted at rest).
19 pub value: String,
20 /// Optional explicit scope (legacy form). Mutually exclusive with the
21 /// `?environment=` query parameter.
22 #[serde(default)]
23 pub scope: Option<String>,
24 /// Optional per-secret node affinity. `None` (default) = any node may
25 /// host the decryptable form. When set, only matching nodes receive a
26 /// wrap of the DEK material for this row, and the API gate filters
27 /// reads accordingly. Ignored on standalone (non-clustered) daemons.
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub node_affinity: Option<crate::storage::NodeAffinity>,
30}
31
32/// Response containing secret metadata. Never includes the value unless
33/// the caller is on the explicit `?reveal=true` admin path, in which case
34/// `value` is populated.
35#[derive(Debug, Serialize, Deserialize, ToSchema)]
36pub struct SecretMetadataResponse {
37 /// The name/identifier of the secret.
38 pub name: String,
39 /// Unix timestamp when the secret was created.
40 pub created_at: i64,
41 /// Unix timestamp when the secret was last updated.
42 pub updated_at: i64,
43 /// Version number of the secret (incremented on each update).
44 pub version: u32,
45 /// Plaintext value — populated only on `?reveal=true` admin reads.
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub value: Option<String>,
48}
49
50/// Request body for secret rotation.
51#[derive(Debug, Deserialize, ToSchema)]
52pub struct RotateSecretRequest {
53 /// The new secret value (will be encrypted at rest).
54 pub value: String,
55 /// Optional per-secret node affinity update. `None` here means "leave
56 /// existing affinity unchanged"; to clear affinity explicitly, pass an
57 /// empty selector via a separate update endpoint (Phase 2).
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub node_affinity: Option<crate::storage::NodeAffinity>,
60}
61
62/// Response returned by the rotate endpoint.
63#[derive(Debug, Serialize, Deserialize, ToSchema)]
64pub struct RotateSecretResponse {
65 /// The secret name.
66 pub name: String,
67 /// Version prior to rotation. `None` if the secret did not exist (won't
68 /// happen today — rotate rejects missing secrets — but preserved for
69 /// forward compatibility).
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub previous_version: Option<u32>,
72 /// Version after rotation.
73 pub new_version: u32,
74}
75
76/// Response for the batch reveal endpoint — returns every secret in an env as plaintext.
77/// Admin-only for now (Phase 3 will gate this on per-env Read permission instead).
78#[derive(Debug, Serialize, Deserialize, ToSchema)]
79pub struct RevealAllSecretsResponse {
80 /// The environment id the secrets were revealed from.
81 pub environment: String,
82 /// Name → plaintext value map. Includes every secret in the scope.
83 pub secrets: std::collections::HashMap<String, String>,
84}
85
86/// Result body for `POST /api/v1/secrets/bulk-import`.
87#[derive(Debug, Serialize, Deserialize, ToSchema)]
88pub struct BulkImportResponse {
89 /// Number of new secrets created.
90 pub created: usize,
91 /// Number of existing secrets updated.
92 pub updated: usize,
93 /// Per-line errors. Empty when every line parsed and stored cleanly.
94 pub errors: Vec<String>,
95}
96
97/// Query for create / list / get / delete endpoints.
98#[derive(Debug, Default, Deserialize)]
99pub struct SecretsScopeQuery {
100 /// Environment id whose namespace to operate in. Mutually exclusive
101 /// with `scope`.
102 #[serde(default)]
103 pub environment: Option<String>,
104 /// Explicit scope string (legacy). Mutually exclusive with `environment`.
105 #[serde(default)]
106 pub scope: Option<String>,
107}
108
109/// Query for `GET /api/v1/secrets/{name}` — extends the scope query with a
110/// `reveal` flag for admin-only plaintext reads.
111#[derive(Debug, Default, Deserialize)]
112pub struct GetSecretQuery {
113 /// Environment id whose namespace to read from. Mutually exclusive with `scope`.
114 #[serde(default)]
115 pub environment: Option<String>,
116 /// Explicit scope string (legacy). Mutually exclusive with `environment`.
117 #[serde(default)]
118 pub scope: Option<String>,
119 /// When true, include the plaintext value. Admin only.
120 #[serde(default)]
121 pub reveal: bool,
122}
123
124/// Query for `POST /api/v1/secrets/bulk-import` — `environment` is required.
125#[derive(Debug, Deserialize)]
126pub struct BulkImportQuery {
127 /// Environment id to import the secrets into.
128 pub environment: String,
129}