Skip to content

Commit bbca7a4

Browse files
committed
cache credentials in memory, refresh subscriptions on new account
1 parent f13146e commit bbca7a4

File tree

5 files changed

+154
-65
lines changed

5 files changed

+154
-65
lines changed

ntfy-daemon/src/credentials.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use std::cell::{Cell, RefCell};
2+
use std::collections::HashMap;
3+
use std::rc::Rc;
4+
5+
#[derive(Debug, Clone)]
6+
pub struct Credential {
7+
pub username: String,
8+
pub password: String,
9+
}
10+
11+
#[derive(Debug, Clone)]
12+
pub struct Credentials {
13+
keyring: Rc<oo7::Keyring>,
14+
creds: Rc<RefCell<HashMap<String, Credential>>>,
15+
}
16+
17+
impl Credentials {
18+
pub async fn new() -> anyhow::Result<Self> {
19+
let mut this = Self {
20+
keyring: Rc::new(
21+
oo7::Keyring::new()
22+
.await
23+
.expect("Failed to start Secret Service"),
24+
),
25+
creds: Default::default(),
26+
};
27+
this.load().await?;
28+
Ok(this)
29+
}
30+
pub async fn load(&mut self) -> anyhow::Result<()> {
31+
let attrs = HashMap::from([("type", "password")]);
32+
let values = self
33+
.keyring
34+
.search_items(attrs)
35+
.await
36+
.map_err(|e| capnp::Error::failed(e.to_string()))?;
37+
38+
self.creds.borrow_mut().clear();
39+
for item in values {
40+
let attrs = item
41+
.attributes()
42+
.await
43+
.map_err(|e| capnp::Error::failed(e.to_string()))?;
44+
self.creds.borrow_mut().insert(
45+
attrs["server"].to_string(),
46+
Credential {
47+
username: attrs["username"].to_string(),
48+
password: std::str::from_utf8(&item.secret().await?)?.to_string(),
49+
},
50+
);
51+
}
52+
Ok(())
53+
}
54+
pub fn get(&self, server: &str) -> Option<Credential> {
55+
self.creds.borrow().get(server).cloned()
56+
}
57+
pub fn list_all(&self) -> HashMap<String, Credential> {
58+
self.creds.borrow().clone()
59+
}
60+
pub async fn insert(&self, server: &str, username: &str, password: &str) -> anyhow::Result<()> {
61+
let attrs = HashMap::from([
62+
("type", "password"),
63+
("username", username),
64+
("server", server),
65+
]);
66+
self.keyring
67+
.create_item("Password", attrs, password, true)
68+
.await
69+
.map_err(|e| capnp::Error::failed(e.to_string()))?;
70+
71+
self.creds.borrow_mut().insert(
72+
server.to_string(),
73+
Credential {
74+
username: username.to_string(),
75+
password: password.to_string(),
76+
},
77+
);
78+
Ok(())
79+
}
80+
pub async fn delete(&self, server: &str) -> anyhow::Result<()> {
81+
let creds = {
82+
self.creds
83+
.borrow()
84+
.get(server)
85+
.ok_or(anyhow::anyhow!("server creds not found"))?
86+
.clone()
87+
};
88+
let attrs = HashMap::from([
89+
("type", "password"),
90+
("username", &creds.username),
91+
("server", server),
92+
]);
93+
self.keyring
94+
.delete(attrs)
95+
.await
96+
.map_err(|e| capnp::Error::failed(e.to_string()))?;
97+
self.creds
98+
.borrow_mut()
99+
.remove(server)
100+
.ok_or(anyhow::anyhow!("server creds not found"))?;
101+
Ok(())
102+
}
103+
}

ntfy-daemon/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod credentials;
12
pub mod message_repo;
23
pub mod models;
34
pub mod retry;
@@ -16,7 +17,7 @@ pub struct SharedEnv {
1617
proxy: Arc<dyn models::NotificationProxy>,
1718
http: reqwest::Client,
1819
network: Arc<dyn models::NetworkMonitorProxy>,
19-
keyring: Rc<oo7::Keyring>,
20+
credentials: credentials::Credentials,
2021
}
2122

