commit
9ae6d88c21
24 changed files with 7589 additions and 0 deletions
119
src/handlers/auth.rs
Normal file
119
src/handlers/auth.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use crate::auth::AppState;
|
||||
use crate::entities::user;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
};
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AuthCallback {
|
||||
pub code: String,
|
||||
#[allow(dead_code)]
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
pub async fn login(State(state): State<AppState>) -> Response {
|
||||
let oidc = state.oidc;
|
||||
let (auth_url, _csrf_token, _nonce) = oidc.auth_url();
|
||||
Redirect::to(&auth_url).into_response()
|
||||
}
|
||||
|
||||
pub async fn callback(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<AuthCallback>,
|
||||
) -> Response {
|
||||
let oidc = state.oidc;
|
||||
let db = state.db;
|
||||
|
||||
match oidc.exchange_code(params.code).await {
|
||||
Ok(tokens) => match oidc.validate_token(&tokens.id_token).await {
|
||||
Ok(claims) => {
|
||||
let sub = claims.sub;
|
||||
let email = claims.email.unwrap_or_default();
|
||||
let name = claims.name.unwrap_or_default();
|
||||
|
||||
let existing_user = user::Entity::find()
|
||||
.filter(user::Column::Sub.eq(&sub))
|
||||
.one(&db)
|
||||
.await
|
||||
.unwrap_or(None);
|
||||
|
||||
if existing_user.is_none() {
|
||||
let new_user = user::ActiveModel {
|
||||
sub: Set(sub),
|
||||
email: Set(email),
|
||||
name: Set(name),
|
||||
..Default::default()
|
||||
};
|
||||
new_user.insert(&db).await.unwrap();
|
||||
};
|
||||
|
||||
let mut redirect_url = format!("/#access_token={}", tokens.id_token);
|
||||
if let Some(refresh_token) = tokens.refresh_token {
|
||||
redirect_url.push_str(&format!("&refresh_token={}", refresh_token));
|
||||
}
|
||||
|
||||
Redirect::to(&redirect_url).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Token validation error: {:?}", e);
|
||||
(
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Invalid token",
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("OIDC callback error: {:?}", e);
|
||||
(
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Authentication failed",
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn refresh(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<RefreshRequest>,
|
||||
) -> Response {
|
||||
let oidc = state.oidc;
|
||||
|
||||
match oidc.refresh_token(payload.refresh_token).await {
|
||||
Ok(tokens) => Json(tokens).into_response(),
|
||||
Err(e) => {
|
||||
tracing::error!("Token refresh error: {:?}", e);
|
||||
(axum::http::StatusCode::UNAUTHORIZED, "Refresh failed").into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RefreshRequest {
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
pub async fn logout() -> Response {
|
||||
Redirect::to("/").into_response()
|
||||
}
|
||||
|
||||
pub async fn me(
|
||||
crate::auth::CurrentUser {
|
||||
id,
|
||||
sub,
|
||||
email,
|
||||
name,
|
||||
}: crate::auth::CurrentUser,
|
||||
) -> Json<crate::auth::CurrentUser> {
|
||||
Json(crate::auth::CurrentUser {
|
||||
id,
|
||||
sub,
|
||||
email,
|
||||
name,
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue