1use super::{
10 common::send_authd_request,
11 constants::{SN_AUTHD_ENDPOINT_HOST, SN_AUTHD_ENDPOINT_PORT},
12 notifs_endpoint::jsonrpc_listen,
13};
14
15use crate::{AuthReq, AuthedAppsList, Error, Result, SafeAuthReqId};
16
17use serde::{Deserialize, Serialize};
18use serde_json::json;
19use std::{
20 io::{self, Write},
21 path::{Path, PathBuf},
22 process::{Command, Stdio},
23};
24use tokio::{
25 sync::{mpsc, oneshot},
26 task,
27};
28use tracing::{debug, error, info, trace};
29
30#[cfg(not(target_os = "windows"))]
31const SN_AUTHD_EXECUTABLE: &str = "sn_authd";
32
33#[cfg(target_os = "windows")]
34const SN_AUTHD_EXECUTABLE: &str = "sn_authd.exe";
35
36const ENV_VAR_SN_AUTHD_PATH: &str = "SN_AUTHD_PATH";
37
38#[derive(Serialize, Deserialize, Clone, Debug)]
39pub struct AuthdStatus {
40 pub safe_unlocked: bool,
41 pub num_auth_reqs: u32,
42 pub num_notif_subs: u32,
43 pub authd_version: String,
44}
45
46pub type PendingAuthReqs = Vec<AuthReq>;
48
49pub type AuthAllowPrompt = dyn Fn(AuthReq) -> Option<bool> + std::marker::Send + std::marker::Sync;
52
53const SN_AUTHD_METHOD_STATUS: &str = "status";
55
56const SN_AUTHD_METHOD_UNLOCK: &str = "unlock";
58
59const SN_AUTHD_METHOD_LOCK: &str = "lock";
61
62const SN_AUTHD_METHOD_CREATE: &str = "create";
64
65const SN_AUTHD_METHOD_AUTHED_APPS: &str = "authed-apps";
67
68const SN_AUTHD_METHOD_REVOKE: &str = "revoke";
70
71const SN_AUTHD_METHOD_AUTH_REQS: &str = "auth-reqs";
73
74const SN_AUTHD_METHOD_ALLOW: &str = "allow";
76
77const SN_AUTHD_METHOD_DENY: &str = "deny";
79
80const SN_AUTHD_METHOD_SUBSCRIBE: &str = "subscribe";
82
83const SN_AUTHD_METHOD_UNSUBSCRIBE: &str = "unsubscribe";
85
86const SN_AUTHD_CMD_UPDATE: &str = "update";
88
89const SN_AUTHD_CMD_START: &str = "start";
91
92const SN_AUTHD_CMD_STOP: &str = "stop";
94
95const SN_AUTHD_CMD_RESTART: &str = "restart";
97
98pub struct SafeAuthdClient {
100 pub authd_endpoint: String,
101 pub authd_cert_path: PathBuf,
102 pub authd_notify_cert_path: PathBuf,
103 pub authd_notify_key_path: PathBuf,
104 subscribed_endpoint: Option<(String, task::JoinHandle<()>, task::JoinHandle<()>)>,
105 }
109
110impl Drop for SafeAuthdClient {
111 fn drop(&mut self) {
112 trace!("SafeAuthdClient instance being dropped...");
113 match &self.subscribed_endpoint {
115 None => {}
116 Some((url, _, _)) => {
117 match futures::executor::block_on(send_unsubscribe(
118 url,
119 &self.authd_endpoint,
120 &self.authd_cert_path,
121 )) {
122 Ok(msg) => {
123 debug!("{}", msg);
124 }
125 Err(err) => {
126 debug!("Failed to unsubscribe endpoint from authd: {}", err);
131 }
132 }
133 }
134 }
135 }
136}
137
138impl SafeAuthdClient {
139 pub fn new<P: AsRef<Path>>(
140 endpoint: Option<String>,
141 authd_cert_path: P,
142 authd_notify_cert_path: P,
143 authd_notify_key_path: P,
144 ) -> Self {
145 let endpoint = match endpoint {
146 None => format!("{SN_AUTHD_ENDPOINT_HOST}:{SN_AUTHD_ENDPOINT_PORT}"),
147 Some(endpoint) => endpoint,
148 };
149 debug!("Creating new authd client for endpoint {}", endpoint);
150 Self {
151 authd_endpoint: endpoint,
152 authd_cert_path: PathBuf::from(authd_cert_path.as_ref()),
153 authd_notify_cert_path: PathBuf::from(authd_notify_cert_path.as_ref()),
154 authd_notify_key_path: PathBuf::from(authd_notify_key_path.as_ref()),
155 subscribed_endpoint: None,
156 }
157 }
158
159 pub fn version(&self, authd_path: Option<&str>) -> Result<()> {
161 authd_run_cmd(authd_path, &["--version"])
162 }
163
164 pub fn update(&self, authd_path: Option<&str>) -> Result<()> {
166 authd_run_cmd(authd_path, &[SN_AUTHD_CMD_UPDATE])
167 }
168
169 pub fn start(&self, authd_path: Option<&str>) -> Result<()> {
171 authd_run_cmd(
172 authd_path,
173 &[SN_AUTHD_CMD_START, "--listen", &self.authd_endpoint],
174 )
175 }
176
177 pub fn stop(&self, authd_path: Option<&str>) -> Result<()> {
179 authd_run_cmd(authd_path, &[SN_AUTHD_CMD_STOP])
180 }
181
182 pub fn restart(&self, authd_path: Option<&str>) -> Result<()> {
184 authd_run_cmd(
185 authd_path,
186 &[SN_AUTHD_CMD_RESTART, "--listen", &self.authd_endpoint],
187 )
188 }
189
190 pub async fn status(&mut self) -> Result<AuthdStatus> {
192 debug!("Attempting to retrieve status report from remote authd...");
193 let status_report = send_authd_request::<AuthdStatus>(
194 &self.authd_cert_path,
195 &self.authd_endpoint,
196 SN_AUTHD_METHOD_STATUS,
197 serde_json::Value::Null,
198 )
199 .await?;
200
201 info!(
202 "SAFE status report retrieved successfully: {:?}",
203 status_report
204 );
205 Ok(status_report)
206 }
207
208 pub async fn unlock(&mut self, passphrase: &str, password: &str) -> Result<()> {
210 debug!("Attempting to unlock a Safe on remote authd...");
211 let authd_response = send_authd_request::<String>(
212 &self.authd_cert_path,
213 &self.authd_endpoint,
214 SN_AUTHD_METHOD_UNLOCK,
215 json!(vec![passphrase, password]),
216 )
217 .await?;
218
219 info!("The Safe was unlocked successful: {}", authd_response);
220 Ok(())
224 }
225
226 pub async fn lock(&mut self) -> Result<()> {
228 debug!("Locking the Safe on a remote authd...");
229 let authd_response = send_authd_request::<String>(
230 &self.authd_cert_path,
231 &self.authd_endpoint,
232 SN_AUTHD_METHOD_LOCK,
233 serde_json::Value::Null,
234 )
235 .await?;
236
237 info!("Locking action was successful: {}", authd_response);
238
239 Ok(())
243 }
244
245 pub async fn create(&self, passphrase: &str, password: &str) -> Result<()> {
248 debug!("Attempting to create a Safe using remote authd...");
249 let authd_response = send_authd_request::<String>(
250 &self.authd_cert_path,
251 &self.authd_endpoint,
252 SN_AUTHD_METHOD_CREATE,
253 json!(vec![passphrase, password]),
254 )
255 .await?;
256
257 debug!("Creation of a Safe was successful: {}", authd_response);
258 Ok(())
259 }
260
261 pub async fn authed_apps(&self) -> Result<AuthedAppsList> {
263 debug!("Attempting to fetch list of authorised apps from remote authd...");
264 let authed_apps_list = send_authd_request::<AuthedAppsList>(
265 &self.authd_cert_path,
266 &self.authd_endpoint,
267 SN_AUTHD_METHOD_AUTHED_APPS,
268 serde_json::Value::Null,
269 )
270 .await?;
271
272 debug!(
273 "List of applications authorised successfully received: {:?}",
274 authed_apps_list
275 );
276 Ok(authed_apps_list)
277 }
278
279 pub async fn revoke_app(&self, app_id: &str) -> Result<()> {
281 debug!(
282 "Requesting to revoke permissions from application: {}",
283 app_id
284 );
285 let authd_response = send_authd_request::<String>(
286 &self.authd_cert_path,
287 &self.authd_endpoint,
288 SN_AUTHD_METHOD_REVOKE,
289 json!(app_id),
290 )
291 .await?;
292
293 debug!(
294 "Application revocation action successful: {}",
295 authd_response
296 );
297 Ok(())
298 }
299
300 pub async fn auth_reqs(&self) -> Result<PendingAuthReqs> {
302 debug!("Attempting to fetch list of pending authorisation requests from remote authd...");
303 let auth_reqs_list = send_authd_request::<PendingAuthReqs>(
304 &self.authd_cert_path,
305 &self.authd_endpoint,
306 SN_AUTHD_METHOD_AUTH_REQS,
307 serde_json::Value::Null,
308 )
309 .await?;
310
311 debug!(
312 "List of pending authorisation requests successfully received: {:?}",
313 auth_reqs_list
314 );
315 Ok(auth_reqs_list)
316 }
317
318 pub async fn allow(&self, req_id: SafeAuthReqId) -> Result<()> {
320 debug!("Requesting to allow authorisation request: {}", req_id);
321 let authd_response = send_authd_request::<String>(
322 &self.authd_cert_path,
323 &self.authd_endpoint,
324 SN_AUTHD_METHOD_ALLOW,
325 json!(req_id.to_string()),
326 )
327 .await?;
328
329 debug!(
330 "Action to allow authorisation request was successful: {}",
331 authd_response
332 );
333 Ok(())
334 }
335
336 pub async fn deny(&self, req_id: SafeAuthReqId) -> Result<()> {
338 debug!("Requesting to deny authorisation request: {}", req_id);
339 let authd_response = send_authd_request::<String>(
340 &self.authd_cert_path,
341 &self.authd_endpoint,
342 SN_AUTHD_METHOD_DENY,
343 json!(req_id.to_string()),
344 )
345 .await?;
346
347 debug!(
348 "Action to deny authorisation request was successful: {}",
349 authd_response
350 );
351 Ok(())
352 }
353
354 pub async fn subscribe<
357 CB: 'static + Fn(AuthReq) -> Option<bool> + std::marker::Send + std::marker::Sync,
358 >(
359 &mut self,
360 endpoint_url: &str,
361 _app_id: &str,
362 allow_cb: CB,
363 ) -> Result<()> {
364 debug!("Subscribing to receive authorisation requests notifications...",);
365
366 let cert_path = self.authd_cert_path.to_str().ok_or_else(|| {
367 Error::AuthdClientError("Could not convert authd_cert_path to string".to_string())
368 })?;
369 let authd_response = send_authd_request::<String>(
370 &self.authd_cert_path,
371 &self.authd_endpoint,
372 SN_AUTHD_METHOD_SUBSCRIBE,
373 json!(vec![endpoint_url, cert_path]),
374 ).await.map_err(|err| Error::AuthdClientError(format!("Failed when trying to subscribe endpoint URL ({endpoint_url}) to receive authorisation request for self-auth: {err}")))?;
375
376 debug!(
377 "Successfully subscribed to receive authorisation requests notifications: {}",
378 authd_response
379 );
380
381 let (tx, mut rx) = mpsc::unbounded_channel::<(AuthReq, oneshot::Sender<Option<bool>>)>();
384
385 let listen = endpoint_url.to_string();
386 let authd_notify_cert_path = self.authd_notify_cert_path.clone();
390 let authd_notify_key_path = self.authd_notify_key_path.clone();
391 let endpoint_thread_join_handle = tokio::spawn(async move {
392 match jsonrpc_listen(&listen, &authd_notify_cert_path, &authd_notify_key_path, tx).await
393 {
394 Ok(()) => {
395 info!("Endpoint successfully launched for receiving auth req notifications");
396 }
397 Err(err) => {
398 error!(
399 "Failed to launch endpoint for receiving auth req notifications: {}",
400 err
401 );
402 }
403 }
404 });
405
406 let cb = Box::new(allow_cb);
407 let cb_thread_join_handle = tokio::spawn(async move {
408 while let Some((auth_req, decision_tx)) = rx.recv().await {
409 debug!(
410 "Notification for authorisation request ({}) from app ID '{}' received",
411 auth_req.req_id, auth_req.app_id
412 );
413
414 let user_decision = cb(auth_req);
416
417 match decision_tx.send(user_decision) {
420 Ok(_) => debug!("Auth req decision sent to authd"),
421 Err(_) => error!("Auth req decision couldn't be sent back to authd"),
422 };
423 }
424 });
425
426 self.subscribed_endpoint = Some((
427 endpoint_url.to_string(),
428 endpoint_thread_join_handle,
429 cb_thread_join_handle,
430 ));
431
432 Ok(())
433 }
434
435 pub async fn subscribe_url(&self, endpoint_url: &str) -> Result<()> {
437 debug!(
438 "Subscribing '{}' as endpoint for authorisation requests notifications...",
439 endpoint_url
440 );
441
442 let authd_response = send_authd_request::<String>(
443 &self.authd_cert_path,
444 &self.authd_endpoint,
445 SN_AUTHD_METHOD_SUBSCRIBE,
446 json!(vec![endpoint_url]),
447 )
448 .await?;
449
450 debug!(
451 "Successfully subscribed a URL for authorisation requests notifications: {}",
452 authd_response
453 );
454 Ok(())
455 }
456
457 pub async fn unsubscribe(&mut self, endpoint_url: &str) -> Result<()> {
459 debug!("Unsubscribing from authorisation requests notifications...",);
460 let authd_response =
461 send_unsubscribe(endpoint_url, &self.authd_endpoint, &self.authd_cert_path).await?;
462 debug!(
463 "Successfully unsubscribed from authorisation requests notifications: {}",
464 authd_response
465 );
466
467 if let Some((url, _, _)) = &self.subscribed_endpoint {
469 if endpoint_url == url {
470 self.subscribed_endpoint = None;
472 }
473 }
474
475 Ok(())
476 }
477}
478
479async fn send_unsubscribe(
480 endpoint_url: &str,
481 authd_endpoint: &str,
482 authd_cert_path: &Path,
483) -> Result<String> {
484 send_authd_request::<String>(
485 authd_cert_path,
486 authd_endpoint,
487 SN_AUTHD_METHOD_UNSUBSCRIBE,
488 json!(endpoint_url),
489 )
490 .await
491}
492
493fn authd_run_cmd(authd_path: Option<&str>, args: &[&str]) -> Result<()> {
494 let mut path = get_authd_bin_path(authd_path)?;
495 path.push(SN_AUTHD_EXECUTABLE);
496 let path_str = path.display().to_string();
497 debug!("Attempting to {} authd from '{}' ...", args[0], path_str);
498
499 let output = Command::new(&path_str)
500 .args(args)
501 .stdout(Stdio::piped())
502 .stderr(Stdio::piped())
503 .output()
504 .map_err(|err| {
505 Error::AuthdClientError(format!("Failed to execute authd from '{path_str}': {err}",))
506 })?;
507
508 if output.status.success() {
509 io::stdout()
510 .write_all(&output.stdout)
511 .map_err(|err| Error::AuthdClientError(format!("Failed to output stdout: {err}")))?;
512 Ok(())
513 } else {
514 match output.status.code() {
515 Some(10) => {
516 Err(Error::AuthdAlreadyStarted(format!(
518 "Failed to start sn_authd daemon '{path_str}' as an instance seems to be already running",
519 )))
520 }
521 Some(_) | None => Err(Error::AuthdError(format!(
522 "Failed when invoking sn_authd executable from '{path_str}'",
523 ))),
524 }
525 }
526}
527
528fn get_authd_bin_path(authd_path: Option<&str>) -> Result<PathBuf> {
529 match authd_path {
530 Some(p) => Ok(PathBuf::from(p)),
531 None => {
532 if let Ok(authd_path) = std::env::var(ENV_VAR_SN_AUTHD_PATH) {
534 Ok(PathBuf::from(authd_path))
535 } else {
536 let mut path = dirs_next::home_dir().ok_or_else(|| {
537 Error::AuthdClientError("Failed to obtain user's home path".to_string())
538 })?;
539
540 path.push(".safe");
541 path.push("authd");
542 Ok(path)
543 }
544 }
545 }
546}