Skip to main content

objectiveai_cli/filesystem/config/
viewer.rs

1use serde::{Deserialize, Serialize};
2
3/// A generated secret/signature pair for viewer authentication.
4///
5/// The viewer server stores the `secret`. The API client stores the `signature`.
6/// The signature is `sha256=<hex of SHA256(secret)>`. The viewer server validates
7/// by computing SHA256(secret) and comparing against the incoming header value.
8/// Knowing the signature does not reveal the secret (preimage resistance).
9#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
10#[schemars(rename = "filesystem.config.ViewerSecretSignaturePair")]
11pub struct ViewerSecretSignaturePair {
12    /// The secret for the viewer server.
13    pub secret: String,
14    /// The pre-computed signature for the API client (`sha256=<hex>`).
15    pub signature: String,
16}
17
18/// Generates a random secret/signature pair for viewer authentication.
19///
20/// The secret is a random 64-character hex string. The signature is
21/// `sha256=<SHA256(secret)>` — a one-way derivation that cannot be reversed.
22pub fn generate_viewer_secret_signature_pair() -> ViewerSecretSignaturePair {
23    use sha2::{Digest, Sha256};
24
25    let mut bytes = [0u8; 32];
26    rand::fill(&mut bytes);
27    let secret: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
28
29    let hash = Sha256::digest(secret.as_bytes());
30    let signature = format!(
31        "sha256={}",
32        hash.iter()
33            .map(|b| format!("{:02x}", b))
34            .collect::<String>()
35    );
36
37    ViewerSecretSignaturePair { secret, signature }
38}
39
40#[derive(
41    Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema,
42)]
43#[schemars(rename = "filesystem.config.ViewerConfig")]
44pub struct ViewerConfig {
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    #[schemars(extend("omitempty" = true))]
47    pub address: Option<String>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    #[schemars(extend("omitempty" = true))]
50    pub secret: Option<String>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    #[schemars(extend("omitempty" = true))]
53    pub signature: Option<String>,
54}
55
56impl ViewerConfig {
57    pub fn is_empty(&self) -> bool {
58        self.address.is_none()
59            && self.secret.is_none()
60            && self.signature.is_none()
61    }
62
63    pub fn is_none(this: &Option<Self>) -> bool {
64        this.as_ref().is_none_or(|cfg| cfg.is_empty())
65    }
66
67    pub fn get_address(&self) -> Option<&str> {
68        self.address.as_deref()
69    }
70
71    pub fn set_address(&mut self, value: impl Into<String>) {
72        self.address = Some(value.into());
73    }
74
75
76    pub fn get_secret(&self) -> Option<&str> {
77        self.secret.as_deref()
78    }
79
80    pub fn set_secret(&mut self, value: impl Into<String>) {
81        self.secret = Some(value.into());
82    }
83
84    pub fn get_signature(&self) -> Option<&str> {
85        self.signature.as_deref()
86    }
87
88    pub fn set_signature(&mut self, value: impl Into<String>) {
89        self.signature = Some(value.into());
90    }
91
92    pub fn jq(
93        &self,
94        filter: &str,
95    ) -> Result<Vec<serde_json::Value>, super::super::Error> {
96        super::super::run_jq(self, filter)
97    }
98}