Skip to main content

flowsurface_data/config/
auth.rs

1use exchange::proxy::{Proxy, ProxyAuth};
2
3// ── Proxy auth (keychain) ───────────────────────────────────────────────
4
5const PROXY_KEYCHAIN_SERVICE: &str = "flowsurface.proxy";
6
7fn proxy_entry_for(proxy: &Proxy) -> Result<keyring::Entry, keyring::Error> {
8    let key = proxy.to_url_string_no_auth();
9    keyring::Entry::new(PROXY_KEYCHAIN_SERVICE, &key)
10}
11
12/// Load proxy credentials from the system keychain.
13///
14/// Returns `None` if no credentials are stored, the keychain is unavailable,
15/// or the stored value is not valid JSON.
16pub fn load_proxy_auth(proxy: &Proxy) -> Option<ProxyAuth> {
17    let key = proxy.to_url_string_no_auth();
18
19    let entry = match proxy_entry_for(proxy) {
20        Ok(e) => e,
21        Err(err) => {
22            log::warn!(
23                "Keychain entry init failed for service={PROXY_KEYCHAIN_SERVICE} key={key}: {err}"
24            );
25            return None;
26        }
27    };
28
29    let secret = match entry.get_password() {
30        Ok(s) => s,
31        Err(err) => {
32            log::info!(
33                "No proxy auth in keychain for service={PROXY_KEYCHAIN_SERVICE} key={key}: {err}"
34            );
35            return None;
36        }
37    };
38
39    match serde_json::from_str::<ProxyAuth>(&secret) {
40        Ok(auth) => Some(auth),
41        Err(err) => {
42            log::warn!(
43                "Proxy auth in keychain is invalid JSON for service={PROXY_KEYCHAIN_SERVICE} key={key}: {err}"
44            );
45            None
46        }
47    }
48}
49
50/// Save proxy credentials to the system keychain.
51///
52/// The auth is JSON-serialized before storage.
53/// Does nothing if the proxy has no auth configured.
54pub fn save_proxy_auth(proxy: &Proxy) {
55    let key = proxy.to_url_string_no_auth();
56
57    let Some(auth) = proxy.auth() else {
58        log::info!(
59            "Not saving proxy auth: auth is None (service={PROXY_KEYCHAIN_SERVICE} key={key})"
60        );
61        return;
62    };
63
64    let entry = match proxy_entry_for(proxy) {
65        Ok(e) => e,
66        Err(err) => {
67            log::warn!(
68                "Keychain entry init failed for service={PROXY_KEYCHAIN_SERVICE} key={key}: {err}"
69            );
70            return;
71        }
72    };
73
74    let secret = match serde_json::to_string(auth) {
75        Ok(s) => s,
76        Err(err) => {
77            log::warn!(
78                "Failed to serialize proxy auth for service={PROXY_KEYCHAIN_SERVICE} key={key}: {err}"
79            );
80            return;
81        }
82    };
83
84    match entry.set_password(&secret) {
85        Ok(()) => {
86            log::info!("Stored proxy auth in keychain (service={PROXY_KEYCHAIN_SERVICE} key={key})")
87        }
88        Err(err) => {
89            log::warn!(
90                "Failed to store proxy auth in keychain (service={PROXY_KEYCHAIN_SERVICE} key={key}): {err}"
91            );
92        }
93    }
94}
95
96/// Delete proxy credentials from the system keychain.
97///
98/// Uses the proxy's URL (without auth) to look up the stored credential.
99/// Silently succeeds if no credential exists.
100pub fn delete_proxy_auth(proxy: &Proxy) {
101    let key = proxy.to_url_string_no_auth();
102    let entry = match proxy_entry_for(proxy) {
103        Ok(e) => e,
104        Err(err) => {
105            log::warn!(
106                "Keychain entry init failed for service={PROXY_KEYCHAIN_SERVICE} key={key}: {err}"
107            );
108            return;
109        }
110    };
111    match entry.delete_credential() {
112        Ok(()) => log::info!(
113            "Deleted proxy auth from keychain (service={PROXY_KEYCHAIN_SERVICE} key={key})"
114        ),
115        Err(keyring::Error::NoEntry) => { /* nothing to delete */ }
116        Err(err) => log::warn!(
117            "Failed to delete proxy auth from keychain (service={PROXY_KEYCHAIN_SERVICE} key={key}): {err}"
118        ),
119    }
120}
121
122// ── Server auth (keychain) ──────────────────────────────────────────────
123
124const SERVER_KEYCHAIN_SERVICE: &str = "flowsurface.server";
125
126fn server_entry_for(url: &str) -> Result<keyring::Entry, keyring::Error> {
127    keyring::Entry::new(SERVER_KEYCHAIN_SERVICE, url)
128}
129
130/// Load the server auth token from the system keychain.
131///
132/// Returns `None` if no token is stored, the keychain is unavailable,
133/// or the stored value is not valid UTF-8.
134pub fn load_server_token(url: &str) -> Option<String> {
135    let entry = match server_entry_for(url) {
136        Ok(e) => e,
137        Err(err) => {
138            log::warn!(
139                "Keychain entry init failed for service={SERVER_KEYCHAIN_SERVICE} key={url}: {err}"
140            );
141            return None;
142        }
143    };
144
145    match entry.get_password() {
146        Ok(token) => {
147            log::info!(
148                "Loaded server auth token from keychain (service={SERVER_KEYCHAIN_SERVICE} key={url})"
149            );
150            Some(token)
151        }
152        Err(keyring::Error::NoEntry) => {
153            log::info!(
154                "No server auth token in keychain for service={SERVER_KEYCHAIN_SERVICE} key={url}"
155            );
156            None
157        }
158        Err(err) => {
159            log::warn!(
160                "Failed to read server auth token from keychain (service={SERVER_KEYCHAIN_SERVICE} key={url}): {err}"
161            );
162            None
163        }
164    }
165}
166
167/// Save the server auth token to the system keychain.
168///
169/// The token is stored as a plain UTF-8 string (not JSON-wrapped).
170pub fn save_server_token(url: &str, token: &str) {
171    let entry = match server_entry_for(url) {
172        Ok(e) => e,
173        Err(err) => {
174            log::warn!(
175                "Keychain entry init failed for service={SERVER_KEYCHAIN_SERVICE} key={url}: {err}"
176            );
177            return;
178        }
179    };
180
181    match entry.set_password(token) {
182        Ok(()) => log::info!(
183            "Stored server auth token in keychain (service={SERVER_KEYCHAIN_SERVICE} key={url})"
184        ),
185        Err(err) => log::warn!(
186            "Failed to store server auth token in keychain (service={SERVER_KEYCHAIN_SERVICE} key={url}): {err}"
187        ),
188    }
189}
190
191/// Delete the server auth token from the system keychain.
192///
193/// Silently succeeds if no credential exists.
194pub fn delete_server_token(url: &str) {
195    let entry = match server_entry_for(url) {
196        Ok(e) => e,
197        Err(err) => {
198            log::warn!(
199                "Keychain entry init failed for service={SERVER_KEYCHAIN_SERVICE} key={url}: {err}"
200            );
201            return;
202        }
203    };
204    match entry.delete_credential() {
205        Ok(()) => log::info!(
206            "Deleted server auth token from keychain (service={SERVER_KEYCHAIN_SERVICE} key={url})"
207        ),
208        Err(keyring::Error::NoEntry) => { /* nothing to delete */ }
209        Err(err) => log::warn!(
210            "Failed to delete server auth token from keychain (service={SERVER_KEYCHAIN_SERVICE} key={url}): {err}"
211        ),
212    }
213}