this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 791 lines 28 kB view raw
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}