Files
log/src/oauth.rs
syui eb5aa0a2be
Some checks failed
Deploy ailog / build-and-deploy (push) Failing after 14m42s
fix cargo
2025-06-11 18:27:58 +09:00

210 lines
6.7 KiB
Rust

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<String>,
pub avatar: Option<String>,
pub access_jwt: Option<String>,
pub refresh_jwt: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OAuthCallback {
pub code: Option<String>,
pub state: Option<String>,
pub error: Option<String>,
pub error_description: Option<String>,
pub iss: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String, // DID
pub handle: String,
pub display_name: Option<String>,
pub avatar: Option<String>,
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<String> {
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<Claims> {
let token_data = decode::<Claims>(
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<OAuthCallback>,
session: Session,
) -> Result<Html<String>, String> {
println!("🔧 OAuth callback received: {:?}", params);
if let Some(error) = params.error {
let error_html = format!(
r#"
<!DOCTYPE html>
<html>
<head>
<title>OAuth Error</title>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, sans-serif; text-align: center; padding: 50px; }}
.error {{ background: #f8d7da; color: #721c24; padding: 20px; border-radius: 8px; }}
</style>
</head>
<body>
<div class="error">
<h2>❌ Authentication Failed</h2>
<p><strong>Error:</strong> {}</p>
{}
<button onclick="window.close()">Close Window</button>
</div>
</body>
</html>
"#,
error,
params.error_description.map(|d| format!("<p><strong>Description:</strong> {}</p>", 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#"
<!DOCTYPE html>
<html>
<head>
<title>OAuth Success</title>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, sans-serif; text-align: center; padding: 50px; }}
.success {{ background: #d1edff; color: #0c5460; padding: 20px; border-radius: 8px; }}
</style>
</head>
<body>
<div class="success">
<h2>✅ Authentication Successful</h2>
<p><strong>Handle:</strong> @{}</p>
<p><strong>DID:</strong> {}</p>
<p>You can now close this window.</p>
</div>
<script>
// Send success message to parent window
if (window.opener && !window.opener.closed) {{
window.opener.postMessage({{
type: 'oauth_success',
session: {{
authenticated: true,
did: '{}',
handle: '{}',
displayName: '{}',
avatar: '{}',
jwt: '{}'
}}
}}, window.location.origin);
setTimeout(() => window.close(), 2000);
}}
</script>
</body>
</html>
"#,
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<serde_json::Value> {
if let Ok(Some(oauth_data)) = session.get::<OAuthData>("oauth_data").await {
if let Ok(Some(jwt_token)) = session.get::<String>("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<serde_json::Value> {
let _ = session.remove::<OAuthData>("oauth_data").await;
let _ = session.remove::<String>("jwt_token").await;
Json(serde_json::json!({
"success": true,
"message": "Logged out successfully"
}))
}