2223
#[derive(thiserror::Error, Debug)]

ntfy-daemon/src/ntfy.capnp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface Subscription {
3131
updateReadUntil @4 (value: UInt64);
3232

3333
clearNotifications @5 ();
34+
refresh @6 ();
3435
}
3536

3637
struct Account {

ntfy-daemon/src/system_client.rs

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,20 @@ impl subscription::Server for SubscriptionImpl {
326326
model.read_until = value;
327327
Promise::ok(())
328328
}
329+
fn refresh(
330+
&mut self,
331+
_: subscription::RefreshParams,
332+
_: subscription::RefreshResults,
333+
) -> capnp::capability::Promise<(), capnp::Error> {
334+
let sender = self.topic_listener.clone();
335+
Promise::from_future(async move {
336+
sender
337+
.send(ControlFlow::Continue(()))
338+
.await
339+
.map_err(|e| capnp::Error::failed(format!("{:?}", e)))?;
340+
Ok(())
341+
})
342+
}
329343
}
330344

331345
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@@ -343,7 +357,7 @@ impl SystemNotifier {
343357
dbpath: &str,
344358
notification_proxy: Arc<dyn models::NotificationProxy>,
345359
network: Arc<dyn models::NetworkMonitorProxy>,
346-
keyring: oo7::Keyring,
360+
credentials: crate::credentials::Credentials,
347361
) -> Self {
348362
Self {
349363
watching: Rc::new(RefCell::new(HashMap::new())),
@@ -352,7 +366,7 @@ impl SystemNotifier {
352366
proxy: notification_proxy,
353367
http: build_client().unwrap(),
354368
network,
355-
keyring: Rc::new(keyring),
369+
credentials,
356370
},
357371
}
358372
}
@@ -388,6 +402,18 @@ impl SystemNotifier {
388402
Ok(())
389403
})
390404
}
405+
pub fn refresh_all(&mut self) -> Promise<(), capnp::Error> {
406+
let watching = self.watching.clone();
407+
Promise::from_future(async move {
408+
let reqs: Vec<_> = watching
409+
.borrow()
410+
.values()
411+
.map(|w| w.refresh_request())
412+
.collect();
413+
join_all(reqs.into_iter().map(|x| x.send().promise)).await;
414+
Ok(())
415+
})
416+
}
391417
}
392418

