feat(auth): Separate Login, Register, and Profile views with robust validations and SQL-safe backend
This commit is contained in:
1 parent
5f3abba27c
commit
4e450d758b
5 files changed
+392
-126
No files matched your search
+78
-18
@@ -164,8 +164,53 @@ class FeedbackHandler(http.server.BaseHTTPRequestHandler):
|
||||
self.wfile.write(json.dumps({"error": f"Invalid JSON: {e}"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
# 1. User Authentication (Login & Auto-Registration)
|
||||
if path in ["/api/auth/login", "/api/bugs/auth/login", "/api/auth/register"]:
|
||||
# 1a. User Login
|
||||
if path in ["/api/auth/login", "/api/bugs/auth/login"]:
|
||||
username = str(payload.get("username", "")).strip()
|
||||
password_hash = str(payload.get("passwordHash", "")).strip()
|
||||
|
||||
if not username or not password_hash:
|
||||
self.send_response(400)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Введите логин и пароль"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
import re
|
||||
if not re.match(r"^[a-zA-Zа-яА-ЯёЁ0-9_-]{3,20}$", username):
|
||||
self.send_response(400)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Логин может содержать только буквы, цифры, '-' и '_' (3-20 симв.)"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
user = db.get_user_by_username(username)
|
||||
if not user:
|
||||
self.send_response(404)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Пользователь с таким логином не найден. Зарегистрируйтесь."}).encode("utf-8"))
|
||||
return
|
||||
|
||||
res = db.authenticate_user(username, password_hash)
|
||||
if not res or not res.get("success"):
|
||||
self.send_response(401)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(res or {"success": False, "message": "Неверный логин или пароль"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
data = json.dumps(res, ensure_ascii=False).encode("utf-8")
|
||||
self.send_response(200)
|
||||
self._set_cors()
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
return
|
||||
|
||||
# 1b. User Registration
|
||||
if path in ["/api/auth/register", "/api/bugs/auth/register"]:
|
||||
username = str(payload.get("username", "")).strip()
|
||||
password_hash = str(payload.get("passwordHash", "")).strip()
|
||||
initial_data = payload.get("initialData")
|
||||
@@ -174,27 +219,42 @@ class FeedbackHandler(http.server.BaseHTTPRequestHandler):
|
||||
self.send_response(400)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Имя пользователя и пароль обязательны"}).encode("utf-8"))
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Введите логин и пароль для регистрации"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
import re
|
||||
if not re.match(r"^[a-zA-Zа-яА-ЯёЁ0-9_-]{3,20}$", username):
|
||||
self.send_response(400)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Логин может содержать только буквы, цифры, '-' и '_' (3-20 симв.)"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
if len(password_hash) < 1:
|
||||
self.send_response(400)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Пароль не может быть пустым"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
if username.lower() in ["гость", "guest", "admin", "null", "undefined", "system", "anonymous"]:
|
||||
self.send_response(400)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Это имя пользователя зарезервировано"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
# Check if user exists
|
||||
user = db.get_user_by_username(username)
|
||||
if user:
|
||||
res = db.authenticate_user(username, password_hash)
|
||||
if not res or not res.get("success"):
|
||||
self.send_response(401)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(res or {"success": False, "message": "Неверный пароль"}).encode("utf-8"))
|
||||
return
|
||||
resp_json = res
|
||||
else:
|
||||
# Auto-register new user
|
||||
res = db.register_user(username, password_hash, initial_data)
|
||||
resp_json = res
|
||||
self.send_response(409)
|
||||
self._set_cors()
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"success": False, "message": "Пользователь с таким логином уже существует"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
data = json.dumps(resp_json, ensure_ascii=False).encode("utf-8")
|
||||
self.send_response(200)
|
||||
res = db.register_user(username, password_hash, initial_data)
|
||||
data = json.dumps(res, ensure_ascii=False).encode("utf-8")
|
||||
self.send_response(200 if res.get("success") else 400)
|
||||
self._set_cors()
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
|
||||
@@ -186,6 +186,7 @@ def test_auth_system():
|
||||
"getCurrentUser",
|
||||
"isGuest",
|
||||
"login",
|
||||
"register",
|
||||
"loginAsGuest",
|
||||
"logout",
|
||||
"getUserData",
|
||||
@@ -193,6 +194,8 @@ def test_auth_system():
|
||||
"flushSave",
|
||||
"scheduleSave",
|
||||
"apiPost",
|
||||
"validateUsername",
|
||||
"validatePassword"
|
||||
]
|
||||
for m in auth_methods:
|
||||
if m in auth_code:
|
||||
@@ -205,6 +208,19 @@ def test_auth_system():
|
||||
record("auth.js has debounced save (scheduleSave)", "PASS" if "scheduleSave" in auth_code else "FAIL")
|
||||
record("auth.js has flushSave for game-over sync", "PASS" if "flushSave" in auth_code else "FAIL")
|
||||
|
||||
# Check HTML auth views
|
||||
index_html_path = os.path.join(GAME_DIR, "index.html")
|
||||
with open(index_html_path, "r", encoding="utf-8") as f:
|
||||
html_code = f.read()
|
||||
|
||||
record("Auth Login view present", "PASS" if 'id="auth-login-view"' in html_code else "FAIL")
|
||||
record("Auth Register view present", "PASS" if 'id="auth-register-view"' in html_code else "FAIL")
|
||||
record("Auth Logged-in view present", "PASS" if 'id="auth-logged-in-view"' in html_code else "FAIL")
|
||||
record("Auth switch to register button present", "PASS" if 'id="btn-auth-to-register"' in html_code else "FAIL")
|
||||
record("Auth do register button present", "PASS" if 'id="btn-auth-do-register"' in html_code else "FAIL")
|
||||
record("Auth guest button present", "PASS" if 'id="btn-auth-guest"' in html_code else "FAIL")
|
||||
record("Auth logout button present", "PASS" if 'id="btn-auth-logout"' in html_code else "FAIL")
|
||||
|
||||
# Server-side SQLite DB tests
|
||||
print("\n--- 4b. SQLite DB & Auth API CRUD Tests ---")
|
||||
sys.path.insert(0, os.path.join(BASE_DIR, "scripts"))
|
||||
@@ -233,6 +249,10 @@ def test_auth_system():
|
||||
record("SQLite DB: reject wrong password", "PASS" if bad_auth and not bad_auth.get("success") else "FAIL",
|
||||
"Bad password was accepted!" if bad_auth and bad_auth.get("success") else "")
|
||||
|
||||
# Test duplicate registration rejection
|
||||
dup_reg = pvz_db.register_user("kopri", "c63bc483")
|
||||
record("SQLite DB: reject duplicate user registration", "PASS" if not dup_reg.get("success") else "FAIL")
|
||||
|
||||
# Test save profile (update newbie coins)
|
||||
save_res = pvz_db.save_user_profile("newbie_test", {"coins": 999, "level": 3, "highScore": 2500})
|
||||
record("SQLite DB: save_user_profile() succeeds", "PASS" if save_res.get("success") else "FAIL")
|
||||
|
||||
Reference in new issue
Block a user