This commit is contained in:
arrelin
2026-01-17 10:15:44 +03:00
parent 564adac629
commit a4b06fb057
26 changed files with 1542 additions and 346 deletions

125
backend/src/routes/oauth.rs Normal file
View File

@@ -0,0 +1,125 @@
use axum::{
extract::{Query, State},
http::StatusCode,
response::Redirect,
Json,
};
use axum_login::AuthSession;
use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize};
use tower_sessions::Session;
use utoipa::ToSchema;
use crate::auth::AuthBackend;
use crate::services::OAuthService;
const CSRF_TOKEN_KEY: &str = "oauth_csrf_token";
const FRONTEND_URL_KEY: &str = "oauth_frontend_url";
#[derive(Debug, Deserialize, ToSchema)]
pub struct GoogleAuthQuery {
pub redirect_url: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct GoogleCallbackQuery {
pub code: String,
pub state: String,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct OAuthUrlResponse {
pub url: String,
}
#[utoipa::path(
get,
path = "/auth/google",
tag = "auth",
params(
("redirect_url" = Option<String>, Query, description = "Frontend URL to redirect after auth")
),
responses(
(status = 200, description = "Returns Google OAuth URL", body = OAuthUrlResponse)
)
)]
pub async fn google_auth(
session: Session,
Query(query): Query<GoogleAuthQuery>,
) -> Result<Json<OAuthUrlResponse>, StatusCode> {
let oauth_service = OAuthService::new();
let (auth_url, csrf_token) = oauth_service.get_auth_url();
session
.insert(CSRF_TOKEN_KEY, csrf_token.secret().clone())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if let Some(redirect_url) = query.redirect_url {
session
.insert(FRONTEND_URL_KEY, redirect_url)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}
Ok(Json(OAuthUrlResponse { url: auth_url }))
}
#[utoipa::path(
get,
path = "/auth/google/callback",
tag = "auth",
responses(
(status = 302, description = "Redirects to frontend after successful auth"),
(status = 401, description = "Authentication failed")
)
)]
pub async fn google_callback(
mut auth_session: AuthSession<AuthBackend>,
session: Session,
State(db): State<DatabaseConnection>,
Query(query): Query<GoogleCallbackQuery>,
) -> Result<Redirect, StatusCode> {
let stored_csrf: Option<String> = session
.get(CSRF_TOKEN_KEY)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let frontend_url: Option<String> = session
.get(FRONTEND_URL_KEY)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
session.remove::<String>(CSRF_TOKEN_KEY).await.ok();
session.remove::<String>(FRONTEND_URL_KEY).await.ok();
if stored_csrf.as_deref() != Some(&query.state) {
return Err(StatusCode::UNAUTHORIZED);
}
let oauth_service = OAuthService::new();
let access_token = oauth_service
.exchange_code(query.code)
.await
.map_err(|_| StatusCode::UNAUTHORIZED)?;
let google_user = oauth_service
.get_user_info(&access_token)
.await
.map_err(|_| StatusCode::UNAUTHORIZED)?;
let user = oauth_service
.find_or_create_user(&db, google_user)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
auth_session
.login(&user)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let redirect_url = frontend_url.unwrap_or_else(|| "http://localhost:3000".to_string());
Ok(Redirect::temporary(&redirect_url))
}