learn and share notes on atproto (wip) 馃 malfestio.stormlightlabs.org/
readability solid axum atproto srs
5
fork

Configure Feed

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

at main 144 lines 5.3 kB view raw
1use crate::state::SharedState; 2 3use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; 4use serde::{Deserialize, Serialize}; 5use serde_json::json; 6 7#[derive(Deserialize)] 8pub struct LoginRequest { 9 identifier: String, 10 password: String, 11} 12 13#[derive(Serialize)] 14pub struct LoginResponse { 15 access_jwt: String, 16 refresh_jwt: String, 17 did: String, 18 handle: String, 19} 20 21/// Login with app password. 22/// 23/// Resolves the user's PDS from their handle/DID, authenticates with that PDS, 24/// and stores the session for future requests. 25pub async fn login(State(state): State<SharedState>, Json(payload): Json<LoginRequest>) -> impl IntoResponse { 26 use crate::oauth::resolver::{is_valid_did, is_valid_handle}; 27 use crate::repository::oauth::StoreAppPasswordSessionRequest; 28 29 let client = reqwest::Client::new(); 30 31 let pds_url = if is_valid_did(&payload.identifier) { 32 match state.identity_resolver.resolve_did(&payload.identifier).await { 33 Ok(identity) => identity.pds_url, 34 Err(e) => { 35 tracing::error!("Failed to resolve DID {}: {}", payload.identifier, e); 36 return ( 37 StatusCode::BAD_REQUEST, 38 Json(json!({ "error": format!("Failed to resolve DID: {}", e) })), 39 ) 40 .into_response(); 41 } 42 } 43 } else if is_valid_handle(&payload.identifier) { 44 let did = match state.identity_resolver.resolve_handle(&payload.identifier).await { 45 Ok(did) => did, 46 Err(e) => { 47 tracing::error!("Failed to resolve handle {}: {}", payload.identifier, e); 48 return ( 49 StatusCode::BAD_REQUEST, 50 Json(json!({ "error": format!("Failed to resolve handle: {}", e) })), 51 ) 52 .into_response(); 53 } 54 }; 55 56 match state.identity_resolver.resolve_did(&did).await { 57 Ok(identity) => identity.pds_url, 58 Err(e) => { 59 tracing::error!("Failed to resolve DID {} for handle {}: {}", did, payload.identifier, e); 60 return ( 61 StatusCode::BAD_REQUEST, 62 Json(json!({ "error": format!("Failed to resolve DID: {}", e) })), 63 ) 64 .into_response(); 65 } 66 } 67 } else { 68 tracing::warn!("Invalid identifier format: {}, using default PDS", payload.identifier); 69 state.config.pds_url.clone() 70 }; 71 72 tracing::info!("Authenticating {} with PDS: {}", payload.identifier, pds_url); 73 74 let resp = client 75 .post(format!("{}/xrpc/com.atproto.server.createSession", pds_url)) 76 .json(&json!({ 77 "identifier": payload.identifier, 78 "password": payload.password 79 })) 80 .send() 81 .await; 82 83 match resp { 84 Ok(response) => { 85 if response.status().is_success() { 86 let body: serde_json::Value = response.json().await.unwrap_or_default(); 87 let access_jwt = body["accessJwt"].as_str().unwrap_or("").to_string(); 88 let refresh_jwt = body["refreshJwt"].as_str().unwrap_or("").to_string(); 89 let did = body["did"].as_str().unwrap_or("").to_string(); 90 let handle = body["handle"].as_str().unwrap_or("").to_string(); 91 92 // Store the session with the resolved PDS URL 93 let store_result = state 94 .oauth_repo 95 .store_app_password_session(StoreAppPasswordSessionRequest { 96 did: &did, 97 pds_url: &pds_url, 98 access_token: &access_jwt, 99 refresh_token: Some(&refresh_jwt), 100 expires_at: None, 101 }) 102 .await; 103 104 if let Err(e) = store_result { 105 tracing::error!("Failed to store app password session: {}", e); 106 } 107 108 ( 109 StatusCode::OK, 110 Json(json!({ 111 "accessJwt": access_jwt, 112 "refreshJwt": refresh_jwt, 113 "did": did, 114 "handle": handle 115 })), 116 ) 117 .into_response() 118 } else { 119 let error_body: serde_json::Value = response.json().await.unwrap_or_default(); 120 (StatusCode::UNAUTHORIZED, Json(error_body)).into_response() 121 } 122 } 123 Err(e) => ( 124 StatusCode::INTERNAL_SERVER_ERROR, 125 Json(json!({ "error": e.to_string() })), 126 ) 127 .into_response(), 128 } 129} 130 131pub async fn me(ctx: Option<axum::Extension<crate::middleware::auth::UserContext>>) -> impl IntoResponse { 132 match ctx { 133 Some(axum::Extension(user)) => ( 134 StatusCode::OK, 135 Json(json!({ 136 "status": "authenticated", 137 "did": user.did, 138 "handle": user.handle 139 })), 140 ) 141 .into_response(), 142 None => (StatusCode::UNAUTHORIZED, Json(json!({ "error": "Unauthorized" }))).into_response(), 143 } 144}