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 (legacy 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 legacy API endpoints.
53    ///
54    /// Returns `None` for [`Cloud`](Self::Cloud) because the legacy API
55    /// is not available via the cloud connector.
56    pub fn legacy_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 / cloud: `/integration`
68    pub fn integration_prefix(&self) -> &'static str {
69        match self {
70            Self::UnifiOs => "/proxy/network/integration",
71            Self::ClassicController | Self::Cloud => "/integration",
72        }
73    }
74
75    /// The login endpoint path.
76    ///
77    /// Returns `None` for [`Cloud`](Self::Cloud) because cloud uses
78    /// API key auth -- no session login needed.
79    pub fn login_path(&self) -> Option<&'static str> {
80        match self {
81            Self::UnifiOs => Some("/api/auth/login"),
82            Self::ClassicController => Some("/api/login"),
83            Self::Cloud => None,
84        }
85    }
86
87    /// The logout endpoint path.
88    ///
89    /// Returns `None` for [`Cloud`](Self::Cloud).
90    pub fn logout_path(&self) -> Option<&'static str> {
91        match self {
92            Self::UnifiOs => Some("/api/auth/logout"),
93            Self::ClassicController => Some("/api/logout"),
94            Self::Cloud => None,
95        }
96    }
97
98    /// The WebSocket path template. `{site}` must be replaced by the caller.
99    ///
100    /// Returns `None` for [`Cloud`](Self::Cloud) because WebSocket
101    /// connections are not available via the cloud connector.
102    pub fn websocket_path(&self) -> Option<&'static str> {
103        match self {
104            Self::UnifiOs => Some("/proxy/network/wss/s/{site}/events"),
105            Self::ClassicController => Some("/wss/s/{site}/events"),
106            Self::Cloud => None,
107        }
108    }
109}