393419
impl system_notifier::Server for SystemNotifier {
@@ -457,35 +483,26 @@ impl system_notifier::Server for SystemNotifier {
457483
_: system_notifier::ListAccountsParams,
458484
mut results: system_notifier::ListAccountsResults,
459485
) -> capnp::capability::Promise<(), capnp::Error> {
460-
let keyring = self.env.keyring.clone();
486+
let values = self.env.credentials.list_all();
461487

462488
Promise::from_future(async move {
463-
let attrs = HashMap::from([("type", "password")]);
464-
let values = keyring
465-
.search_items(attrs)
466-
.await
467-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
468-
469489
let mut list = results.get().init_list(values.len() as u32);
470-
for (i, item) in values.iter().enumerate() {
471-
let attrs = item
472-
.attributes()
473-
.await
474-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
490+
for (i, item) in values.into_iter().enumerate() {
475491
let mut acc = list.reborrow().get(i as u32);
476-
acc.set_username(attrs["username"][..].into());
477-
acc.set_server(attrs["server"][..].into());
492+
acc.set_server(item.0[..].into());
493+
acc.set_username(item.1.username[..].into());
478494
}
479495
Ok(())
480496
})
481497
}
482498
fn add_account(
483499
&mut self,
484500
params: system_notifier::AddAccountParams,
485-
mut results: system_notifier::AddAccountResults,
501+
_: system_notifier::AddAccountResults,
486502
) -> capnp::capability::Promise<(), capnp::Error> {
487-
let keyring = self.env.keyring.clone();
503+
let credentials = self.env.credentials.clone();
488504
let http = self.env.http.clone();
505+
let refresh = self.refresh_all();
489506
Promise::from_future(async move {
490507
let account = params.get()?.get_account()?;
491508
let username = account.get_username()?.to_str()?;
@@ -503,15 +520,11 @@ impl system_notifier::Server for SystemNotifier {
503520
.error_for_status()
504521
.map_err(|e| capnp::Error::failed(e.to_string()))?;
505522

506-
let attrs = HashMap::from([
507-
("type", "password"),
508-
("username", username),
509-
("server", server),
510-
]);
511-
keyring
512-
.create_item("Password", attrs, password, true)
523+
credentials
524+
.insert(server, username, password)
513525
.await
514526
.map_err(|e| capnp::Error::failed(e.to_string()))?;
527+
refresh.await?;
515528

516529
info!(server = %server, username = %username, "added account");
517530

@@ -521,21 +534,16 @@ impl system_notifier::Server for SystemNotifier {
521534
fn remove_account(
522535
&mut self,
523536
params: system_notifier::RemoveAccountParams,
524-
mut results: system_notifier::RemoveAccountResults,
537+
_: system_notifier::RemoveAccountResults,
525538
) -> capnp::capability::Promise<(), capnp::Error> {
526-
let keyring = self.env.keyring.clone();
539+
let credentials = self.env.credentials.clone();
527540
Promise::from_future(async move {
528541
let account = params.get()?.get_account()?;
529542
let username = account.get_username()?.to_str()?;
530543
let server = account.get_server()?.to_str()?;
531544

532-
let attrs = HashMap::from([
533-
("type", "password"),
534-
("username", username),
535-
("server", server),
536-
]);
537-
keyring
538-
.delete(attrs)
545+
credentials
546+
.delete(server)
539547
.await
540548
.map_err(|e| capnp::Error::failed(e.to_string()))?;
541549

@@ -561,17 +569,13 @@ pub fn start(
561569
UnixListener::bind(&socket_path).unwrap()
562570
});
563571

564-
let keyring = rt.block_on(async {
565-
oo7::Keyring::new()
566-
.await
567-
.expect("Failed to start Secret Service")
568-
});
569-
570572
let dbpath = dbpath.to_owned();
571573
let f = move || {
574+
let credentials =
575+
rt.block_on(async { crate::credentials::Credentials::new().await.unwrap() });
572576
let local = tokio::task::LocalSet::new();
573577
let mut system_notifier =
574-
SystemNotifier::new(&dbpath, notification_proxy, network_proxy, keyring);
578+
SystemNotifier::new(&dbpath, notification_proxy, network_proxy, credentials);
575579
local.spawn_local(async move {
576580
system_notifier.watch_subscribed().await.unwrap();
577581
let system_client: system_notifier::Client = capnp_rpc::new_client(system_notifier);

ntfy-daemon/src/topic_listener.rs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -170,34 +170,14 @@ impl TopicListener {
170170

171171
#[instrument(skip_all)]
172172
async fn recv_and_forward(&mut self) -> anyhow::Result<()> {
173-
let (username, password) = {
174-
let attrs = HashMap::from([("type", "password"), ("server", &self.endpoint)]);
175-
let items = self
176-
.env
177-
.keyring
178-
.search_items(attrs)
179-
.await
180-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
181-
182-
if let Some(item) = items.into_iter().next() {
183-
let attrs = item
184-
.attributes()
185-
.await
186-
.map_err(|e| capnp::Error::failed(e.to_string()))?;
187-
let password = item.secret().await?;
188-
let password = std::str::from_utf8(&*password)?;
189-
(attrs.get("username").cloned(), Some(password.to_string()))
190-
} else {
191-
(None, None)
192-
}
193-
};
173+
let creds = self.env.credentials.get(&self.endpoint);
194174
let req = topic_request(
195175
&self.env.http,
196176
&self.endpoint,
197177
&self.topic,
198178
self.since,
199-
username.as_deref(),
200-
password.as_deref(),
179+
creds.as_ref().map(|x| x.username.as_str()),
180+
creds.as_ref().map(|x| x.password.as_str()),
201181
);
202182
let res = self.env.http.execute(req?).await?;
203183
let reader = tokio_util::io::StreamReader::new(

0 commit comments

Comments
 (0)