207 lines
6.4 KiB
Rust
207 lines
6.4 KiB
Rust
use axum::extract::{Form, Query, State};
|
|
use axum::http::HeaderMap;
|
|
use axum_extra::extract::cookie::{Cookie, CookieJar};
|
|
use chrono::Utc;
|
|
use rusqlite::Connection;
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::time::Instant;
|
|
|
|
use bzod::analytics::AnalyticsQueue;
|
|
use bzod::auth::hash_password;
|
|
use bzod::config::Config;
|
|
use bzod::db::users::{create_admin_user, get_user_by_username};
|
|
use bzod::db::Db;
|
|
use bzod::state::AppState;
|
|
use bzod::web::admin::{
|
|
users_create_post, users_delete_post, users_get, CreateUserForm, DeleteUserForm, UsersQuery,
|
|
};
|
|
|
|
fn create_temp_config(temp_dir: PathBuf) -> Config {
|
|
let mut config = Config::load_with_cli(Some(&temp_dir)).unwrap();
|
|
config.backup_dir = temp_dir.clone();
|
|
config.base_url = Some("http://bzo.in".to_string());
|
|
config
|
|
}
|
|
|
|
fn build_state(config: Config) -> (Db, AppState) {
|
|
let db = Db::init(&config).expect("Failed to init Db");
|
|
let (tx, rx) = tokio::sync::watch::channel(false);
|
|
Box::leak(Box::new(tx));
|
|
let (queue, _) = AnalyticsQueue::new(db.clone(), 1000, rx);
|
|
let state = AppState {
|
|
admin_db: db.admin.clone(),
|
|
system_db: db.system.clone(),
|
|
users_db: db.users.clone(),
|
|
user_dbs: std::sync::Arc::new(std::sync::Mutex::new(HashMap::new())),
|
|
db: db.clone(),
|
|
config,
|
|
analytics_queue: queue,
|
|
start_time: Instant::now(),
|
|
};
|
|
(db, state)
|
|
}
|
|
|
|
fn create_admin_session(conn: &Connection, admin_id: i64, session_id: &str) {
|
|
let now = Utc::now();
|
|
let expires_at = (now + chrono::Duration::hours(1)).to_rfc3339();
|
|
let created_at = now.to_rfc3339();
|
|
conn.execute(
|
|
"INSERT INTO sessions (id, user_id, expires_at, created_at) VALUES (?1, ?2, ?3, ?4);",
|
|
rusqlite::params![session_id, admin_id, expires_at, created_at],
|
|
)
|
|
.expect("Failed to insert admin session");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_admin_users_page_and_user_creation() {
|
|
let temp_dir = std::env::temp_dir().join(format!(
|
|
"bzod_test_admin_user_management_{}",
|
|
uuid::Uuid::new_v4()
|
|
));
|
|
fs::create_dir_all(&temp_dir).unwrap();
|
|
let config = create_temp_config(temp_dir.clone());
|
|
let (_db, state) = build_state(config.clone());
|
|
|
|
let password = "adminpassword";
|
|
let hash = hash_password(password).unwrap();
|
|
let admin_user = {
|
|
let conn = state.users_db.lock().unwrap();
|
|
create_admin_user(&conn, "admin", &hash).unwrap()
|
|
};
|
|
|
|
let session_token = "session-token-123";
|
|
{
|
|
let conn = state.users_db.lock().unwrap();
|
|
create_admin_session(&conn, admin_user.id, session_token);
|
|
}
|
|
|
|
let jar = CookieJar::new().add(Cookie::new("bzod_session", session_token));
|
|
let response = users_get(
|
|
State(state.clone()),
|
|
jar,
|
|
Query(UsersQuery {
|
|
success: None,
|
|
error: None,
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
if response.status().is_redirection() {
|
|
let location = response
|
|
.headers()
|
|
.get("location")
|
|
.map(|v| v.to_str().unwrap_or(""));
|
|
println!("users_get redirect to: {:?}", location);
|
|
}
|
|
|
|
assert_eq!(response.status(), axum::http::StatusCode::OK);
|
|
let body = axum::body::to_bytes(response.into_body(), 65536)
|
|
.await
|
|
.unwrap();
|
|
let body_str = String::from_utf8_lossy(&body);
|
|
assert!(body_str.contains("Users Management"));
|
|
assert!(body_str.contains("Create New User"));
|
|
assert!(body_str.contains("admin"));
|
|
|
|
let _ = fs::remove_dir_all(&temp_dir);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_admin_create_and_delete_user() {
|
|
let temp_dir = std::env::temp_dir().join(format!(
|
|
"bzod_test_admin_user_management_delete_{}",
|
|
uuid::Uuid::new_v4()
|
|
));
|
|
fs::create_dir_all(&temp_dir).unwrap();
|
|
let config = create_temp_config(temp_dir.clone());
|
|
let (_db, state) = build_state(config.clone());
|
|
|
|
let password = "adminpassword";
|
|
let hash = hash_password(password).unwrap();
|
|
let admin_user = {
|
|
let conn = state.users_db.lock().unwrap();
|
|
create_admin_user(&conn, "admin", &hash).unwrap()
|
|
};
|
|
|
|
let session_token = "session-token-456";
|
|
{
|
|
let conn = state.users_db.lock().unwrap();
|
|
create_admin_session(&conn, admin_user.id, session_token);
|
|
}
|
|
|
|
let jar = CookieJar::new().add(Cookie::new("bzod_session", session_token));
|
|
|
|
let form = CreateUserForm {
|
|
username: "testuser".to_string(),
|
|
password: "password123".to_string(),
|
|
account_type: "standard".to_string(),
|
|
metadata: "test metadata".to_string(),
|
|
csrf_token: "invalid".to_string(),
|
|
};
|
|
|
|
// invalid CSRF should redirect with error
|
|
let response = users_create_post(State(state.clone()), jar.clone(), Form(form)).await;
|
|
assert!(response.status().is_redirection());
|
|
assert!(response
|
|
.headers()
|
|
.get("location")
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.contains("Invalid CSRF token"));
|
|
|
|
// build a valid CSRF token from admin session
|
|
let csrf_token = bzod::auth::generate_csrf_token(session_token);
|
|
let form = CreateUserForm {
|
|
username: "testuser".to_string(),
|
|
password: "password123".to_string(),
|
|
account_type: "standard".to_string(),
|
|
metadata: "test metadata".to_string(),
|
|
csrf_token: csrf_token.clone(),
|
|
};
|
|
|
|
let response = users_create_post(State(state.clone()), jar.clone(), Form(form)).await;
|
|
assert!(response.status().is_redirection());
|
|
assert_eq!(
|
|
response.headers().get("location").unwrap(),
|
|
"/admin/users?success=User created successfully"
|
|
);
|
|
|
|
let created_user_id = {
|
|
let conn = state.users_db.lock().unwrap();
|
|
let user = get_user_by_username(&conn, "testuser")
|
|
.unwrap()
|
|
.expect("created user should exist");
|
|
user.id
|
|
};
|
|
|
|
let delete_form = DeleteUserForm {
|
|
csrf_token: csrf_token.clone(),
|
|
};
|
|
let response = users_delete_post(
|
|
State(state.clone()),
|
|
jar.clone(),
|
|
HeaderMap::new(),
|
|
None,
|
|
axum::extract::Path(created_user_id),
|
|
Form(delete_form),
|
|
)
|
|
.await;
|
|
|
|
assert!(response.status().is_redirection());
|
|
assert_eq!(
|
|
response.headers().get("location").unwrap(),
|
|
"/admin/users?success=User deleted successfully"
|
|
);
|
|
|
|
{
|
|
let conn = state.users_db.lock().unwrap();
|
|
let user = get_user_by_username(&conn, "testuser").unwrap();
|
|
assert!(user.is_none());
|
|
}
|
|
|
|
let _ = fs::remove_dir_all(&temp_dir);
|
|
}
|