Skip to main content

unifly_api/
auth.rs

1use std::sync::Arc;
2
3use reqwest::cookie::Jar;
4use secrecy::SecretString;
5
6/// Which authentication strategy to use for a particular API call.
7///
8/// Marker enum (no data) -- the actual credentials live in [`Credentials`].
9/// Useful for branching on auth flow without carrying secret material.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum AuthStrategy {
12    /// Cookie-based session (session API, WebSocket).
13    Session,
14    /// Local API key header (Integration API).
15    ApiKey,
16    /// Cloud API key (Site Manager, Cloud Connector).
17    CloudApiKey,
18}
19
20/// Credentials for authenticating with a UniFi controller.
21///
22/// Each variant carries the secret material needed for its auth flow.
23#[derive(Debug, Clone)]
24pub enum Credentials {
25    /// Cookie-based session auth. The jar holds the session cookie
26    /// after a successful login; pass it into the `reqwest::Client` builder.
27    Session { cookie_jar: Arc<Jar> },
28
29    /// Local API key for the Integration API.
30    /// Generated at: Network > Settings > Control Plane > Integrations.
31    ApiKey { key: SecretString },
32
33    /// Cloud API key for Site Manager + Cloud Connector.
34    /// Generated at: <https://unifi.ui.com> > Settings > API Keys.
35    Cloud { key: SecretString, host_id: String },
36}
37
38/// The platform type of the UniFi controller.
39///
40/// Determines URL prefixes, login paths, and which API surfaces are available.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ControllerPlatform {
43    /// UniFi OS device (UDM, UCG, etc.) -- port 443, `/proxy/network/` prefix.
44    UnifiOs,
45    /// Standalone Network Application (Java) -- port 8443, no prefix.
46    ClassicController,
47    /// Cloud-hosted via Site Manager / Cloud Connector (api.ui.com).
48    Cloud,
49}
50
51impl ControllerPlatform {
52    /// The path prefix for session API endpoints.
53    ///
54    /// Returns `None` for [`Cloud`](Self::Cloud) because the session API
55    /// is not available via the cloud connector.
56    pub fn session_prefix(&self) -> Option<&'static str> {
57        match self {
58            Self::UnifiOs => Some("/proxy/network"),
59            Self::ClassicController => Some(""),
60            Self::Cloud => None,
61        }
62    }
63
64    /// The path prefix for the Integration API.
65    ///
66    /// On UniFi OS devices: `/proxy/network/integration`
67    /// On standalone controllers: `/integration`
68    /// On cloud connector: `/proxy/network/integration`
69    pub fn integration_prefix(&self) -> &'static str {
70        match self {
71            Self::UnifiOs | Self::Cloud => "/proxy/network/integration",
72            Self::ClassicController => "/integration",
73        }
74    }
75
76    /// The login endpoint path.
77    ///
78    /// Returns `None` for [`Cloud`](Self::Cloud) because cloud uses
79    /// API key auth -- no session login needed.
80    pub fn login_path(&self) -> Option<&'static str> {
81        match self {
82            Self::UnifiOs => Some("/api/auth/login"),
83            Self::ClassicController => Some("/api/login"),
84            Self::Cloud => None,
85        }
86    }
87
88    /// The logout endpoint path.
89    ///
90    /// Returns `None` for [`Cloud`](Self::Cloud).
91    pub fn logout_path(&self) -> Option<&'static str> {
92        match self {
93            Self::UnifiOs => Some("/api/auth/logout"),
94            Self::ClassicController => Some("/api/logout"),
95            Self::Cloud => None,
96        }
97    }
98
99    /// The WebSocket path template. `{site}` must be replaced by the caller.
100    ///
101    /// Returns `None` for [`Cloud`](Self::Cloud) because WebSocket
102    /// connections are not available via the cloud connector.
103    pub fn websocket_path(&self) -> Option<&'static str> {
104        match self {
105            Self::UnifiOs => Some("/proxy/network/wss/s/{site}/events"),
106            Self::ClassicController => Some("/wss/s/{site}/events"),
107            Self::Cloud => None,
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::ControllerPlatform;
115
116    #[test]
117    fn cloud_uses_unifi_os_integration_prefix() {
118        assert_eq!(
119            ControllerPlatform::Cloud.integration_prefix(),
120            "/proxy/network/integration"
121        );
122    }
123}