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}