Backend initial admin setup

This commit is contained in:
2026-05-09 17:33:14 +02:00
parent 99bcbaf3b0
commit 26ac32db21
2 changed files with 82 additions and 2 deletions

View File

@@ -304,6 +304,84 @@ pub async fn update_user(
Ok(Json(response)) Ok(Json(response))
} }
pub async fn check_admin_exists(
State(data): State<Arc<AppState>>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
let admin_count = sqlx::query_scalar::<_, i64>(r#"SELECT COUNT(*) FROM users WHERE is_admin = true"#)
.fetch_one(&data.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"status": "error", "message": format!("{:?}", e)})),
)
})?;
let has_admin = admin_count > 0;
Ok(Json(json!({"has_admin": has_admin})))
}
pub async fn setup_initial_admin(
State(data): State<Arc<AppState>>,
Json(request): Json<UserCreateScheme>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
// Check if any admin already exists
let admin_count = sqlx::query_scalar::<_, i64>(r#"SELECT COUNT(*) FROM users WHERE is_admin = true"#)
.fetch_one(&data.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"status": "error", "message": format!("{:?}", e)})),
)
})?;
if admin_count > 0 {
return Err((
StatusCode::BAD_REQUEST,
Json(json!({"status": "error", "message": "Admin user already exists"})),
));
}
if request.username.is_empty() || request.pwd.is_empty() {
return Err((
StatusCode::BAD_REQUEST,
Json(json!({"status": "error", "message": "Missing credential"})),
));
}
let argon = Argon2::default();
let salt = SaltString::generate(&mut OsRng);
let hashed_pwd = match argon.hash_password(request.pwd.clone().as_bytes(), &salt) {
Ok(h) => h.to_string(),
Err(e) => panic!("Error hashing {:}", e),
};
let user = sqlx::query("INSERT INTO users (username, pwd, first_name, last_name, is_admin) VALUES ($1, $2, $3, $4, $5)")
.bind(request.username)
.bind(&hashed_pwd)
.bind(request.first_name)
.bind(request.last_name)
.bind(true)
.execute(&data.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"status": "error", "message": format!("{}", e)})),
)
})?;
if user.rows_affected() < 1 {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"status": "error", "message": "Error creating admin user"})),
));
} else {
Ok(Json(json!({"status": "success", "result": "Admin user created"})))
}
}
pub fn filter_user(user: &User) -> FilteredUser { pub fn filter_user(user: &User) -> FilteredUser {
FilteredUser { FilteredUser {
id: user.id, id: user.id,

View File

@@ -10,8 +10,8 @@ use crate::{
cookie::validation::{validate_admin, validate_token}, cookie::validation::{validate_admin, validate_token},
handlers::{ handlers::{
auth::{ auth::{
create_user, delete_user, get_current_user, get_user_by_id, get_users, login, logout, check_admin_exists, create_user, delete_user, get_current_user, get_user_by_id, get_users, login, logout,
update_user, setup_initial_admin, update_user,
}, },
ticket::{create_ticket, delete_ticket, edit_ticket, get_ticket_by_id, get_tickets}, ticket::{create_ticket, delete_ticket, edit_ticket, get_ticket_by_id, get_tickets},
}, },
@@ -50,5 +50,7 @@ pub fn create_router(state: Arc<AppState>) -> Router {
Router::new() Router::new()
.merge(protected_routes) .merge(protected_routes)
.route("/api/login", post(login)) .route("/api/login", post(login))
.route("/api/check-admin", get(check_admin_exists))
.route("/api/setup-admin", post(setup_initial_admin))
.with_state(state) .with_state(state)
} }