this repo has no description

switch to app-password based login

Akshay 4ae73d9e 6250a916

Changed files
+89 -182
core
src
+89 -182
core/src/main.rs
··· 1 1 use std::sync::Arc; 2 + use tokio::sync::Mutex; 2 3 3 4 use atrium_api::{ 4 - client::AtpServiceClient, did_doc::DidDocument, types::string::AtIdentifier, 5 + agent::{AtpAgent, store::MemorySessionStore}, 6 + client::AtpServiceClient, 7 + did_doc::DidDocument, 8 + types::string::AtIdentifier, 5 9 xrpc::types::AuthorizationToken, 6 10 }; 7 11 use atrium_common::resolver::Resolver; ··· 29 33 let app_state = AppState::new(); 30 34 let service = Router::new() 31 35 .route("/", routing::get(index::get)) 32 - .route("/test-auth", routing::get(test_atproto::get)) 36 + // .route("/test-auth", routing::get(test_atproto::get)) 33 37 .route("/login", routing::get(login::get).post(login::post)) 34 - .route("/callback", routing::get(callback::get)) 38 + // .route("/callback", routing::get(callback::get)) 35 39 .layer(session_layer) 36 40 .with_state(app_state); 37 41 ··· 41 45 42 46 #[derive(Clone)] 43 47 struct AppState { 44 - inner: Arc<AppStateInner>, 48 + inner: Arc<Mutex<AppStateInner>>, 49 + did_resolver: Arc<CommonDidResolver<DefaultHttpClient>>, 50 + handle_resolver: Arc<AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>>, 45 51 } 46 52 47 53 impl AppState { 48 54 fn new() -> Self { 49 - Self { 50 - inner: Arc::new(AppStateInner::new()), 51 - } 52 - } 53 - } 54 - 55 - struct AppStateInner { 56 - did_resolver: CommonDidResolver<DefaultHttpClient>, 57 - handle_resolver: AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>, 58 - oauth_client: OAuthClient< 59 - atrium_oauth_client::store::state::MemoryStateStore, 60 - CommonDidResolver<DefaultHttpClient>, 61 - atrium_identity::handle::AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>, 62 - >, 63 - } 64 - 65 - impl AppStateInner { 66 - fn new() -> Self { 67 55 let client = Arc::new(DefaultHttpClient::default()); 68 - let did_resolver = Self::did_resolver(client.clone()); 69 - let handle_resolver = Self::handle_resolver(client.clone()); 70 - let config = OAuthClientConfig { 71 - client_metadata: AtprotoLocalhostClientMetadata { 72 - // TODO: change this 73 - redirect_uris: Some(vec![String::from("http://127.0.0.1:3000/callback")]), 74 - scopes: Some(vec![ 75 - Scope::Known(KnownScope::Atproto), 76 - Scope::Known(KnownScope::TransitionGeneric), 77 - ]), 78 - }, 79 - keys: None, 80 - resolver: OAuthResolverConfig { 81 - did_resolver: Self::did_resolver(client.clone()), 82 - handle_resolver: Self::handle_resolver(client.clone()), 83 - authorization_server_metadata: Default::default(), 84 - protected_resource_metadata: Default::default(), 85 - }, 86 - state_store: MemoryStateStore::default(), 87 - }; 88 - let oauth_client = OAuthClient::new(config).unwrap(); 89 56 Self { 90 - oauth_client, 91 - did_resolver, 92 - handle_resolver, 57 + inner: Arc::new(Mutex::new(AppStateInner::new(client.clone()))), 58 + did_resolver: Arc::new(did_resolver(client.clone())), 59 + handle_resolver: Arc::new(handle_resolver(client.clone())), 93 60 } 94 61 } 95 62 96 - fn did_resolver<H: HttpClient>(http_client: Arc<H>) -> CommonDidResolver<H> { 97 - CommonDidResolver::new(CommonDidResolverConfig { 98 - plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(), 99 - http_client, 100 - }) 101 - } 102 - 103 - fn handle_resolver<H: HttpClient>( 104 - http_client: Arc<H>, 105 - ) -> AtprotoHandleResolver<HickoryDnsTxtResolver, H> { 106 - AtprotoHandleResolver::new(AtprotoHandleResolverConfig { 107 - dns_txt_resolver: HickoryDnsTxtResolver::default(), 108 - http_client, 109 - }) 110 - } 111 - 112 63 async fn resolve_did_document( 113 64 &self, 114 65 ident: &AtIdentifier, ··· 123 74 } 124 75 } 125 76 77 + fn did_resolver<H: HttpClient>(http_client: Arc<H>) -> CommonDidResolver<H> { 78 + CommonDidResolver::new(CommonDidResolverConfig { 79 + plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(), 80 + http_client, 81 + }) 82 + } 83 + 84 + fn handle_resolver<H: HttpClient>( 85 + http_client: Arc<H>, 86 + ) -> AtprotoHandleResolver<HickoryDnsTxtResolver, H> { 87 + AtprotoHandleResolver::new(AtprotoHandleResolverConfig { 88 + dns_txt_resolver: HickoryDnsTxtResolver::default(), 89 + http_client, 90 + }) 91 + } 92 + 93 + struct AppStateInner { 94 + agent: Option<AtpAgent<MemorySessionStore, IsahcClient>>, 95 + } 96 + 97 + impl AppStateInner { 98 + fn new(http_client: Arc<DefaultHttpClient>) -> Self { 99 + // let config = OAuthClientConfig { 100 + // client_metadata: AtprotoLocalhostClientMetadata { 101 + // // TODO: change this 102 + // redirect_uris: Some(vec![String::from("http://127.0.0.1:3000/callback")]), 103 + // scopes: Some(vec![ 104 + // Scope::Known(KnownScope::Atproto), 105 + // Scope::Known(KnownScope::TransitionGeneric), 106 + // ]), 107 + // }, 108 + // keys: None, 109 + // resolver: OAuthResolverConfig { 110 + // did_resolver: did_resolver(http_client.clone()), 111 + // handle_resolver: handle_resolver(http_client.clone()), 112 + // authorization_server_metadata: Default::default(), 113 + // protected_resource_metadata: Default::default(), 114 + // }, 115 + // state_store: MemoryStateStore::default(), 116 + // }; 117 + // let oauth_client = OAuthClient::new(config).unwrap(); 118 + Self { agent: None } 119 + } 120 + } 121 + 126 122 mod login { 127 123 use axum::{ 128 124 extract::{Form, State}, 129 - response::{IntoResponse, Redirect, Result}, 125 + http::StatusCode, 126 + response::IntoResponse, 130 127 }; 131 128 use serde::Deserialize; 132 129 ··· 139 136 #[derive(Deserialize, Debug)] 140 137 pub struct Req { 141 138 handle: AtIdentifier, 139 + app_password: String, 142 140 } 143 141 144 142 pub async fn post( 145 143 State(state): State<AppState>, 144 + session: tower_sessions::Session, 146 145 Form(req): Form<Req>, 147 - ) -> Result<impl IntoResponse> { 148 - let did_document = state.inner.resolve_did_document(&req.handle).await.unwrap(); 149 - dbg!(&did_document); 150 - let res = state 151 - .inner 152 - .oauth_client 153 - .authorize(did_document.get_pds_endpoint().unwrap(), AuthorizeOptions { 154 - scopes: vec![ 155 - Scope::Known(KnownScope::Atproto), 156 - Scope::Known(KnownScope::TransitionGeneric), 157 - ], 158 - prompt: Some(AuthorizeOptionPrompt::Login), 159 - ..Default::default() 160 - }) 161 - .await 162 - .unwrap(); 163 - Ok(Redirect::to(&res)) 164 - } 165 - } 166 - 167 - mod callback { 168 - use axum::{ 169 - extract::{Query, State}, 170 - http::StatusCode, 171 - }; 172 - 173 - use super::*; 174 - 175 - pub async fn get( 176 - Query(params): Query<atrium_oauth_client::CallbackParams>, 177 - State(state): State<AppState>, 178 - session: tower_sessions::Session, 179 - ) -> StatusCode { 180 - let ts = state.inner.oauth_client.callback(params).await.unwrap(); 181 - let _ = session.insert("bild", &ts).await; 146 + ) -> impl IntoResponse { 147 + let did_document = state.resolve_did_document(&req.handle).await.unwrap(); 148 + let agent = AtpAgent::new( 149 + IsahcClient::new(did_document.get_pds_endpoint().unwrap()), 150 + MemorySessionStore::default(), 151 + ); 152 + let res = agent.login(req.handle, req.app_password).await.unwrap(); 153 + println!("logged in as {:?} ({:?})", res.handle, res.did); 154 + session.insert("at_session", res).await.unwrap(); 155 + println!("stored session"); 182 156 StatusCode::OK 183 157 } 184 158 } 185 159 186 160 // dummy endpoint to test if sessions are working 187 161 mod index { 188 - use axum::http::StatusCode; 189 - 190 - pub async fn get(session: tower_sessions::Session) -> StatusCode { 191 - dbg!( 192 - session 193 - .get::<atrium_oauth_client::TokenSet>("bild") 194 - .await 195 - .unwrap() 196 - ); 197 - StatusCode::OK 198 - } 199 - } 200 - 201 - struct AuthenticatedClient { 202 - token: String, 203 - base_uri: String, 204 - inner: IsahcClient, 205 - } 206 - 207 - impl atrium_xrpc::HttpClient for AuthenticatedClient { 208 - async fn send_http( 209 - &self, 210 - request: atrium_xrpc::http::Request<Vec<u8>>, 211 - ) -> Result< 212 - atrium_xrpc::http::Response<Vec<u8>>, 213 - Box<dyn std::error::Error + Send + Sync + 'static>, 214 - > { 215 - self.inner.send_http(request).await 216 - } 217 - } 218 - 219 - impl atrium_xrpc::XrpcClient for AuthenticatedClient { 220 - fn base_uri(&self) -> String { 221 - self.base_uri.clone() 222 - } 223 - async fn authorization_token(&self, _: bool) -> Option<AuthorizationToken> { 224 - Some(AuthorizationToken::Dpop(self.token.clone())) 225 - } 226 - } 227 - 228 - // dummy endpoint to perform an atproto request with access token 229 - mod test_atproto { 230 - use atrium_api::{ 231 - agent::{AtpAgent, store::MemorySessionStore}, 232 - app, com, 233 - }; 234 - use axum::http::StatusCode; 235 - 236 162 use super::*; 237 163 238 - fn get_session_client( 239 - tokenset: &atrium_oauth_client::TokenSet, 240 - ) -> AtpServiceClient<AuthenticatedClient> { 241 - //) -> AtpAgent<MemorySessionStore, AuthenticatedClient> { 242 - // AtpAgent::new( 243 - // AuthenticatedClient { 244 - // token: tokenset.access_token.clone(), 245 - // base_uri: tokenset.iss.clone(), 246 - // inner: IsahcClient::new(tokenset.iss.clone()), 247 - // }, 248 - // MemorySessionStore::default(), 249 - // ) 250 - AtpServiceClient::new(AuthenticatedClient { 251 - token: tokenset.access_token.clone(), 252 - base_uri: tokenset.aud.clone(), 253 - inner: IsahcClient::new(tokenset.aud.clone()), 254 - }) 255 - } 256 - 257 - pub async fn get(session: tower_sessions::Session) -> StatusCode { 258 - let token_set = session 259 - .get::<atrium_oauth_client::TokenSet>("bild") 164 + pub async fn get(session: tower_sessions::Session) -> &'static str { 165 + match session 166 + .get::<atrium_api::agent::Session>("at_session") 260 167 .await 261 - .ok() 262 - .flatten() 263 - .unwrap(); 264 - let client = get_session_client(&token_set); 265 - let res = client 266 - .service 267 - .com 268 - .atproto 269 - .repo 270 - .upload_blob(vec![0]) 271 - .await 272 - .unwrap(); 273 - dbg!(res); 274 - StatusCode::OK 168 + .unwrap() 169 + { 170 + None => "no session", 171 + Some(s) => { 172 + // let did_doc = s.did_doc.unwrap(); 173 + let agent = AtpAgent::new( 174 + IsahcClient::new("https://bsky.social"), 175 + MemorySessionStore::default(), 176 + ); 177 + println!("resuming session of {:?} ({:?})", s.handle, s.did); 178 + agent.resume_session(s).await.unwrap(); 179 + "resuming session" 180 + } 181 + } 275 182 } 276 183 } 277 184