1
0
Files
card/src/handlers/auth.rs
2025-06-09 03:58:38 +09:00

161 lines
4.9 KiB
Rust

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<AppState> {
Router::new()
.route("/login", post(login))
.route("/verify", post(verify_token))
}
/// Authenticate user with atproto credentials
async fn login(
State(state): State<AppState>,
Json(request): Json<LoginRequest>,
) -> AppResult<Json<LoginResponse>> {
// 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<AppState>,
Json(token): Json<serde_json::Value>,
) -> AppResult<Json<serde_json::Value>> {
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<User> {
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)
}
}