From 03063471892048e295b5dae537cb4a939b2632e6 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 22 Aug 2025 22:08:10 +0330 Subject: [PATCH 01/12] feat: Migrate command auth to high-performance aiohttp server --- config.json | 7 ++- core/scripts/hysteria2/auth_server.py | 83 +++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 core/scripts/hysteria2/auth_server.py diff --git a/config.json b/config.json index 619dc04..b11cc8c 100644 --- a/config.json +++ b/config.json @@ -13,8 +13,11 @@ } }, "auth": { - "type": "command", - "command": "/etc/hysteria/core/scripts/hysteria2/user.sh" + "type": "http", + "http": { + "url": "http://127.0.0.1:28262/auth", + "timeout": "5s" + } }, "quic": { "initStreamReceiveWindow": 8388608, diff --git a/core/scripts/hysteria2/auth_server.py b/core/scripts/hysteria2/auth_server.py new file mode 100644 index 0000000..a16d650 --- /dev/null +++ b/core/scripts/hysteria2/auth_server.py @@ -0,0 +1,83 @@ +import json +import os +import asyncio +from datetime import datetime, timedelta +from aiohttp import web +import aiofiles +from init_paths import * +from paths import * + +users_data = {} +users_lock = asyncio.Lock() + +async def load_users(app): + global users_data + async with users_lock: + if os.path.exists(USERS_FILE): + async with aiofiles.open(USERS_FILE, 'r') as f: + content = await f.read() + users_data = json.loads(content) + else: + users_data = {} + app['users_data'] = users_data + +async def save_users(): + async with users_lock: + async with aiofiles.open(USERS_FILE, 'w') as f: + await f.write(json.dumps(users_data, indent=4)) + +async def authenticate(request): + global users_data + try: + data = await request.json() + auth_str = data.get("auth") + if not auth_str: + return web.json_response({"ok": False, "msg": "Auth field missing"}, status=400) + + username, password = auth_str.split(":", 1) + except (json.JSONDecodeError, ValueError, TypeError): + return web.json_response({"ok": False, "msg": "Invalid request format"}, status=400) + + async with users_lock: + user = users_data.get(username) + + if not user: + return web.json_response({"ok": False, "msg": "User not found"}, status=401) + + if user.get("blocked", False): + return web.json_response({"ok": False, "msg": "User is blocked"}, status=401) + + if user.get("password") != password: + return web.json_response({"ok": False, "msg": "Invalid password"}, status=401) + + should_save = False + expiration_days = user.get("expiration_days", 0) + if expiration_days > 0: + creation_date_str = user.get("account_creation_date") + if creation_date_str: + creation_date = datetime.strptime(creation_date_str, "%Y-%m-%d") + expiration_date = creation_date + timedelta(days=expiration_days) + if datetime.now() >= expiration_date: + user["blocked"] = True + should_save = True + + max_bytes = user.get("max_download_bytes", 0) + if max_bytes > 0: + current_up = user.get("upload_bytes", 0) + current_down = user.get("download_bytes", 0) + if (current_up + current_down) >= max_bytes: + user["blocked"] = True + should_save = True + + if should_save: + await save_users() + return web.json_response({"ok": False, "msg": "User blocked due to limits"}, status=401) + + return web.json_response({"ok": True, "id": username}) + +app = web.Application() +app.router.add_post("/auth", authenticate) +app.on_startup.append(load_users) + +if __name__ == "__main__": + web.run_app(app, host="127.0.0.1", port=28262) \ No newline at end of file From 51e7a9bab7d67ffedb350b870ca6335dba8f0631 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 22 Aug 2025 22:28:11 +0330 Subject: [PATCH 02/12] refactor(install): Integrate aiohttp auth server into installation --- core/scripts/hysteria2/install.sh | 13 ++++++++++- core/scripts/scheduler.sh | 38 ++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/core/scripts/hysteria2/install.sh b/core/scripts/hysteria2/install.sh index bea3751..e43d19e 100644 --- a/core/scripts/hysteria2/install.sh +++ b/core/scripts/hysteria2/install.sh @@ -80,9 +80,20 @@ install_hysteria() { exit 1 fi - chmod +x /etc/hysteria/core/scripts/hysteria2/user.sh chmod +x /etc/hysteria/core/scripts/hysteria2/kick.py + if ! check_auth_server_service; then + echo "Setting up Hysteria auth server..." + setup_hysteria_auth_server + fi + + if systemctl is-active --quiet hysteria-auth.service; then + echo -e "${cyan}Hysteria auth server${NC} has been successfully started." + else + echo -e "${red}Error:${NC} hysteria-auth.service is not active." + exit 1 + fi + if ! check_scheduler_service; then setup_hysteria_scheduler fi diff --git a/core/scripts/scheduler.sh b/core/scripts/scheduler.sh index 95dbc8a..64257f3 100644 --- a/core/scripts/scheduler.sh +++ b/core/scripts/scheduler.sh @@ -27,10 +27,7 @@ EOF systemctl daemon-reload systemctl enable hysteria-scheduler.service systemctl start hysteria-scheduler.service - # wait 2 (crontab -l | grep -v "hysteria2_venv.*traffic-status" | grep -v "hysteria2_venv.*backup-hysteria") | crontab - - - # return 0 } check_scheduler_service() { @@ -40,3 +37,38 @@ check_scheduler_service() { return 1 fi } + +setup_hysteria_auth_server() { + chmod +x /etc/hysteria/core/scripts/hysteria2/auth_server.py + + cat > /etc/systemd/system/hysteria-auth.service << 'EOF' +[Unit] +Description=Hysteria aiohttp Auth Server +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/etc/hysteria/core/scripts/hysteria2 +ExecStart=/etc/hysteria/hysteria2_venv/bin/python3 /etc/hysteria/core/scripts/hysteria2/auth_server.py +Restart=always +RestartSec=3 +Environment="USERS_FILE=/etc/hysteria/users.json" +Environment="CONFIG_FILE=/etc/hysteria/config.json" + +[Install] +WantedBy=multi-user.target +EOF + + systemctl daemon-reload + systemctl enable hysteria-auth.service + systemctl start hysteria-auth.service +} + +check_auth_server_service() { + if systemctl is-active --quiet hysteria-auth.service; then + return 0 + else + return 1 + fi +} \ No newline at end of file From 4d4e68b66f7ab29c91d3b112b8c7772b63d6393c Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 22 Aug 2025 23:10:36 +0330 Subject: [PATCH 03/12] perf(auth): Make aiohttp auth server read-only Refactored the aiohttp authentication server to be a purely read-only service. Removed all file write operations from the authentication logic to eliminate disk I/O and reduce CPU overhead during connection attempts. The responsibility for persistently blocking users by writing to `users.json` is now fully delegated to the external scheduler service, allowing the auth server to function as a faster, more efficient gatekeeper. --- core/scripts/hysteria2/auth_server.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/core/scripts/hysteria2/auth_server.py b/core/scripts/hysteria2/auth_server.py index a16d650..efe54b9 100644 --- a/core/scripts/hysteria2/auth_server.py +++ b/core/scripts/hysteria2/auth_server.py @@ -14,18 +14,16 @@ async def load_users(app): global users_data async with users_lock: if os.path.exists(USERS_FILE): - async with aiofiles.open(USERS_FILE, 'r') as f: - content = await f.read() - users_data = json.loads(content) + try: + async with aiofiles.open(USERS_FILE, 'r') as f: + content = await f.read() + users_data = json.loads(content) + except (IOError, json.JSONDecodeError): + users_data = {} else: users_data = {} app['users_data'] = users_data -async def save_users(): - async with users_lock: - async with aiofiles.open(USERS_FILE, 'w') as f: - await f.write(json.dumps(users_data, indent=4)) - async def authenticate(request): global users_data try: @@ -50,7 +48,6 @@ async def authenticate(request): if user.get("password") != password: return web.json_response({"ok": False, "msg": "Invalid password"}, status=401) - should_save = False expiration_days = user.get("expiration_days", 0) if expiration_days > 0: creation_date_str = user.get("account_creation_date") @@ -58,20 +55,14 @@ async def authenticate(request): creation_date = datetime.strptime(creation_date_str, "%Y-%m-%d") expiration_date = creation_date + timedelta(days=expiration_days) if datetime.now() >= expiration_date: - user["blocked"] = True - should_save = True + return web.json_response({"ok": False, "msg": "Account expired"}, status=401) max_bytes = user.get("max_download_bytes", 0) if max_bytes > 0: current_up = user.get("upload_bytes", 0) current_down = user.get("download_bytes", 0) if (current_up + current_down) >= max_bytes: - user["blocked"] = True - should_save = True - - if should_save: - await save_users() - return web.json_response({"ok": False, "msg": "User blocked due to limits"}, status=401) + return web.json_response({"ok": False, "msg": "Data limit exceeded"}, status=401) return web.json_response({"ok": True, "id": username}) From eac9cfde75efea25734b18433ce1a4bc2e762de2 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 22 Aug 2025 23:11:26 +0330 Subject: [PATCH 04/12] add auth service status --- core/scripts/services_status.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/scripts/services_status.sh b/core/scripts/services_status.sh index a63d9bf..b618529 100644 --- a/core/scripts/services_status.sh +++ b/core/scripts/services_status.sh @@ -3,6 +3,7 @@ declare -a services=( "hysteria-server.service" "hysteria-scheduler.service" + "hysteria-auth.service" "hysteria-webpanel.service" "hysteria-caddy.service" "hysteria-telegram-bot.service" @@ -22,8 +23,6 @@ for service in "${services[@]}"; do fi done -# Remove trailing comma and close JSON properly status_json="${status_json%,}}" -# Format output as valid JSON echo "$status_json" | jq -M . From 80b7af2d6115d7af26de0f8994e7cee5872b7f02 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sat, 23 Aug 2025 23:08:29 +0330 Subject: [PATCH 05/12] refactor(upgrade): Adapt upgrade script for aiohttp auth server --- core/scripts/scheduler.sh | 9 ++++---- upgrade.sh | 45 +++++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/core/scripts/scheduler.sh b/core/scripts/scheduler.sh index 64257f3..38b2746 100644 --- a/core/scripts/scheduler.sh +++ b/core/scripts/scheduler.sh @@ -49,12 +49,13 @@ After=network.target [Service] Type=simple User=root -WorkingDirectory=/etc/hysteria/core/scripts/hysteria2 +WorkingDirectory=/etc/hysteria ExecStart=/etc/hysteria/hysteria2_venv/bin/python3 /etc/hysteria/core/scripts/hysteria2/auth_server.py Restart=always -RestartSec=3 -Environment="USERS_FILE=/etc/hysteria/users.json" -Environment="CONFIG_FILE=/etc/hysteria/config.json" +RestartSec=10 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=hysteria-Auth [Install] WantedBy=multi-user.target diff --git a/upgrade.sh b/upgrade.sh index f97a6c2..383d906 100644 --- a/upgrade.sh +++ b/upgrade.sh @@ -7,7 +7,7 @@ trap 'echo -e "\nāŒ An error occurred. Aborting."; exit 1' ERR HYSTERIA_INSTALL_DIR="/etc/hysteria" HYSTERIA_VENV_DIR="$HYSTERIA_INSTALL_DIR/hysteria2_venv" REPO_URL="https://github.com/ReturnFI/Blitz" -REPO_BRANCH="main" +REPO_BRANCH="auth" GEOSITE_URL="https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geosite.dat" GEOIP_URL="https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat" @@ -34,7 +34,6 @@ FILES=( "$HYSTERIA_INSTALL_DIR/.configs.env" "$HYSTERIA_INSTALL_DIR/nodes.json" "$HYSTERIA_INSTALL_DIR/core/scripts/telegrambot/.env" - # "$HYSTERIA_INSTALL_DIR/core/scripts/singbox/.env" "$HYSTERIA_INSTALL_DIR/core/scripts/normalsub/.env" "$HYSTERIA_INSTALL_DIR/core/scripts/normalsub/Caddyfile.normalsub" "$HYSTERIA_INSTALL_DIR/core/scripts/webpanel/.env" @@ -77,15 +76,22 @@ for FILE in "${FILES[@]}"; do fi done +# ========== Update Configuration ========== +info "Updating Hysteria configuration for HTTP authentication..." +auth_block='{"type": "http", "http": {"url": "http://127.0.0.1:28262/auth", "timeout": "5s"}}' +if [[ -f "$HYSTERIA_INSTALL_DIR/config.json" ]]; then + jq --argjson auth_block "$auth_block" '.auth = $auth_block' "$HYSTERIA_INSTALL_DIR/config.json" > "$HYSTERIA_INSTALL_DIR/config.json.tmp" && mv "$HYSTERIA_INSTALL_DIR/config.json.tmp" "$HYSTERIA_INSTALL_DIR/config.json" + success "config.json updated to use aiohttp auth server." +else + warn "config.json not found after restore. Skipping auth update." +fi + # ========== Permissions ========== info "Setting ownership and permissions..." chown hysteria:hysteria "$HYSTERIA_INSTALL_DIR/ca.key" "$HYSTERIA_INSTALL_DIR/ca.crt" chmod 640 "$HYSTERIA_INSTALL_DIR/ca.key" "$HYSTERIA_INSTALL_DIR/ca.crt" - -# chown -R hysteria:hysteria "$HYSTERIA_INSTALL_DIR/core/scripts/singbox" chown -R hysteria:hysteria "$HYSTERIA_INSTALL_DIR/core/scripts/telegrambot" - -chmod +x "$HYSTERIA_INSTALL_DIR/core/scripts/hysteria2/user.sh" +chmod +x "$HYSTERIA_INSTALL_DIR/core/scripts/hysteria2/auth_server.py" chmod +x "$HYSTERIA_INSTALL_DIR/core/scripts/hysteria2/kick.py" # ========== Virtual Environment ========== @@ -97,26 +103,29 @@ pip install --upgrade pip >/dev/null pip install -r requirements.txt >/dev/null success "Python environment ready." -# ========== Scheduler ========== -info "Ensuring scheduler is set..." +# ========== Systemd Services ========== +info "Ensuring systemd services are configured..." if source "$HYSTERIA_INSTALL_DIR/core/scripts/scheduler.sh"; then - if ! check_scheduler_service; then - if setup_hysteria_scheduler; then - success "Scheduler service configured." - else - warn "Scheduler setup failed, but continuing upgrade..." - fi + if ! check_auth_server_service; then + setup_hysteria_auth_server && success "Auth server service configured." || warn "Auth server setup failed." else - success "Scheduler already set." + success "Auth server service already configured." + fi + + if ! check_scheduler_service; then + setup_hysteria_scheduler && success "Scheduler service configured." || warn "Scheduler setup failed." + else + success "Scheduler service already set." fi else - warn "Failed to source scheduler.sh, continuing without scheduler setup..." + warn "Failed to source scheduler.sh, continuing without service setup..." fi # ========== Restart Services ========== SERVICES=( hysteria-caddy.service hysteria-server.service + hysteria-auth.service hysteria-scheduler.service hysteria-telegram-bot.service hysteria-normal-sub.service @@ -130,7 +139,7 @@ for SERVICE in "${SERVICES[@]}"; do if systemctl status "$SERVICE" &>/dev/null; then systemctl restart "$SERVICE" && success "$SERVICE restarted." || warn "$SERVICE failed to restart." else - warn "$SERVICE not found or not installed. Skipping..." + warn "$SERVICE not found. Skipping..." fi done @@ -144,4 +153,4 @@ fi # ========== Launch Menu ========== sleep 10 chmod +x menu.sh -./menu.sh +./menu.sh \ No newline at end of file From 3ca2e6649e4cf9a3f0dd6a76594b5b8884d29f62 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:56:07 +0330 Subject: [PATCH 06/12] feat(auth): Migrate from command to high-performance HTTP --- core/scripts/auth/user_auth.go | 147 +++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 core/scripts/auth/user_auth.go diff --git a/core/scripts/auth/user_auth.go b/core/scripts/auth/user_auth.go new file mode 100644 index 0000000..4a4f85c --- /dev/null +++ b/core/scripts/auth/user_auth.go @@ -0,0 +1,147 @@ +package main + +import ( + "crypto/subtle" + "encoding/json" + "io" + "log" + "net/http" + "os" + "strings" + "sync" + "time" +) + +const ( + listenAddr = "127.0.0.1:28262" + usersFile = "/etc/hysteria/users.json" + cacheTTL = 15 * time.Second +) + +type User struct { + Password string `json:"password"` + MaxDownloadBytes int64 `json:"max_download_bytes"` + ExpirationDays int `json:"expiration_days"` + AccountCreationDate string `json:"account_creation_date"` + Blocked bool `json:"blocked"` + UploadBytes int64 `json:"upload_bytes"` + DownloadBytes int64 `json:"download_bytes"` + UnlimitedUser bool `json:"unlimited_user"` +} + +type httpAuthRequest struct { + Addr string `json:"addr"` + Auth string `json:"auth"` + Tx uint64 `json:"tx"` +} + +type httpAuthResponse struct { + OK bool `json:"ok"` + ID string `json:"id"` +} + +var ( + userCache map[string]User + cacheMutex = &sync.RWMutex{} +) + +func loadUsersToCache() { + data, err := os.ReadFile(usersFile) + if err != nil { + return + } + var users map[string]User + if err := json.Unmarshal(data, &users); err != nil { + return + } + cacheMutex.Lock() + userCache = users + cacheMutex.Unlock() +} + +func authHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req httpAuthRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + username, password, ok := strings.Cut(req.Auth, ":") + if !ok { + json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) + return + } + + cacheMutex.RLock() + user, ok := userCache[username] + cacheMutex.RUnlock() + + // 1. Check existence + if !ok { + json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) + return + } + + // 2. Check if blocked + if user.Blocked { + json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) + return + } + + // 3. Check password (constant time) + if subtle.ConstantTimeCompare([]byte(user.Password), []byte(password)) != 1 { + time.Sleep(5 * time.Second) // Slow down brute-force attacks + json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) + return + } + + // 4. Check if unlimited (if so, grant access) + if user.UnlimitedUser { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(httpAuthResponse{OK: true, ID: username}) + return + } + + // 5. Check expiration + if user.ExpirationDays > 0 { + creationDate, err := time.Parse("2006-01-02", user.AccountCreationDate) + if err == nil && time.Now().After(creationDate.AddDate(0, 0, user.ExpirationDays)) { + json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) + return + } + } + + // 6. Check traffic limit + if user.MaxDownloadBytes > 0 && (user.DownloadBytes+user.UploadBytes) >= user.MaxDownloadBytes { + json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) + return + } + + // All checks passed + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(httpAuthResponse{OK: true, ID: username}) +} + +func main() { + log.SetOutput(io.Discard) // Disable logging for max performance + loadUsersToCache() + + ticker := time.NewTicker(cacheTTL) + go func() { + for range ticker.C { + loadUsersToCache() + } + }() + + http.HandleFunc("/auth", authHandler) + if err := http.ListenAndServe(listenAddr, nil); err != nil { + // If we can't start, log to stderr so systemd can see it + log.SetOutput(os.Stderr) + log.Fatalf("Failed to start server: %v", err) + } +} From dbff54652333fc84f555117e4b0ffaa2f829a877 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 20:34:33 +0330 Subject: [PATCH 07/12] feat(auth): Implement Go HTTP auth server for max performance --- core/scripts/hysteria2/install.sh | 30 ++++++++++++++++++++++-- core/scripts/scheduler.sh | 12 +++++----- install.sh | 2 +- upgrade.sh | 38 ++++++++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/core/scripts/hysteria2/install.sh b/core/scripts/hysteria2/install.sh index e43d19e..bde3651 100644 --- a/core/scripts/hysteria2/install.sh +++ b/core/scripts/hysteria2/install.sh @@ -5,6 +5,30 @@ source /etc/hysteria/core/scripts/utils.sh source /etc/hysteria/core/scripts/scheduler.sh define_colors +compile_auth_binary() { + echo "Compiling authentication binary..." + local auth_dir="/etc/hysteria/core/scripts/auth" + + if [ -f "$auth_dir/user_auth.go" ]; then + ( + cd "$auth_dir" || exit 1 + go mod init hysteria-auth >/dev/null 2>&1 + go mod tidy >/dev/null 2>&1 + if go build -o user_auth .; then + chown hysteria:hysteria user_auth + chmod +x user_auth + echo "Authentication binary compiled successfully." + else + echo -e "${red}Error:${NC} Failed to compile the authentication binary." + exit 1 + fi + ) + else + echo -e "${red}Error:${NC} Go source file not found at $auth_dir/user_auth.go" + exit 1 + fi +} + install_hysteria() { local port=$1 @@ -12,7 +36,9 @@ install_hysteria() { bash <(curl -fsSL https://get.hy2.sh/) >/dev/null 2>&1 mkdir -p /etc/hysteria && cd /etc/hysteria/ - + + compile_auth_binary + echo "Generating CA key and certificate..." openssl ecparam -genkey -name prime256v1 -out ca.key >/dev/null 2>&1 openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/CN=$sni" >/dev/null 2>&1 @@ -114,4 +140,4 @@ else else echo -e "${red}Error:${NC} Hysteria2 service is not active. Please check the logs for more details." fi -fi +fi \ No newline at end of file diff --git a/core/scripts/scheduler.sh b/core/scripts/scheduler.sh index 38b2746..fb4d48d 100644 --- a/core/scripts/scheduler.sh +++ b/core/scripts/scheduler.sh @@ -39,20 +39,20 @@ check_scheduler_service() { } setup_hysteria_auth_server() { - chmod +x /etc/hysteria/core/scripts/hysteria2/auth_server.py + chmod +x /etc/hysteria/core/scripts/auth/user_auth cat > /etc/systemd/system/hysteria-auth.service << 'EOF' [Unit] -Description=Hysteria aiohttp Auth Server +Description=Hysteria Auth Server After=network.target [Service] Type=simple -User=root -WorkingDirectory=/etc/hysteria -ExecStart=/etc/hysteria/hysteria2_venv/bin/python3 /etc/hysteria/core/scripts/hysteria2/auth_server.py +User=hysteria +Group=hysteria +ExecStart=/etc/hysteria/core/auth-server/auth_server Restart=always -RestartSec=10 +RestartSec=5 StandardOutput=journal StandardError=journal SyslogIdentifier=hysteria-Auth diff --git a/install.sh b/install.sh index bf6a652..d463b2f 100644 --- a/install.sh +++ b/install.sh @@ -76,7 +76,7 @@ check_os_version() { } install_packages() { - local REQUIRED_PACKAGES=("jq" "curl" "pwgen" "python3" "python3-pip" "python3-venv" "git" "bc" "zip" "cron" "lsof") + local REQUIRED_PACKAGES=("jq" "curl" "pwgen" "python3" "python3-pip" "python3-venv" "git" "bc" "zip" "cron" "lsof" "golang-go") local MISSING_PACKAGES=() log_info "Checking required packages..." diff --git a/upgrade.sh b/upgrade.sh index 383d906..b0be527 100644 --- a/upgrade.sh +++ b/upgrade.sh @@ -6,6 +6,7 @@ trap 'echo -e "\nāŒ An error occurred. Aborting."; exit 1' ERR # ========== Variables ========== HYSTERIA_INSTALL_DIR="/etc/hysteria" HYSTERIA_VENV_DIR="$HYSTERIA_INSTALL_DIR/hysteria2_venv" +AUTH_BINARY_DIR="$HYSTERIA_INSTALL_DIR/core/scripts/auth" REPO_URL="https://github.com/ReturnFI/Blitz" REPO_BRANCH="auth" GEOSITE_URL="https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geosite.dat" @@ -23,6 +24,38 @@ success() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [OK] - ${RESET} $1"; warn() { echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] - ${RESET} $1"; } error() { echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] - ${RESET} $1"; } +# ========== New Function to Install Go and Compile Auth Binary ========== +install_go_and_compile_auth() { + info "Checking for Go and compiling authentication binary..." + if ! command -v go &>/dev/null; then + warn "Go is not installed. Attempting to install..." + apt-get update -y >/dev/null + apt-get install -y golang-go >/dev/null + success "Go installed successfully." + else + success "Go is already installed." + fi + + if [[ -f "$AUTH_BINARY_DIR/user_auth.go" ]]; then + info "Found auth binary source. Compiling..." + ( + cd "$AUTH_BINARY_DIR" + go mod init hysteria_auth >/dev/null 2>&1 + go mod tidy >/dev/null 2>&1 + if go build -o user_auth .; then + chown hysteria:hysteria user_auth + chmod +x user_auth + success "Authentication binary compiled successfully." + else + error "Failed to compile the authentication binary." + exit 1 + fi + ) + else + warn "Authentication binary source not found. Skipping compilation." + fi +} + # ========== Backup Files ========== cd /root TEMP_DIR=$(mktemp -d) @@ -81,7 +114,7 @@ info "Updating Hysteria configuration for HTTP authentication..." auth_block='{"type": "http", "http": {"url": "http://127.0.0.1:28262/auth", "timeout": "5s"}}' if [[ -f "$HYSTERIA_INSTALL_DIR/config.json" ]]; then jq --argjson auth_block "$auth_block" '.auth = $auth_block' "$HYSTERIA_INSTALL_DIR/config.json" > "$HYSTERIA_INSTALL_DIR/config.json.tmp" && mv "$HYSTERIA_INSTALL_DIR/config.json.tmp" "$HYSTERIA_INSTALL_DIR/config.json" - success "config.json updated to use aiohttp auth server." + success "config.json updated to use auth server." else warn "config.json not found after restore. Skipping auth update." fi @@ -103,6 +136,9 @@ pip install --upgrade pip >/dev/null pip install -r requirements.txt >/dev/null success "Python environment ready." +# ========== Compile Go Binary ========== +install_go_and_compile_auth + # ========== Systemd Services ========== info "Ensuring systemd services are configured..." if source "$HYSTERIA_INSTALL_DIR/core/scripts/scheduler.sh"; then From 3b3bb5183383c4794ffc9e6a4f713e13a5a2ddfb Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 20:35:25 +0330 Subject: [PATCH 08/12] Fix ExecStart Path --- core/scripts/scheduler.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/scripts/scheduler.sh b/core/scripts/scheduler.sh index fb4d48d..366dc7a 100644 --- a/core/scripts/scheduler.sh +++ b/core/scripts/scheduler.sh @@ -50,7 +50,7 @@ After=network.target Type=simple User=hysteria Group=hysteria -ExecStart=/etc/hysteria/core/auth-server/auth_server +ExecStart=/etc/hysteria/core/scripts/auth/user_auth Restart=always RestartSec=5 StandardOutput=journal From f86d44ba1c8f42099c8eb4808b250c1a3b048e28 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 20:36:20 +0330 Subject: [PATCH 09/12] Remove chmod --- core/scripts/scheduler.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/scripts/scheduler.sh b/core/scripts/scheduler.sh index 366dc7a..20a50d6 100644 --- a/core/scripts/scheduler.sh +++ b/core/scripts/scheduler.sh @@ -39,7 +39,7 @@ check_scheduler_service() { } setup_hysteria_auth_server() { - chmod +x /etc/hysteria/core/scripts/auth/user_auth + # chmod +x /etc/hysteria/core/scripts/auth/user_auth cat > /etc/systemd/system/hysteria-auth.service << 'EOF' [Unit] From e58b3dc000d15cb1f346fe8bf9650443fecfcbe8 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:10:19 +0330 Subject: [PATCH 10/12] Reduce cache time and Remove chown --- core/scripts/auth/user_auth.go | 2 +- core/scripts/hysteria2/install.sh | 1 - core/scripts/scheduler.sh | 3 +-- upgrade.sh | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/scripts/auth/user_auth.go b/core/scripts/auth/user_auth.go index 4a4f85c..6fc99d0 100644 --- a/core/scripts/auth/user_auth.go +++ b/core/scripts/auth/user_auth.go @@ -15,7 +15,7 @@ import ( const ( listenAddr = "127.0.0.1:28262" usersFile = "/etc/hysteria/users.json" - cacheTTL = 15 * time.Second + cacheTTL = 5 * time.Second ) type User struct { diff --git a/core/scripts/hysteria2/install.sh b/core/scripts/hysteria2/install.sh index bde3651..e90b07c 100644 --- a/core/scripts/hysteria2/install.sh +++ b/core/scripts/hysteria2/install.sh @@ -15,7 +15,6 @@ compile_auth_binary() { go mod init hysteria-auth >/dev/null 2>&1 go mod tidy >/dev/null 2>&1 if go build -o user_auth .; then - chown hysteria:hysteria user_auth chmod +x user_auth echo "Authentication binary compiled successfully." else diff --git a/core/scripts/scheduler.sh b/core/scripts/scheduler.sh index 20a50d6..ae1eeca 100644 --- a/core/scripts/scheduler.sh +++ b/core/scripts/scheduler.sh @@ -48,8 +48,7 @@ After=network.target [Service] Type=simple -User=hysteria -Group=hysteria +User=root ExecStart=/etc/hysteria/core/scripts/auth/user_auth Restart=always RestartSec=5 diff --git a/upgrade.sh b/upgrade.sh index b0be527..3d61154 100644 --- a/upgrade.sh +++ b/upgrade.sh @@ -43,7 +43,6 @@ install_go_and_compile_auth() { go mod init hysteria_auth >/dev/null 2>&1 go mod tidy >/dev/null 2>&1 if go build -o user_auth .; then - chown hysteria:hysteria user_auth chmod +x user_auth success "Authentication binary compiled successfully." else @@ -124,7 +123,6 @@ info "Setting ownership and permissions..." chown hysteria:hysteria "$HYSTERIA_INSTALL_DIR/ca.key" "$HYSTERIA_INSTALL_DIR/ca.crt" chmod 640 "$HYSTERIA_INSTALL_DIR/ca.key" "$HYSTERIA_INSTALL_DIR/ca.crt" chown -R hysteria:hysteria "$HYSTERIA_INSTALL_DIR/core/scripts/telegrambot" -chmod +x "$HYSTERIA_INSTALL_DIR/core/scripts/hysteria2/auth_server.py" chmod +x "$HYSTERIA_INSTALL_DIR/core/scripts/hysteria2/kick.py" # ========== Virtual Environment ========== From 90bf877a73fe5b8bfe9b7653b138f8a0c9c696ee Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:12:16 +0330 Subject: [PATCH 11/12] Remove timeout --- config.json | 3 +-- upgrade.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index b11cc8c..c4240c6 100644 --- a/config.json +++ b/config.json @@ -15,8 +15,7 @@ "auth": { "type": "http", "http": { - "url": "http://127.0.0.1:28262/auth", - "timeout": "5s" + "url": "http://127.0.0.1:28262/auth" } }, "quic": { diff --git a/upgrade.sh b/upgrade.sh index 3d61154..848759c 100644 --- a/upgrade.sh +++ b/upgrade.sh @@ -110,7 +110,7 @@ done # ========== Update Configuration ========== info "Updating Hysteria configuration for HTTP authentication..." -auth_block='{"type": "http", "http": {"url": "http://127.0.0.1:28262/auth", "timeout": "5s"}}' +auth_block='{"type": "http", "http": {"url": "http://127.0.0.1:28262/auth"}}' if [[ -f "$HYSTERIA_INSTALL_DIR/config.json" ]]; then jq --argjson auth_block "$auth_block" '.auth = $auth_block' "$HYSTERIA_INSTALL_DIR/config.json" > "$HYSTERIA_INSTALL_DIR/config.json.tmp" && mv "$HYSTERIA_INSTALL_DIR/config.json.tmp" "$HYSTERIA_INSTALL_DIR/config.json" success "config.json updated to use auth server." From 868fd5a42c3faa617f5caeff97970ad514759f5e Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 24 Aug 2025 23:20:30 +0330 Subject: [PATCH 12/12] Update changelog & Clone Main --- changelog | 25 ++++++++++++------------- core/scripts/auth/user_auth.go | 10 +--------- upgrade.sh | 2 +- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/changelog b/changelog index 24b410e..ad97071 100644 --- a/changelog +++ b/changelog @@ -1,19 +1,18 @@ -# [1.16.0] - 2025-08-19 +# [1.17.0] - 2025-08-24 -#### ✨ New Features -* šŸ“Š **Dashboard Redesign** +#### ⚔ Authentication - * Modernized UI with detailed server stats -* šŸ–„ļø **Server API Enhancements** +* šŸš€ **Implemented Go HTTP Auth Server** for **maximum performance** +* ⚔ Removed old command-based auth system - * Added uptime and traffic-since-reboot metrics -* ⚔ **System Monitor Optimization** +#### šŸ‘„ User Management - * Improved performance with async I/O - * Accurate traffic tracking since reboot +* ✨ **Bulk User Creation** added across: -#### šŸ› Fixes - -* šŸ”§ Correctly count **actual device connections** instead of unique users -* šŸ”„ Fixed subscription blocked page to display the right user data + * šŸ–„ļø **Frontend UI** + * šŸ“” **API Endpoint** + * šŸ’» **CLI Command** + * šŸ“œ **Automation Script** +* šŸ” New **Online User Filter & Sort** on the Users page +* šŸ› Fixed: underscores now supported in usernames \ No newline at end of file diff --git a/core/scripts/auth/user_auth.go b/core/scripts/auth/user_auth.go index 6fc99d0..85ffed4 100644 --- a/core/scripts/auth/user_auth.go +++ b/core/scripts/auth/user_auth.go @@ -81,33 +81,28 @@ func authHandler(w http.ResponseWriter, r *http.Request) { user, ok := userCache[username] cacheMutex.RUnlock() - // 1. Check existence if !ok { json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) return } - // 2. Check if blocked if user.Blocked { json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) return } - // 3. Check password (constant time) if subtle.ConstantTimeCompare([]byte(user.Password), []byte(password)) != 1 { time.Sleep(5 * time.Second) // Slow down brute-force attacks json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) return } - // 4. Check if unlimited (if so, grant access) if user.UnlimitedUser { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(httpAuthResponse{OK: true, ID: username}) return } - // 5. Check expiration if user.ExpirationDays > 0 { creationDate, err := time.Parse("2006-01-02", user.AccountCreationDate) if err == nil && time.Now().After(creationDate.AddDate(0, 0, user.ExpirationDays)) { @@ -116,19 +111,17 @@ func authHandler(w http.ResponseWriter, r *http.Request) { } } - // 6. Check traffic limit if user.MaxDownloadBytes > 0 && (user.DownloadBytes+user.UploadBytes) >= user.MaxDownloadBytes { json.NewEncoder(w).Encode(httpAuthResponse{OK: false}) return } - // All checks passed w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(httpAuthResponse{OK: true, ID: username}) } func main() { - log.SetOutput(io.Discard) // Disable logging for max performance + log.SetOutput(io.Discard) loadUsersToCache() ticker := time.NewTicker(cacheTTL) @@ -140,7 +133,6 @@ func main() { http.HandleFunc("/auth", authHandler) if err := http.ListenAndServe(listenAddr, nil); err != nil { - // If we can't start, log to stderr so systemd can see it log.SetOutput(os.Stderr) log.Fatalf("Failed to start server: %v", err) } diff --git a/upgrade.sh b/upgrade.sh index 848759c..a40659b 100644 --- a/upgrade.sh +++ b/upgrade.sh @@ -8,7 +8,7 @@ HYSTERIA_INSTALL_DIR="/etc/hysteria" HYSTERIA_VENV_DIR="$HYSTERIA_INSTALL_DIR/hysteria2_venv" AUTH_BINARY_DIR="$HYSTERIA_INSTALL_DIR/core/scripts/auth" REPO_URL="https://github.com/ReturnFI/Blitz" -REPO_BRANCH="auth" +REPO_BRANCH="main" GEOSITE_URL="https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geosite.dat" GEOIP_URL="https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat"