feat: Add admin authentication for Settings panel
- Add sftpgo_admins table to auth.sqlite (synced from PostgreSQL admins) - Add PgAdmin struct + sync_admins() method in sync.rs - Add fetch_admins() method in pg_client.rs - Add AdminLoginRequest/Response + admin_login() + verify_admin_token() in auth.rs - Add POST /api/v2/admin/login + GET /api/v2/admin/verify endpoints in server.rs - Add AdminLoginModal UI with password input + localStorage token in page.html - Test password: admin123 (bcrypt hash updated in PostgreSQL admins table) Architecture: - Independent admin auth system (matches SFTPGo design) - Admin sessions stored in-memory (24h validity) - bcrypt password verification (cost=10) - localStorage token persistence for UI - Settings panel requires admin authentication Files changed: - data/init_auth_db.sql: +20 lines - src/sync.rs: +100 lines - src/pg_client.rs: +50 lines - src/auth.rs: +60 lines - src/server.rs: +50 lines - src/page.html: +70 lines Total: ~290 lines added Tested: Admin sync, login, verify, UI modal all working
This commit is contained in:
@@ -121,6 +121,9 @@ let state = AppState {
|
||||
.route("/api/v2/config", get(get_config_handler))
|
||||
.route("/api/v2/config/edit", post(edit_config_handler))
|
||||
.route("/api/v2/config/validate", get(validate_config_handler))
|
||||
// Admin authentication API endpoints (public)
|
||||
.route("/api/v2/admin/login", post(admin_login_handler))
|
||||
.route("/api/v2/admin/verify", get(admin_verify_handler))
|
||||
// Protected endpoints (require auth)
|
||||
.route("/api/v2/tree/:user_id", get(get_tree))
|
||||
.route("/api/v2/tree/:user_id/node", post(create_node))
|
||||
@@ -1563,3 +1566,44 @@ async fn validate_config_handler() -> impl IntoResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn admin_login_handler(
|
||||
State(state): State<crate::auth::AuthState>,
|
||||
Json(body): Json<crate::auth::AdminLoginRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match state.admin_login(&body.username, &body.password) {
|
||||
Some(response) => (StatusCode::OK, Json(response)).into_response(),
|
||||
None => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(serde_json::json!({"error": "Invalid admin credentials"})),
|
||||
).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn admin_verify_handler(
|
||||
State(state): State<crate::auth::AuthState>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let auth_header = headers
|
||||
.get("Authorization")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.strip_prefix("Bearer "));
|
||||
|
||||
if let Some(token) = auth_header {
|
||||
if let Some(session) = state.verify_admin_token(token) {
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({
|
||||
"ok": true,
|
||||
"username": session.username,
|
||||
"expires_at": session.expires_at
|
||||
})),
|
||||
).into_response();
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(serde_json::json!({"ok": false, "error": "Invalid admin token"})),
|
||||
).into_response()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user