ironflow_api/entities/
secret.rs1use chrono::{DateTime, Utc};
4use ironflow_store::entities::{
5 DEFAULT_ROTATION_BATCH_SIZE, KeyVersionStatus, RotationBatch, Secret, SecretMetadata,
6};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9use validator::Validate;
10
11#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
13#[derive(Debug, Serialize)]
14pub struct SecretResponse {
15 pub id: Uuid,
17 pub key: String,
19 pub created_at: DateTime<Utc>,
21 pub updated_at: DateTime<Utc>,
23}
24
25impl From<SecretMetadata> for SecretResponse {
26 fn from(meta: SecretMetadata) -> Self {
27 Self {
28 id: meta.id,
29 key: meta.key,
30 created_at: meta.created_at,
31 updated_at: meta.updated_at,
32 }
33 }
34}
35
36impl From<Secret> for SecretResponse {
37 fn from(secret: Secret) -> Self {
38 Self {
39 id: secret.id,
40 key: secret.key,
41 created_at: secret.created_at,
42 updated_at: secret.updated_at,
43 }
44 }
45}
46
47#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
49#[derive(Debug, Deserialize, Validate)]
50pub struct SetSecretRequest {
51 #[validate(length(min = 1, max = 512), custom(function = "validate_secret_key"))]
53 pub key: String,
54 #[validate(length(min = 1, max = 65536))]
56 pub value: String,
57}
58
59#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
64#[derive(Debug, Default, Deserialize, Validate)]
65pub struct RotateSecretsRequest {
66 #[validate(range(min = 1))]
68 pub to_version: Option<i32>,
69 #[validate(range(min = 1))]
71 pub batch_size: Option<u32>,
72 pub after_id: Option<Uuid>,
74}
75
76impl RotateSecretsRequest {
77 pub fn effective_batch_size(&self) -> u32 {
79 self.batch_size.unwrap_or(DEFAULT_ROTATION_BATCH_SIZE)
80 }
81}
82
83#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
85#[derive(Debug, Serialize)]
86pub struct RotateSecretsResponse {
87 pub to_version: i32,
89 pub rotated: u64,
91 pub failed: u64,
93 pub remaining: u64,
95 pub last_id: Option<Uuid>,
98}
99
100impl From<RotationBatch> for RotateSecretsResponse {
101 fn from(batch: RotationBatch) -> Self {
102 Self {
103 to_version: batch.to_version,
104 rotated: batch.rotated,
105 failed: batch.failed,
106 remaining: batch.remaining,
107 last_id: batch.last_id,
108 }
109 }
110}
111
112#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
114#[derive(Debug, Serialize)]
115pub struct KeyVersionsResponse {
116 pub active: i32,
118 pub configured: Vec<i32>,
120 pub in_use: Vec<i32>,
122 pub missing: Vec<i32>,
125 pub retirable: Vec<i32>,
127}
128
129impl From<KeyVersionStatus> for KeyVersionsResponse {
130 fn from(status: KeyVersionStatus) -> Self {
131 Self {
132 active: status.active,
133 configured: status.configured,
134 in_use: status.in_use,
135 missing: status.missing,
136 retirable: status.retirable,
137 }
138 }
139}
140
141fn validate_secret_key(key: &str) -> Result<(), validator::ValidationError> {
145 if !key
146 .chars()
147 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '/' | '-' | '_' | '.'))
148 {
149 let mut err = validator::ValidationError::new("invalid_characters");
150 err.message =
151 Some("key must only contain alphanumeric characters, '/', '-', '_', '.'".into());
152 return Err(err);
153 }
154 if key.starts_with('/') || key.ends_with('/') || key.contains("//") {
155 let mut err = validator::ValidationError::new("invalid_format");
156 err.message = Some("key must not start/end with '/' or contain '//'".into());
157 return Err(err);
158 }
159 Ok(())
160}