oauth2
This commit is contained in:
125
backend/src/routes/oauth.rs
Normal file
125
backend/src/routes/oauth.rs
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user