From b6d89757a6575cd9093008a28fe889717fd4eacb Mon Sep 17 00:00:00 2001 From: sylv256 Date: Sat, 10 Oct 2026 14:58:23 -0400 Subject: [PATCH] feat: login management --- client/src/main.rs | 62 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 110f1a9..986c870 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,10 +1,11 @@ -use std::{collections::HashMap, ops::Deref, sync::Arc, time::{Duration, Instant}}; +use std::{collections::HashMap, error::Error, ops::Deref, pin::Pin, sync::Arc, time::{Duration, Instant}}; -use atproto_client::HttpRecordResolver; +use atproto_client::{HttpRecordResolver, client::{AppPasswordAuth, Auth::{self, AppPassword}}, com::atproto::server::{AppPasswordSession, refresh_session}, errors::ClientError}; use atproto_identity::{config::default_env, resolve::{HickoryDnsResolver, InnerIdentityResolver, SharedIdentityResolver}, traits::DnsResolver}; +use atproto_lexicon::{resolve::{get_pds_from_did, pds_endpoint}, validation::syntax::validate_handle}; use clap::{Parser, crate_name, crate_version}; use crossterm::event::{self, KeyCode::{self}, KeyEventKind}; -use ratatui::{DefaultTerminal, Frame, layout::{Constraint, Layout, Position}, style::{Color, Stylize}, symbols, text::{Line, Text}, widgets::{Block, List, ListItem, Paragraph}}; +use ratatui::{DefaultTerminal, Frame, layout::{Constraint, Layout, Position}, style::{Color, Stylize}, symbols, text::{Line, Text}, widgets::{Block, Paragraph}}; #[derive(Parser, Debug)] #[command( @@ -14,7 +15,7 @@ use ratatui::{DefaultTerminal, Frame, layout::{Constraint, Layout, Position}, st )] struct Args { handle: String, - password: String, + app_password: String, } const MAX_INPUT_LINES: usize = 4; @@ -31,7 +32,7 @@ async fn main() -> anyhow::Result<()> { // Start the TUI let terminal = ratatui::init(); - App::new(client, dns_resolver, plc_hostname)?.run(terminal).await?; + App::new(client, dns_resolver, plc_hostname, args.handle, args.app_password).await?.run(terminal).await?; ratatui::restore(); Ok(()) @@ -80,24 +81,65 @@ struct ClientState { struct App { cs: ClientState, + http_client: atproto_client::reqwest::Client, identity_resolver: SharedIdentityResolver, record_resolver: HttpRecordResolver, + pds_endpoint: String, + auth: Auth, + session: AppPasswordSession, } impl App { - pub fn new(http_client: atproto_client::reqwest::Client, dns_resolver: Arc, plc_hostname: String) -> anyhow::Result { + pub async fn new(http_client: atproto_client::reqwest::Client, dns_resolver: Arc, plc_hostname: String, handle: String, app_password: String) -> anyhow::Result { + validate_handle(&handle)?; let identity_resolver = SharedIdentityResolver(Arc::new(InnerIdentityResolver { dns_resolver, http_client: http_client.clone(), plc_hostname, })); + let did_document = identity_resolver.resolve(&handle).await?; + let pds = pds_endpoint(&did_document)?; + let session = atproto_client::com::atproto::server::create_session( + &http_client, + &pds, + &handle, + &app_password, + None + ).await?; Ok(Self { cs: Default::default(), + http_client: http_client.clone(), identity_resolver: identity_resolver.clone(), record_resolver: HttpRecordResolver::new(http_client, identity_resolver.0), + pds_endpoint: pds, + auth: Auth::AppPassword(AppPasswordAuth { access_token: session.access_jwt.clone() }), + session, }) } + async fn auto_refresh Pin>>>>(&mut self, op: F) -> anyhow::Result { + match op().await { + Err(cause) => match cause.downcast_ref::() { + Some(e) => if let ClientError::HttpRequestFailed { url: _, error } = e { + if error.is_body() && error.to_string().contains("ExpiredToken") { + let response = refresh_session( + &self.http_client, + &self.pds_endpoint, + &self.session.refresh_jwt + ).await?; + self.session.access_jwt = response.access_jwt; + self.session.refresh_jwt = response.refresh_jwt; + op().await + } else { + Err(cause) + } + } else { Err(cause) }, + None => Err(cause), + }, + Ok(result) => Ok(result), + } + } + pub async fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { loop { terminal.draw(|frame| self.render(frame))?; @@ -353,6 +395,14 @@ impl App { } async fn disconnect(&mut self) -> anyhow::Result<()> { + let session_deletion = atproto_client::com::atproto::server::delete_session( + &self.http_client, + &self.pds_endpoint, + &self.session.refresh_jwt + ); + + // Finish everything + session_deletion.await?; Ok(()) } } -- 2.54.0