nxtgauge-backend-rust/apps/employees/src/handlers/auth.rs
Ashwin Kumar 7928e21a21 fix: resolve all compilation warnings and errors across services
- Remove duplicate departments/designations/employees handlers from users service (already in employees service)
- Fix all 9 profession admin handlers to use correct DB schema (display_name, bio, location, custom_data)
- Fix companies admin handler to match CompanyProfile DB model with all fields
- Fix customers admin handler to match Requirement model with preferred_date
- Fix missing serde_json imports and type annotations in admin handlers
- Add #[allow(dead_code)] for intentionally unused structs/fields
- Add test infrastructure: auth crypto tests (2 passing), test directory structure
- Zero compilation warnings across all services
2026-04-07 12:52:55 +02:00

128 lines
4.1 KiB
Rust

use auth::{
crypto::{verify_password},
jwt::generate_tokens,
};
use axum::{
extract::State,
http::{header::SET_COOKIE, StatusCode},
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use db::models::employee::EmployeeRepository;
use serde::{Deserialize, Serialize};
use contracts::auth_middleware::AuthUser;
use crate::AppState;
pub fn router() -> Router<AppState> {
Router::new()
.route("/login", post(login))
.route("/logout", post(logout))
.route("/session", get(session))
}
#[derive(Deserialize)]
pub struct LoginPayload {
pub email: String,
pub password: String,
}
#[derive(Serialize)]
#[allow(dead_code)]
pub struct SessionEmployee {
pub id: String,
pub email: String,
pub full_name: String,
pub role_code: String,
}
#[derive(Serialize)]
pub struct ErrorResponse {
pub error: String,
pub code: String,
}
fn err(status: StatusCode, msg: &str, code: &str) -> (StatusCode, Json<ErrorResponse>) {
(status, Json(ErrorResponse {
error: msg.to_string(),
code: code.to_string(),
}))
}
async fn login(
State(state): State<AppState>,
Json(payload): Json<LoginPayload>,
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
let email = payload.email.to_lowercase();
let employee = EmployeeRepository::get_by_email(&state.pool, &email)
.await
.map_err(|_| err(StatusCode::INTERNAL_SERVER_ERROR, "DB error", "DB_ERROR"))?
.ok_or_else(|| err(StatusCode::UNAUTHORIZED, "Invalid credentials", "INVALID_CREDENTIALS"))?;
if employee.status != "ACTIVE" {
return Err(err(StatusCode::FORBIDDEN, "Account not active", "ACCOUNT_INACTIVE"));
}
let is_valid = verify_password(&payload.password, &employee.password_hash)
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "INTERNAL_ERROR"))?;
if !is_valid {
return Err(err(StatusCode::UNAUTHORIZED, "Invalid credentials", "INVALID_CREDENTIALS"));
}
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
// Internal Staff Roles
let roles = vec![employee.role_code.clone()];
let tokens = generate_tokens(
employee.id.to_string(),
employee.email.clone(),
roles.clone(),
roles.first().cloned(),
&jwt_secret,
)
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "TOKEN_ERROR"))?;
// Store session
EmployeeRepository::store_session(&state.pool, employee.id, &tokens.refresh_token, chrono::Utc::now() + chrono::Duration::days(30))
.await
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "DB_ERROR"))?;
let cookie = format!(
"nxtgauge_admin_token={}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=2592000",
tokens.refresh_token
);
Ok((StatusCode::OK, [(SET_COOKIE, cookie)], Json(serde_json::json!({
"access_token": tokens.access_token,
"token_type": "Bearer",
"user": {
"id": employee.id.to_string(),
"email": employee.email,
"full_name": format!("{} {}", employee.first_name, employee.last_name),
"role_code": employee.role_code,
}
}))))
}
async fn logout(
_req: axum::http::Request<axum::body::Body>,
) -> impl IntoResponse {
let clear = "nxtgauge_admin_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0";
(StatusCode::OK, [(SET_COOKIE, clear)], Json(serde_json::json!({ "message": "Logged out" })))
}
async fn session(
auth: AuthUser,
_state: State<AppState>,
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
// For now, allow simple return from AuthUser if it matches internal structure
// In future, fetch from employee repository for fresh data
Ok(Json(serde_json::json!({
"id": auth.user_id.to_string(),
"email": auth.email,
"role_code": auth.claims.active_role,
})))
}