Skip to main content

ironflow_api/entities/
secret.rs

1//! Secret request and response DTOs.
2
3use chrono::{DateTime, Utc};
4use ironflow_store::entities::{Secret, SecretMetadata};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7use validator::Validate;
8
9/// Response DTO for a secret (never exposes the value).
10#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
11#[derive(Debug, Serialize)]
12pub struct SecretResponse {
13    /// Secret ID.
14    pub id: Uuid,
15    /// Secret key.
16    pub key: String,
17    /// Creation timestamp.
18    pub created_at: DateTime<Utc>,
19    /// Last update timestamp.
20    pub updated_at: DateTime<Utc>,
21}
22
23impl From<SecretMetadata> for SecretResponse {
24    fn from(meta: SecretMetadata) -> Self {
25        Self {
26            id: meta.id,
27            key: meta.key,
28            created_at: meta.created_at,
29            updated_at: meta.updated_at,
30        }
31    }
32}
33
34impl From<Secret> for SecretResponse {
35    fn from(secret: Secret) -> Self {
36        Self {
37            id: secret.id,
38            key: secret.key,
39            created_at: secret.created_at,
40            updated_at: secret.updated_at,
41        }
42    }
43}
44
45/// Request body for creating or updating a secret.
46#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
47#[derive(Debug, Deserialize, Validate)]
48pub struct SetSecretRequest {
49    /// Secret key (namespaced, e.g. `workflows/inbox/gmail_token`).
50    #[validate(length(min = 1, max = 512), custom(function = "validate_secret_key"))]
51    pub key: String,
52    /// Secret value (plaintext, will be encrypted at rest).
53    #[validate(length(min = 1, max = 65536))]
54    pub value: String,
55}
56
57/// Validate that a secret key contains only safe characters.
58///
59/// Allowed: alphanumeric, `/`, `-`, `_`, `.`
60fn validate_secret_key(key: &str) -> Result<(), validator::ValidationError> {
61    if !key
62        .chars()
63        .all(|c| c.is_ascii_alphanumeric() || matches!(c, '/' | '-' | '_' | '.'))
64    {
65        let mut err = validator::ValidationError::new("invalid_characters");
66        err.message =
67            Some("key must only contain alphanumeric characters, '/', '-', '_', '.'".into());
68        return Err(err);
69    }
70    if key.starts_with('/') || key.ends_with('/') || key.contains("//") {
71        let mut err = validator::ValidationError::new("invalid_format");
72        err.message = Some("key must not start/end with '/' or contain '//'".into());
73        return Err(err);
74    }
75    Ok(())
76}