use serde::{Deserialize, Serialize}; use tower_sessions::Session; use axum::{ extract::Query, response::Html, Json, }; use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; use anyhow::Result; #[derive(Debug, Serialize, Deserialize)] pub struct OAuthData { pub did: String, pub handle: String, pub display_name: Option, pub avatar: Option, pub access_jwt: Option, pub refresh_jwt: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct OAuthCallback { pub code: Option, pub state: Option, pub error: Option, pub error_description: Option, pub iss: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub sub: String, // DID pub handle: String, pub display_name: Option, pub avatar: Option, pub exp: usize, pub iat: usize, } const _JWT_SECRET: &[u8] = b"ailog-oauth-secret-key-2025"; #[allow(dead_code)] pub fn create_jwt(oauth_data: &OAuthData) -> Result { let now = chrono::Utc::now().timestamp() as usize; let claims = Claims { sub: oauth_data.did.clone(), handle: oauth_data.handle.clone(), display_name: oauth_data.display_name.clone(), avatar: oauth_data.avatar.clone(), exp: now + 24 * 60 * 60, // 24 hours iat: now, }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(_JWT_SECRET), )?; Ok(token) } #[allow(dead_code)] pub fn verify_jwt(token: &str) -> Result { let token_data = decode::( token, &DecodingKey::from_secret(_JWT_SECRET), &Validation::new(Algorithm::HS256), )?; Ok(token_data.claims) } #[allow(dead_code)] pub async fn oauth_callback_handler( Query(params): Query, session: Session, ) -> Result, String> { println!("🔧 OAuth callback received: {:?}", params); if let Some(error) = params.error { let error_html = format!( r#" OAuth Error

❌ Authentication Failed

Error: {}

{}
"#, error, params.error_description.map(|d| format!("

Description: {}

", d)).unwrap_or_default() ); return Ok(Html(error_html)); } if let Some(code) = params.code { // In a real implementation, you would exchange the code for tokens here // For now, we'll create a mock session let oauth_data = OAuthData { did: format!("did:plc:example_{}", &code[..8]), handle: "user.bsky.social".to_string(), display_name: Some("OAuth User".to_string()), avatar: Some("https://via.placeholder.com/40x40/1185fe/ffffff?text=U".to_string()), access_jwt: None, refresh_jwt: None, }; // Create JWT let jwt_token = create_jwt(&oauth_data).map_err(|e| e.to_string())?; // Store in session session.insert("oauth_data", &oauth_data).await.map_err(|e| e.to_string())?; session.insert("jwt_token", &jwt_token).await.map_err(|e| e.to_string())?; println!("✅ OAuth session created for: {}", oauth_data.handle); let success_html = format!( r#" OAuth Success

✅ Authentication Successful

Handle: @{}

DID: {}

You can now close this window.

"#, oauth_data.handle, oauth_data.did, oauth_data.did, oauth_data.handle, oauth_data.display_name.as_deref().unwrap_or("User"), oauth_data.avatar.as_deref().unwrap_or(""), jwt_token ); return Ok(Html(success_html)); } Err("No authorization code received".to_string()) } #[allow(dead_code)] pub async fn oauth_session_handler(session: Session) -> Json { if let Ok(Some(oauth_data)) = session.get::("oauth_data").await { if let Ok(Some(jwt_token)) = session.get::("jwt_token").await { return Json(serde_json::json!({ "authenticated": true, "user": oauth_data, "jwt": jwt_token })); } } Json(serde_json::json!({ "authenticated": false })) } #[allow(dead_code)] pub async fn oauth_logout_handler(session: Session) -> Json { let _ = session.remove::("oauth_data").await; let _ = session.remove::("jwt_token").await; Json(serde_json::json!({ "success": true, "message": "Logged out successfully" })) }