Skip to main content

smbcloud_model/
app_auth.rs

1use crate::ar_date_format;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use tsync::tsync;
5
6#[derive(Serialize, Deserialize, Debug, Clone)]
7pub struct AuthApp {
8    pub id: String,
9    pub secret: Option<String>,
10    pub name: String,
11    // Serialized as a number by the API, like `Deployment`/`MailApp`. The
12    // create/update payloads take it as a String because it comes from a CLI
13    // arg — the two are intentionally different types.
14    pub project_id: Option<i32>,
15    pub support_email: Option<String>,
16    /// Whether Sign in with Apple is enabled for this auth app.
17    pub apple_oauth_enabled: Option<bool>,
18    /// The Services ID used as the Apple OAuth client identifier.
19    pub apple_oauth_client_id: Option<String>,
20    /// Whether the required Apple OAuth credentials are configured.
21    pub apple_oauth_configured: Option<bool>,
22    /// Whether an Apple OAuth client secret has been stored.
23    pub apple_oauth_secret_present: Option<bool>,
24    #[serde(with = "ar_date_format")]
25    pub created_at: DateTime<Utc>,
26    #[serde(with = "ar_date_format")]
27    pub updated_at: DateTime<Utc>,
28}
29
30#[derive(Serialize, Deserialize, Debug, Clone)]
31pub struct AuthAppCreate {
32    pub name: String,
33    pub project_id: String,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub support_email: Option<String>,
36}
37
38#[derive(Serialize, Deserialize, Debug, Clone, Default)]
39pub struct AuthAppUpdate {
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub name: Option<String>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub support_email: Option<String>,
44}
45
46impl AuthAppUpdate {
47    pub fn is_empty(&self) -> bool {
48        self.name.is_none() && self.support_email.is_none()
49    }
50}
51
52/// A public OAuth client registered against an AuthApp for the hosted
53/// Authorization Code + PKCE flow.
54///
55/// These are public clients: no client secret is issued, so security rests on
56/// PKCE plus the `redirect_uris` allowlist. `redirect_uris` is a newline-
57/// separated list. `client_id` is the public identifier (prefixed `auc_`) sent
58/// in the `/authorize` request.
59#[derive(Serialize, Deserialize, Debug, Clone)]
60#[tsync]
61pub struct AuthAppClient {
62    pub id: i64,
63    pub client_id: String,
64    pub name: String,
65    pub redirect_uris: String,
66    pub confidential: bool,
67    pub auth_app_id: String,
68    #[serde(with = "ar_date_format")]
69    pub created_at: DateTime<Utc>,
70    #[serde(with = "ar_date_format")]
71    pub updated_at: DateTime<Utc>,
72}
73
74/// Request body for registering a new public OAuth client on an AuthApp.
75/// `redirect_uris` is a newline-separated allowlist; each entry must be https,
76/// a loopback http URL, or a custom scheme (native apps).
77#[derive(Serialize, Deserialize, Debug, Clone)]
78#[tsync]
79pub struct AuthAppClientCreate {
80    pub name: String,
81    pub redirect_uris: String,
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use serde_json::json;
88
89    #[test]
90    fn auth_app_apple_settings_are_optional_and_typed() -> Result<(), serde_json::Error> {
91        let mut payload = json!({
92            "id": "app-id",
93            "name": "Example",
94            "created_at": "2026-09-30T08:00:00.000+00:00",
95            "updated_at": "2026-09-30T08:00:00.000+00:00"
96        });
97        let legacy: AuthApp = serde_json::from_value(payload.clone())?;
98        assert_eq!(legacy.apple_oauth_enabled, None);
99        assert_eq!(legacy.apple_oauth_client_id, None);
100        assert_eq!(legacy.apple_oauth_configured, None);
101        assert_eq!(legacy.apple_oauth_secret_present, None);
102
103        payload["apple_oauth_enabled"] = json!(true);
104        payload["apple_oauth_client_id"] = json!("com.example.web");
105        payload["apple_oauth_configured"] = json!(true);
106        payload["apple_oauth_secret_present"] = json!(true);
107        let configured: AuthApp = serde_json::from_value(payload)?;
108        assert_eq!(configured.apple_oauth_enabled, Some(true));
109        assert_eq!(
110            configured.apple_oauth_client_id.as_deref(),
111            Some("com.example.web")
112        );
113        assert_eq!(configured.apple_oauth_configured, Some(true));
114        assert_eq!(configured.apple_oauth_secret_present, Some(true));
115
116        let serialized = serde_json::to_value(&configured)?;
117        assert_eq!(serialized["apple_oauth_enabled"], json!(true));
118        assert_eq!(
119            serialized["apple_oauth_client_id"],
120            json!("com.example.web")
121        );
122        assert_eq!(serialized["apple_oauth_configured"], json!(true));
123        assert_eq!(serialized["apple_oauth_secret_present"], json!(true));
124        Ok(())
125    }
126    #[test]
127    fn test_auth_app_create() {
128        let auth_app_create = AuthAppCreate {
129            name: "test".to_owned(),
130            project_id: "1".to_owned(),
131            support_email: None,
132        };
133        let json = json!({
134            "name": "test",
135            "project_id": "1",
136        });
137        assert_eq!(serde_json::to_value(auth_app_create).unwrap(), json);
138    }
139}