190 lines
4.9 KiB
Rust
190 lines
4.9 KiB
Rust
use axum::{
|
|
extract::State,
|
|
http::StatusCode,
|
|
Json,
|
|
};
|
|
use axum_login::AuthSession;
|
|
use sea_orm::{DatabaseConnection, EntityTrait, ColumnTrait, QueryFilter, ActiveModelTrait, Set};
|
|
use serde::{Deserialize, Serialize};
|
|
use utoipa::ToSchema;
|
|
|
|
use crate::auth::{AuthBackend, Credentials};
|
|
use crate::models::{user, User, family, Family};
|
|
use crate::services::FamilyService;
|
|
|
|
#[derive(Debug, Deserialize, ToSchema)]
|
|
pub struct LoginRequest {
|
|
pub username: String,
|
|
pub password: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, ToSchema)]
|
|
pub struct LoginResponse {
|
|
pub success: bool,
|
|
pub is_admin: bool,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, ToSchema)]
|
|
pub struct MeResponse {
|
|
pub id: i32,
|
|
pub username: Option<String>,
|
|
pub email: Option<String>,
|
|
pub is_admin: bool,
|
|
pub family_id: Option<i32>,
|
|
}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/login",
|
|
tag = "auth",
|
|
request_body = LoginRequest,
|
|
responses(
|
|
(status = 200, description = "Login successful", body = LoginResponse),
|
|
(status = 401, description = "Invalid credentials")
|
|
)
|
|
)]
|
|
pub async fn login(
|
|
mut auth_session: AuthSession<AuthBackend>,
|
|
Json(payload): Json<LoginRequest>,
|
|
) -> Result<Json<LoginResponse>, StatusCode> {
|
|
let user = auth_session
|
|
.authenticate(Credentials {
|
|
username: payload.username,
|
|
password: payload.password,
|
|
})
|
|
.await
|
|
.map_err(|_| StatusCode::UNAUTHORIZED)?
|
|
.ok_or(StatusCode::UNAUTHORIZED)?;
|
|
|
|
auth_session
|
|
.login(&user)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
Ok(Json(LoginResponse {
|
|
success: true,
|
|
is_admin: user.is_admin,
|
|
}))
|
|
}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/logout",
|
|
tag = "auth",
|
|
responses(
|
|
(status = 200, description = "Logout successful")
|
|
)
|
|
)]
|
|
pub async fn logout(
|
|
mut auth_session: AuthSession<AuthBackend>,
|
|
) -> Result<StatusCode, StatusCode> {
|
|
auth_session
|
|
.logout()
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
Ok(StatusCode::OK)
|
|
}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/me",
|
|
tag = "auth",
|
|
responses(
|
|
(status = 200, description = "Current user info", body = MeResponse),
|
|
(status = 401, description = "Not authenticated")
|
|
)
|
|
)]
|
|
pub async fn me(
|
|
auth_session: AuthSession<AuthBackend>,
|
|
) -> Result<Json<MeResponse>, StatusCode> {
|
|
let user = auth_session.user.ok_or(StatusCode::UNAUTHORIZED)?;
|
|
|
|
Ok(Json(MeResponse {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
is_admin: user.is_admin,
|
|
family_id: user.family_id,
|
|
}))
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, ToSchema)]
|
|
pub struct FamilyLoginRequest {
|
|
pub family_name: String,
|
|
pub password: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, ToSchema)]
|
|
pub struct FamilyLoginResponse {
|
|
pub success: bool,
|
|
pub family_id: i32,
|
|
}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/auth/family-login",
|
|
tag = "auth",
|
|
request_body = FamilyLoginRequest,
|
|
responses(
|
|
(status = 200, description = "Login successful", body = FamilyLoginResponse),
|
|
(status = 401, description = "Invalid credentials"),
|
|
(status = 404, description = "Family not found")
|
|
)
|
|
)]
|
|
pub async fn family_login(
|
|
mut auth_session: AuthSession<AuthBackend>,
|
|
State(db): State<DatabaseConnection>,
|
|
Json(payload): Json<FamilyLoginRequest>,
|
|
) -> Result<Json<FamilyLoginResponse>, StatusCode> {
|
|
let family = Family::find()
|
|
.filter(family::Column::Name.eq(&payload.family_name))
|
|
.one(&db)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
|
.ok_or(StatusCode::NOT_FOUND)?;
|
|
|
|
let valid = FamilyService::verify_password(&db, family.id, payload.password.clone())
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
if !valid {
|
|
return Err(StatusCode::UNAUTHORIZED);
|
|
}
|
|
|
|
let existing_member = User::find()
|
|
.filter(user::Column::FamilyId.eq(family.id))
|
|
.filter(user::Column::GoogleId.is_null())
|
|
.filter(user::Column::Username.eq(&payload.family_name))
|
|
.one(&db)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
let member_user = if let Some(user) = existing_member {
|
|
user
|
|
} else {
|
|
let new_member = user::ActiveModel {
|
|
username: Set(Some(payload.family_name)),
|
|
email: Set(None),
|
|
google_id: Set(None),
|
|
password_hash: Set(None),
|
|
is_admin: Set(false),
|
|
family_id: Set(Some(family.id)),
|
|
..Default::default()
|
|
};
|
|
new_member.insert(&db)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
|
};
|
|
|
|
auth_session
|
|
.login(&member_user)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
Ok(Json(FamilyLoginResponse {
|
|
success: true,
|
|
family_id: family.id,
|
|
}))
|
|
}
|