this repo has no description
1use jacquard::{
2 CowStr, IntoStatic,
3 client::{
4 AgentSession, AtpSession, FileAuthStore, SessionStore, SessionStoreError,
5 credential_session::{CredentialSession, SessionKey},
6 token::StoredSession,
7 },
8 error::{ClientError, XrpcResult},
9 identity::JacquardResolver,
10 prelude::{HttpClient, IdentityResolver},
11 types::{did::Did, string::Handle},
12 xrpc::{XrpcClient, XrpcRequest, XrpcResponse},
13};
14use jacquard_identity::PublicResolver;
15use jacquard_oauth::{
16 atproto::AtprotoClientMetadata,
17 authstore::ClientAuthStore,
18 client::{OAuthClient, OAuthSession},
19 loopback::LoopbackConfig,
20 session::{ClientData, ClientSessionData},
21};
22use keyring::Entry;
23use owo_colors::OwoColorize;
24use serde::{Deserialize, Serialize, de::DeserializeOwned};
25use std::{
26 fmt::Display,
27 hash::Hash,
28 path::{Path, PathBuf},
29 sync::Arc,
30};
31
32use crate::{
33 StoreMethod,
34 error::{MapErrExt, OnyxError},
35};
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct StoredPasswordSession {
39 access_jwt: String,
40 refresh_jwt: String,
41 did: String,
42 session_id: String,
43 handle: String,
44}
45
46#[derive(Clone, Debug)]
47pub struct KeyringTokenStore {
48 pub service: String,
49}
50
51impl KeyringTokenStore {
52 pub fn new(service: String) -> Self {
53 Self { service }
54 }
55}
56
57impl<K: Send + Sync + Hash + Eq + Display, T: Send + Sync + Clone + Serialize + DeserializeOwned>
58 SessionStore<K, T> for KeyringTokenStore
59{
60 async fn get(&self, key: &K) -> Option<T> {
61 let key_string = key.to_string();
62 let entry = Entry::new(&self.service, &key_string).ok()?;
63 let value = entry.get_password().ok()?;
64 serde_json::from_str(&value).ok()
65 }
66
67 async fn set(&self, key: K, session: T) -> Result<(), SessionStoreError> {
68 let key_string = key.to_string();
69 let entry = Entry::new(&self.service, &key_string).map_session_store_err()?;
70 let value = serde_json::to_string(&session)?;
71 entry.set_password(&value).map_session_store_err()?;
72 Ok(())
73 }
74
75 async fn del(&self, key: &K) -> Result<(), SessionStoreError> {
76 let key_string = key.to_string();
77 let entry = Entry::new(&self.service, &key_string).map_session_store_err()?;
78 entry.delete_credential().map_session_store_err()?;
79 Ok(())
80 }
81}
82
83impl SessionStore<SessionKey, AtpSession> for KeyringAuthStore {
84 async fn get(&self, key: &SessionKey) -> Option<AtpSession> {
85 let key_str = format!("{}_{}", key.0, key.1);
86 if let Some(stored) =
87 SessionStore::<String, StoredPasswordSession>::get(&self.0, &key_str).await
88 {
89 Some(AtpSession {
90 access_jwt: stored.access_jwt.into(),
91 refresh_jwt: stored.refresh_jwt.into(),
92 did: stored.did.into(),
93 handle: stored.handle.into(),
94 })
95 } else {
96 None
97 }
98 }
99
100 async fn set(&self, key: SessionKey, session: AtpSession) -> Result<(), SessionStoreError> {
101 let key_str = format!("{}_{}", key.0, key.1);
102 let stored = StoredPasswordSession {
103 access_jwt: session.access_jwt.to_string(),
104 refresh_jwt: session.refresh_jwt.to_string(),
105 did: session.did.to_string(),
106 session_id: key.1.to_string(),
107 handle: session.handle.to_string(),
108 };
109 self.0.set(key_str, stored).await
110 }
111
112 async fn del(&self, key: &SessionKey) -> Result<(), SessionStoreError> {
113 let key_str = format!("{}_{}", key.0, key.1);
114 let entry = Entry::new(&self.0.service, &key_str).map_session_store_err()?;
115 entry.delete_credential().map_session_store_err()?;
116 Ok(())
117 }
118}
119
120// An light adaptation of `jacquard::FileAuthStore` for keyrings
121pub struct KeyringAuthStore(KeyringTokenStore);
122
123impl KeyringAuthStore {
124 pub fn new(service: String) -> Self {
125 Self(KeyringTokenStore::new(service))
126 }
127}
128
129impl jacquard_oauth::authstore::ClientAuthStore for KeyringAuthStore {
130 async fn get_session(
131 &self,
132 did: &Did<'_>,
133 session_id: &str,
134 ) -> Result<Option<ClientSessionData<'_>>, SessionStoreError> {
135 let key = format!("{}_{}", did, session_id);
136 if let StoredSession::OAuth(session) = self
137 .0
138 .get(&key)
139 .await
140 .ok_or(SessionStoreError::Other("not found".into()))?
141 {
142 Ok(Some(session.into()))
143 } else {
144 Ok(None)
145 }
146 }
147
148 async fn upsert_session(
149 &self,
150 session: ClientSessionData<'_>,
151 ) -> Result<(), SessionStoreError> {
152 let key = format!("{}_{}", session.account_did, session.session_id);
153 self.0
154 .set(key, StoredSession::OAuth(session.into()))
155 .await?;
156 Ok(())
157 }
158
159 async fn delete_session(
160 &self,
161 did: &Did<'_>,
162 session_id: &str,
163 ) -> Result<(), SessionStoreError> {
164 let key = format!("{}_{}", did, session_id).to_string();
165 let entry = Entry::new(&self.0.service, &key).map_session_store_err()?;
166
167 match entry.delete_credential() {
168 Ok(()) => Ok(()),
169 Err(keyring::Error::NoEntry) => Ok(()),
170 Err(e) => Err(SessionStoreError::Other(Box::new(e))),
171 }
172 }
173
174 async fn get_auth_req_info(
175 &self,
176 state: &str,
177 ) -> Result<Option<jacquard_oauth::session::AuthRequestData<'_>>, SessionStoreError> {
178 let key = format!("authreq_{}", state);
179 if let StoredSession::OAuthState(auth_req) = self
180 .0
181 .get(&key)
182 .await
183 .ok_or(SessionStoreError::Other("not found".into()))?
184 {
185 Ok(Some(auth_req.into()))
186 } else {
187 Ok(None)
188 }
189 }
190
191 async fn save_auth_req_info(
192 &self,
193 auth_req_info: &jacquard_oauth::session::AuthRequestData<'_>,
194 ) -> Result<(), SessionStoreError> {
195 let key = format!("authreq_{}", auth_req_info.state);
196 let state = auth_req_info
197 .clone()
198 .try_into()
199 .map_err(|e| SessionStoreError::Other(Box::new(e)))?;
200 self.0.set(key, StoredSession::OAuthState(state)).await?;
201 Ok(())
202 }
203
204 async fn delete_auth_req_info(&self, state: &str) -> Result<(), SessionStoreError> {
205 let key = format!("authreq_{}", state);
206 let entry = Entry::new(&self.0.service, &key).map_session_store_err()?;
207
208 match entry.delete_credential() {
209 Ok(()) => Ok(()),
210 Err(keyring::Error::NoEntry) => Ok(()),
211 Err(e) => Err(SessionStoreError::Other(Box::new(e))),
212 }
213 }
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
217pub enum AuthMethod {
218 OAuth,
219 AppPassword,
220}
221
222#[derive(Debug, Serialize, Deserialize)]
223pub struct AuthSession {
224 pub did: String,
225 pub handles: Vec<String>,
226 pub session_id: String,
227 pub store: StoreMethod,
228 pub auth: AuthMethod,
229}
230
231pub struct AuthSessionStore {
232 pub config_dir: PathBuf,
233}
234
235impl AuthSessionStore {
236 fn try_new(config_dir: &Path) -> Result<Self, OnyxError> {
237 if !config_dir.exists() {
238 std::fs::create_dir_all(config_dir)?;
239 }
240
241 Ok(Self {
242 config_dir: config_dir.to_owned(),
243 })
244 }
245
246 fn get_session(&self) -> Result<Option<AuthSession>, OnyxError> {
247 let session_path = self.config_dir.join("session.json");
248 if !session_path.exists() {
249 return Ok(None);
250 }
251
252 let session_str = std::fs::read_to_string(session_path)?;
253 let session = serde_json::from_str(&session_str)?;
254 Ok(Some(session))
255 }
256
257 fn set_session(&self, session: &AuthSession) -> Result<(), OnyxError> {
258 let session_str = serde_json::to_string(session)?;
259 let session_path = self.config_dir.join("session.json");
260 std::fs::write(&session_path, &session_str)?;
261
262 // set file perms on unix for security (just in case)
263 #[cfg(unix)]
264 {
265 use std::fs;
266 use std::os::unix::fs::PermissionsExt;
267
268 let perms = fs::Permissions::from_mode(0o0600); // -rw-------
269 fs::set_permissions(&session_path, perms)?;
270 }
271
272 Ok(())
273 }
274
275 fn delete_session(&self) -> Result<(), OnyxError> {
276 let session_path = self.config_dir.join("session.json");
277 if !session_path.exists() {
278 return Ok(());
279 }
280
281 std::fs::remove_file(&session_path)?;
282 Ok(())
283 }
284}
285
286// There was probably a better way (I hope)
287pub enum GenericSession {
288 KeyringOAuth(OAuthSession<JacquardResolver, KeyringAuthStore>),
289 FileOAuth(OAuthSession<JacquardResolver, FileAuthStore>),
290 KeyringPassword(CredentialSession<KeyringAuthStore, JacquardResolver>),
291 FilePassword(CredentialSession<FileAuthStore, JacquardResolver>),
292}
293
294impl HttpClient for GenericSession {
295 type Error = OnyxError;
296
297 async fn send_http(
298 &self,
299 request: http::Request<Vec<u8>>,
300 ) -> core::result::Result<http::Response<Vec<u8>>, Self::Error> {
301 match self {
302 GenericSession::KeyringOAuth(session) => session
303 .send_http(request)
304 .await
305 .map_err(|e| OnyxError::Auth(e.to_string())),
306 GenericSession::FileOAuth(session) => session
307 .send_http(request)
308 .await
309 .map_err(|e| OnyxError::Auth(e.to_string())),
310 GenericSession::KeyringPassword(session) => session
311 .send_http(request)
312 .await
313 .map_err(|e| OnyxError::Auth(e.to_string())),
314 GenericSession::FilePassword(session) => session
315 .send_http(request)
316 .await
317 .map_err(|e| OnyxError::Auth(e.to_string())),
318 }
319 }
320}
321
322impl XrpcClient for GenericSession {
323 async fn base_uri(&self) -> jacquard::CowStr<'static> {
324 match self {
325 GenericSession::KeyringOAuth(session) => session.base_uri().await,
326 GenericSession::FileOAuth(session) => session.base_uri().await,
327 GenericSession::KeyringPassword(session) => session.base_uri().await,
328 GenericSession::FilePassword(session) => session.base_uri().await,
329 }
330 }
331
332 async fn opts(&self) -> jacquard::xrpc::CallOptions<'_> {
333 match self {
334 GenericSession::KeyringOAuth(session) => session.opts().await,
335 GenericSession::FileOAuth(session) => session.opts().await,
336 GenericSession::KeyringPassword(session) => session.opts().await,
337 GenericSession::FilePassword(session) => session.opts().await,
338 }
339 }
340
341 async fn set_opts(&self, opts: jacquard::xrpc::CallOptions<'_>) {
342 match self {
343 GenericSession::KeyringOAuth(session) => session.set_opts(opts).await,
344 GenericSession::FileOAuth(session) => session.set_opts(opts).await,
345 GenericSession::KeyringPassword(session) => session.set_opts(opts).await,
346 GenericSession::FilePassword(session) => session.set_opts(opts).await,
347 }
348 }
349
350 async fn set_base_uri(&self, url: jacquard::url::Url) {
351 match self {
352 GenericSession::KeyringOAuth(session) => session.set_base_uri(url).await,
353 GenericSession::FileOAuth(session) => session.set_base_uri(url).await,
354 GenericSession::KeyringPassword(session) => session.set_base_uri(url).await,
355 GenericSession::FilePassword(session) => session.set_base_uri(url).await,
356 }
357 }
358
359 async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
360 where
361 R: XrpcRequest + Send + Sync,
362 <R as XrpcRequest>::Response: Send + Sync,
363 {
364 match self {
365 GenericSession::KeyringOAuth(session) => session.send::<R>(request).await,
366 GenericSession::FileOAuth(session) => session.send::<R>(request).await,
367 GenericSession::KeyringPassword(session) => session.send::<R>(request).await,
368 GenericSession::FilePassword(session) => session.send::<R>(request).await,
369 }
370 }
371
372 async fn send_with_opts<R>(
373 &self,
374 request: R,
375 opts: jacquard::xrpc::CallOptions<'_>,
376 ) -> XrpcResult<XrpcResponse<R>>
377 where
378 R: XrpcRequest + Send + Sync,
379 <R as XrpcRequest>::Response: Send + Sync,
380 Self: Sync,
381 {
382 match self {
383 GenericSession::KeyringOAuth(session) => {
384 session.send_with_opts::<R>(request, opts).await
385 }
386 GenericSession::FileOAuth(session) => session.send_with_opts::<R>(request, opts).await,
387 GenericSession::KeyringPassword(session) => {
388 session.send_with_opts::<R>(request, opts).await
389 }
390 GenericSession::FilePassword(session) => {
391 session.send_with_opts::<R>(request, opts).await
392 }
393 }
394 }
395}
396
397impl IdentityResolver for GenericSession {
398 fn options(&self) -> &jacquard_identity::resolver::ResolverOptions {
399 match self {
400 GenericSession::KeyringOAuth(session) => session.options(),
401 GenericSession::FileOAuth(session) => session.options(),
402 GenericSession::KeyringPassword(session) => session.options(),
403 GenericSession::FilePassword(session) => session.options(),
404 }
405 }
406
407 async fn resolve_handle(
408 &self,
409 handle: &Handle<'_>,
410 ) -> jacquard_identity::resolver::Result<Did<'static>>
411 where
412 Self: Sync,
413 {
414 match self {
415 GenericSession::KeyringOAuth(session) => session.resolve_handle(handle).await,
416 GenericSession::FileOAuth(session) => session.resolve_handle(handle).await,
417 GenericSession::KeyringPassword(session) => session.resolve_handle(handle).await,
418 GenericSession::FilePassword(session) => session.resolve_handle(handle).await,
419 }
420 }
421
422 async fn resolve_did_doc(
423 &self,
424 did: &Did<'_>,
425 ) -> jacquard_identity::resolver::Result<jacquard_identity::resolver::DidDocResponse>
426 where
427 Self: Sync,
428 {
429 match self {
430 GenericSession::KeyringOAuth(session) => session.resolve_did_doc(did).await,
431 GenericSession::FileOAuth(session) => session.resolve_did_doc(did).await,
432 GenericSession::KeyringPassword(session) => session.resolve_did_doc(did).await,
433 GenericSession::FilePassword(session) => session.resolve_did_doc(did).await,
434 }
435 }
436}
437
438impl AgentSession for GenericSession {
439 fn session_kind(&self) -> jacquard::client::AgentKind {
440 match self {
441 GenericSession::KeyringOAuth(_) => jacquard::client::AgentKind::OAuth,
442 GenericSession::FileOAuth(_) => jacquard::client::AgentKind::OAuth,
443 GenericSession::KeyringPassword(_) => jacquard::client::AgentKind::AppPassword,
444 GenericSession::FilePassword(_) => jacquard::client::AgentKind::AppPassword,
445 }
446 }
447
448 async fn session_info(&self) -> Option<(Did<'static>, Option<jacquard::CowStr<'static>>)> {
449 match self {
450 GenericSession::KeyringOAuth(session) => {
451 let (did, sid) = session.session_info().await;
452 Some((did.into_static(), Some(sid.into_static())))
453 }
454 GenericSession::FileOAuth(session) => {
455 let (did, sid) = session.session_info().await;
456 Some((did.into_static(), Some(sid.into_static())))
457 }
458 GenericSession::KeyringPassword(session) => {
459 session.session_info().await.map(|key| (key.0, Some(key.1)))
460 }
461 GenericSession::FilePassword(session) => {
462 session.session_info().await.map(|key| (key.0, Some(key.1)))
463 }
464 }
465 }
466
467 async fn endpoint(&self) -> jacquard::CowStr<'static> {
468 match self {
469 GenericSession::KeyringOAuth(session) => session.endpoint().await,
470 GenericSession::FileOAuth(session) => session.endpoint().await,
471 GenericSession::KeyringPassword(session) => session.endpoint().await,
472 GenericSession::FilePassword(session) => session.endpoint().await,
473 }
474 }
475
476 async fn set_options<'a>(&'a self, opts: jacquard::xrpc::CallOptions<'a>) {
477 match self {
478 GenericSession::KeyringOAuth(session) => session.set_options(opts).await,
479 GenericSession::FileOAuth(session) => session.set_options(opts).await,
480 GenericSession::KeyringPassword(session) => session.set_options(opts).await,
481 GenericSession::FilePassword(session) => session.set_options(opts).await,
482 }
483 }
484
485 async fn refresh(&self) -> jacquard::error::XrpcResult<jacquard::AuthorizationToken<'static>> {
486 match self {
487 GenericSession::KeyringOAuth(session) => session
488 .refresh()
489 .await
490 .map(|t| t.into_static())
491 .map_err(|e| ClientError::transport(e).with_context("OAuth token refresh failed")),
492
493 GenericSession::FileOAuth(session) => session
494 .refresh()
495 .await
496 .map(|t| t.into_static())
497 .map_err(|e| ClientError::transport(e).with_context("OAuth token refresh failed")),
498 GenericSession::KeyringPassword(session) => session
499 .refresh()
500 .await
501 .map(|t| t.into_static())
502 .map_err(|e| {
503 ClientError::transport(e).with_context("App password token refresh failed")
504 }),
505 GenericSession::FilePassword(session) => session
506 .refresh()
507 .await
508 .map(|t| t.into_static())
509 .map_err(|e| {
510 ClientError::transport(e).with_context("App password token refresh failed")
511 }),
512 }
513 }
514}
515
516pub struct Authenticator {
517 pub service: String,
518 pub config_dir: PathBuf,
519
520 resolver: JacquardResolver,
521 auth_store: AuthSessionStore,
522}
523
524impl Authenticator {
525 pub fn try_new(service: &str, config_dir: &Path) -> Result<Self, OnyxError> {
526 Ok(Self {
527 service: service.to_owned(),
528 config_dir: config_dir.to_owned(),
529 resolver: PublicResolver::default(),
530 auth_store: AuthSessionStore::try_new(config_dir)?,
531 })
532 }
533
534 async fn resolve_did(&self, ident: &str) -> Result<Did<'_>, OnyxError> {
535 if let Ok(did) = ident.parse() {
536 return Ok(did);
537 }
538
539 let handle = Handle::new(ident)?;
540 let did = self.resolver.resolve_handle(&handle).await?;
541 Ok(did)
542 }
543
544 async fn resolve_handles(&self, ident: &str) -> Result<Vec<Handle<'_>>, OnyxError> {
545 if let Ok(handle) = ident.parse() {
546 return Ok(vec![handle]);
547 }
548
549 let did = Did::new(ident)?;
550 let did_doc = self.resolver.resolve_did_doc(&did).await?;
551 let doc = did_doc.parse()?;
552 Ok(doc.handles())
553 }
554
555 pub async fn login(
556 &self,
557 ident: &str,
558 store: StoreMethod,
559 password: Option<String>,
560 ) -> Result<(), OnyxError> {
561 // ensure previous creds are cleared
562 let _ = self.logout().await;
563
564 match password {
565 Some(pass) => self.login_app_password(ident, store, pass).await,
566 None => self.login_oauth(ident, store).await,
567 }
568 }
569
570 async fn login_app_password(
571 &self,
572 ident: &str,
573 store_method: StoreMethod,
574 password: String,
575 ) -> Result<(), OnyxError> {
576 let session_id = "session";
577 let resolver = PublicResolver::default();
578
579 let handles = self
580 .resolve_handles(ident)
581 .await
582 .unwrap_or(vec![])
583 .iter()
584 .map(|h| h.to_string())
585 .collect();
586
587 // TODO: See if there's a clean way to fix this duplication
588
589 if store_method == StoreMethod::Keyring {
590 let store = KeyringAuthStore::new(self.service.clone());
591 let session = CredentialSession::new(Arc::new(store), Arc::new(resolver));
592 let auth = session
593 .login(
594 CowStr::Borrowed(ident),
595 CowStr::Borrowed(&password),
596 Some(CowStr::Borrowed(session_id)),
597 None,
598 None,
599 None,
600 )
601 .await?;
602 let auth_session = AuthSession {
603 did: auth.did.to_string(),
604 handles,
605 session_id: session_id.to_string(),
606 store: store_method,
607 auth: AuthMethod::AppPassword,
608 };
609 self.auth_store.set_session(&auth_session)?;
610 } else if store_method == StoreMethod::File {
611 let store = FileAuthStore::new(self.get_file_store());
612 let session = CredentialSession::new(Arc::new(store), Arc::new(resolver));
613 let auth = session
614 .login(
615 CowStr::Borrowed(ident),
616 CowStr::Borrowed(&password),
617 Some(CowStr::Borrowed(session_id)),
618 None,
619 None,
620 None,
621 )
622 .await?;
623 let auth_session = AuthSession {
624 did: auth.did.to_string(),
625 handles,
626 session_id: session_id.to_string(),
627 store: store_method,
628 auth: AuthMethod::AppPassword,
629 };
630 self.auth_store.set_session(&auth_session)?;
631 }
632
633 Ok(())
634 }
635
636 async fn login_oauth(&self, ident: &str, store_method: StoreMethod) -> Result<(), OnyxError> {
637 let did = self.resolve_did(ident).await?;
638
639 let client_data = ClientData {
640 keyset: None,
641 config: AtprotoClientMetadata::default_localhost(),
642 };
643
644 let handles = self
645 .resolve_handles(ident)
646 .await
647 .unwrap_or(vec![])
648 .iter()
649 .map(|h| h.to_string())
650 .collect();
651
652 // There's probably a better way of doing this to avoid duplication,
653 // but stores aren't dyn-compatible, and I couldn't be bothered
654 if store_method == StoreMethod::Keyring {
655 let store = KeyringAuthStore::new(self.service.clone());
656 let oauth = OAuthClient::new(store, client_data);
657 let session = oauth
658 .login_with_local_server(&did, Default::default(), LoopbackConfig::default())
659 .await?;
660
661 let session_id = session.data.try_read()?.session_id.clone();
662 let auth_session = AuthSession {
663 did: did.to_string(),
664 handles,
665 session_id: session_id.to_string(),
666 store: store_method,
667 auth: AuthMethod::OAuth,
668 };
669 self.auth_store.set_session(&auth_session)?;
670 } else if store_method == StoreMethod::File {
671 let store = FileAuthStore::new(self.get_file_store());
672 let oauth = OAuthClient::new(store, client_data);
673 let session = oauth
674 .login_with_local_server(&did, Default::default(), LoopbackConfig::default())
675 .await?;
676
677 let session_id = session.data.try_read()?.session_id.clone();
678 let auth_session = AuthSession {
679 did: did.to_string(),
680 handles,
681 session_id: session_id.to_string(),
682 store: store_method,
683 auth: AuthMethod::OAuth,
684 };
685 self.auth_store.set_session(&auth_session)?;
686 }
687
688 Ok(())
689 }
690
691 pub async fn restore(&self) -> Result<GenericSession, OnyxError> {
692 let session = match self.auth_store.get_session()? {
693 Some(s) => s,
694 None => {
695 return Err(OnyxError::Auth("not logged in".to_string()));
696 }
697 };
698
699 match session.auth {
700 AuthMethod::OAuth => self.restore_oauth(session).await,
701 AuthMethod::AppPassword => self.restore_app_password(session).await,
702 }
703 }
704
705 async fn restore_app_password(
706 &self,
707 auth_session: AuthSession,
708 ) -> Result<GenericSession, OnyxError> {
709 let did = Did::new(&auth_session.did)?;
710 let resolver = PublicResolver::default();
711
712 match auth_session.store {
713 StoreMethod::Keyring => {
714 let store = KeyringAuthStore::new(self.service.clone());
715 let session = CredentialSession::new(Arc::new(store), Arc::new(resolver));
716 session
717 .restore(did, CowStr::Borrowed(&auth_session.session_id))
718 .await?;
719 Ok(GenericSession::KeyringPassword(session))
720 }
721 StoreMethod::File => {
722 let store = FileAuthStore::new(self.get_file_store());
723 let session = CredentialSession::new(Arc::new(store), Arc::new(resolver));
724 session
725 .restore(did, CowStr::Borrowed(&auth_session.session_id))
726 .await?;
727 Ok(GenericSession::FilePassword(session))
728 }
729 }
730 }
731
732 async fn restore_oauth(&self, session: AuthSession) -> Result<GenericSession, OnyxError> {
733 let did = Did::new(&session.did)?;
734
735 let client_data = ClientData {
736 keyset: None,
737 config: AtprotoClientMetadata::default_localhost(),
738 };
739
740 match session.store {
741 StoreMethod::Keyring => {
742 let store = KeyringAuthStore::new(self.service.clone());
743 let oauth = OAuthClient::new(store, client_data);
744 let session = oauth.restore(&did, &session.session_id).await?;
745 Ok(GenericSession::KeyringOAuth(session))
746 }
747 StoreMethod::File => {
748 let store = FileAuthStore::new(self.get_file_store());
749 let oauth = OAuthClient::new(store, client_data);
750 let session = oauth.restore(&did, &session.session_id).await?;
751 Ok(GenericSession::FileOAuth(session))
752 }
753 }
754 }
755
756 pub async fn logout(&self) -> Result<(), OnyxError> {
757 let session = match self.auth_store.get_session()? {
758 Some(s) => s,
759 None => {
760 return Err(OnyxError::Auth("not logged in".to_string()));
761 }
762 };
763
764 println!("{}", format!("logging out {}", &session.did).dimmed());
765
766 let did = Did::new(&session.did)?;
767
768 if session.store == StoreMethod::Keyring {
769 let store = KeyringAuthStore::new(self.service.clone());
770 store.delete_session(&did, &session.session_id).await?;
771 } else if session.store == StoreMethod::File {
772 let store = FileAuthStore::new(self.get_file_store());
773 store.delete_session(&did, &session.session_id).await?;
774 }
775
776 self.auth_store.delete_session()
777 }
778
779 pub fn get_session_info(&self) -> Result<AuthSession, OnyxError> {
780 let session = self.auth_store.get_session()?;
781 if let Some(session) = session {
782 Ok(session)
783 } else {
784 Err(OnyxError::Auth("not logged in".to_string()))
785 }
786 }
787
788 fn get_file_store(&self) -> PathBuf {
789 self.config_dir.join("store.json")
790 }
791}