use axum::{ extract::State, response::Json, routing::post, Router, }; use validator::Validate; use crate::{ auth::AtprotoAuthService, error::{AppError, AppResult}, models::*, AppState, }; pub fn create_routes() -> Router { Router::new() .route("/login", post(login)) .route("/verify", post(verify_token)) } /// Authenticate user with atproto credentials async fn login( State(state): State, Json(request): Json, ) -> AppResult> { // Validate request request.validate().map_err(|e| AppError::validation(e.to_string()))?; // Create auth service let auth_service = AtprotoAuthService::new(&state.settings.secret_key); // Authenticate user let user = auth_service .authenticate(&request.identifier, &request.password) .await?; // Create access token let access_token = auth_service .create_access_token(&user, state.settings.access_token_expire_minutes)?; // Create or update user in database let _db_user = create_or_update_user(&state, &user.did, &user.handle).await?; Ok(Json(LoginResponse { access_token, token_type: "Bearer".to_string(), expires_in: state.settings.access_token_expire_minutes * 60, // Convert to seconds user: UserInfo { did: user.did, handle: user.handle, }, })) } /// Verify JWT token async fn verify_token( State(state): State, Json(token): Json, ) -> AppResult> { let token_str = token["token"] .as_str() .ok_or_else(|| AppError::validation("Token is required"))?; let auth_service = AtprotoAuthService::new(&state.settings.secret_key); let claims = auth_service.verify_access_token(token_str)?; Ok(Json(serde_json::json!({ "valid": true, "did": claims.did, "handle": claims.handle, "exp": claims.exp }))) } /// Create or update user in database async fn create_or_update_user( state: &AppState, did: &str, handle: &str, ) -> AppResult { let now = chrono::Utc::now(); // Try to get existing user let existing_user = match &state.db { crate::database::Database::Postgres(pool) => { sqlx::query_as::<_, User>("SELECT * FROM users WHERE did = $1") .bind(did) .fetch_optional(pool) .await .map_err(AppError::Database)? } crate::database::Database::Sqlite(pool) => { sqlx::query_as::<_, User>("SELECT * FROM users WHERE did = ?") .bind(did) .fetch_optional(pool) .await .map_err(AppError::Database)? } }; if let Some(mut user) = existing_user { // Update handle if changed if user.handle != handle { user = match &state.db { crate::database::Database::Postgres(pool) => { sqlx::query_as::<_, User>( "UPDATE users SET handle = $1, updated_at = $2 WHERE did = $3 RETURNING *" ) .bind(handle) .bind(now) .bind(did) .fetch_one(pool) .await .map_err(AppError::Database)? } crate::database::Database::Sqlite(pool) => { sqlx::query_as::<_, User>( "UPDATE users SET handle = ?, updated_at = ? WHERE did = ? RETURNING *" ) .bind(handle) .bind(now) .bind(did) .fetch_one(pool) .await .map_err(AppError::Database)? } }; } Ok(user) } else { // Create new user let user = match &state.db { crate::database::Database::Postgres(pool) => { sqlx::query_as::<_, User>( "INSERT INTO users (did, handle, created_at, updated_at) VALUES ($1, $2, $3, $4) RETURNING *" ) .bind(did) .bind(handle) .bind(now) .bind(now) .fetch_one(pool) .await .map_err(AppError::Database)? } crate::database::Database::Sqlite(pool) => { sqlx::query_as::<_, User>( "INSERT INTO users (did, handle, created_at, updated_at) VALUES (?, ?, ?, ?) RETURNING *" ) .bind(did) .bind(handle) .bind(now) .bind(now) .fetch_one(pool) .await .map_err(AppError::Database)? } }; Ok(user) } }