feat: implement real admin authentication and password management
- Added 'users' table to database with bcrypt hashing. - Refactored login to verify against the database. - Implemented 'Security' section in settings to allow changing the admin password. - Initialized system with default user 'admin' and password 'admin'.
This commit is contained in:
@@ -63,6 +63,7 @@ pub fn router(state: AppState) -> Router {
|
||||
// API endpoints
|
||||
.route("/api/auth/login", post(handle_login))
|
||||
.route("/api/auth/status", get(handle_auth_status))
|
||||
.route("/api/auth/change-password", post(handle_change_password))
|
||||
.route("/api/usage/summary", get(handle_usage_summary))
|
||||
.route("/api/usage/time-series", get(handle_time_series))
|
||||
.route("/api/usage/clients", get(handle_clients_usage))
|
||||
@@ -146,17 +147,45 @@ async fn handle_websocket_message(text: &str, state: &DashboardState) {
|
||||
}
|
||||
|
||||
// Authentication handlers
|
||||
async fn handle_login(State(_state): State<DashboardState>) -> Json<ApiResponse<serde_json::Value>> {
|
||||
// Simple authentication for demo
|
||||
// In production, this would validate credentials against a database
|
||||
Json(ApiResponse::success(serde_json::json!({
|
||||
"token": "demo-token-123456",
|
||||
"user": {
|
||||
"username": "admin",
|
||||
"name": "Administrator",
|
||||
"role": "Super Admin"
|
||||
#[derive(Deserialize)]
|
||||
struct LoginRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
async fn handle_login(
|
||||
State(state): State<DashboardState>,
|
||||
Json(payload): Json<LoginRequest>,
|
||||
) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let pool = &state.app_state.db_pool;
|
||||
|
||||
let user_result = sqlx::query("SELECT username, password_hash, role FROM users WHERE username = ?")
|
||||
.bind(&payload.username)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
|
||||
match user_result {
|
||||
Ok(Some(row)) => {
|
||||
let hash = row.get::<String, _>("password_hash");
|
||||
if bcrypt::verify(&payload.password, &hash).unwrap_or(false) {
|
||||
Json(ApiResponse::success(serde_json::json!({
|
||||
"token": format!("session-{}", uuid::Uuid::new_v4()),
|
||||
"user": {
|
||||
"username": row.get::<String, _>("username"),
|
||||
"name": "Administrator",
|
||||
"role": row.get::<String, _>("role")
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
Json(ApiResponse::error("Invalid username or password".to_string()))
|
||||
}
|
||||
}
|
||||
})))
|
||||
Ok(None) => Json(ApiResponse::error("Invalid username or password".to_string())),
|
||||
Err(e) => {
|
||||
warn!("Database error during login: {}", e);
|
||||
Json(ApiResponse::error("Login failed due to system error".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_auth_status(State(_state): State<DashboardState>) -> Json<ApiResponse<serde_json::Value>> {
|
||||
@@ -170,6 +199,49 @@ async fn handle_auth_status(State(_state): State<DashboardState>) -> Json<ApiRes
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ChangePasswordRequest {
|
||||
current_password: String,
|
||||
new_password: String,
|
||||
}
|
||||
|
||||
async fn handle_change_password(
|
||||
State(state): State<DashboardState>,
|
||||
Json(payload): Json<ChangePasswordRequest>,
|
||||
) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let pool = &state.app_state.db_pool;
|
||||
|
||||
// For now, always change 'admin' user
|
||||
let user_result = sqlx::query("SELECT password_hash FROM users WHERE username = 'admin'")
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
|
||||
match user_result {
|
||||
Ok(row) => {
|
||||
let hash = row.get::<String, _>("password_hash");
|
||||
if bcrypt::verify(&payload.current_password, &hash).unwrap_or(false) {
|
||||
let new_hash = match bcrypt::hash(&payload.new_password, 12) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Json(ApiResponse::error("Failed to hash new password".to_string())),
|
||||
};
|
||||
|
||||
let update_result = sqlx::query("UPDATE users SET password_hash = ? WHERE username = 'admin'")
|
||||
.bind(new_hash)
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
match update_result {
|
||||
Ok(_) => Json(ApiResponse::success(serde_json::json!({ "message": "Password updated successfully" }))),
|
||||
Err(e) => Json(ApiResponse::error(format!("Failed to update database: {}", e))),
|
||||
}
|
||||
} else {
|
||||
Json(ApiResponse::error("Current password incorrect".to_string()))
|
||||
}
|
||||
}
|
||||
Err(e) => Json(ApiResponse::error(format!("User not found: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
// Usage handlers
|
||||
async fn handle_usage_summary(State(state): State<DashboardState>) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let pool = &state.app_state.db_pool;
|
||||
|
||||
Reference in New Issue
Block a user