From 2ae87fca389a6e30d51502f503b0788e5a6e05b3 Mon Sep 17 00:00:00 2001
From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com>
Date: Sun, 22 Feb 2026 04:54:34 +0000
Subject: [PATCH] Refactor login functionality: Remove Remember Me feature and
update tests for deep link support
---
front/index.php | 91 +++++-------
test/ui/test_ui_login.py | 308 +++++++++------------------------------
2 files changed, 104 insertions(+), 295 deletions(-)
diff --git a/front/index.php b/front/index.php
index ca5a8188..a3e5c071 100755
--- a/front/index.php
+++ b/front/index.php
@@ -13,7 +13,6 @@ require_once $_SERVER['DOCUMENT_ROOT'].'/php/templates/security.php';
session_start();
-const COOKIE_NAME = 'NetAlertX_SaveLogin';
const DEFAULT_REDIRECT = '/devices.php';
/* =====================================================
@@ -42,9 +41,39 @@ function validate_local_path(?string $encoded): string {
return $decoded;
}
+function extract_hash_from_path(string $path): array {
+ /*
+ Split a path into path and hash components.
+
+ For deep links encoded in the 'next' parameter like /devices.php#device-123,
+ extract the hash fragment so it can be properly included in the redirect.
+
+ Args:
+ path: Full path potentially with hash (e.g., "/devices.php#device-123")
+
+ Returns:
+ Array with keys 'path' (without hash) and 'hash' (with # prefix, or empty string)
+ */
+ $parts = explode('#', $path, 2);
+ return [
+ 'path' => $parts[0],
+ 'hash' => !empty($parts[1]) ? '#' . $parts[1] : ''
+ ];
+}
+
function append_hash(string $url): string {
+ // First check if the URL already has a hash from the deep link
+ $parts = extract_hash_from_path($url);
+ if (!empty($parts['hash'])) {
+ return $parts['path'] . $parts['hash'];
+ }
+
+ // Fall back to POST url_hash (for browser-captured hashes)
if (!empty($_POST['url_hash'])) {
- return $url . preg_replace('/[^#a-zA-Z0-9_\-]/', '', $_POST['url_hash']);
+ $sanitized = preg_replace('/[^#a-zA-Z0-9_\-]/', '', $_POST['url_hash']);
+ if (str_starts_with($sanitized, '#')) {
+ return $url . $sanitized;
+ }
}
return $url;
}
@@ -134,14 +163,6 @@ function call_api(string $endpoint, array $data = []): ?array {
function logout_user(): void {
$_SESSION = [];
session_destroy();
-
- setcookie(COOKIE_NAME,'',[
- 'expires'=>time()-3600,
- 'path'=>'/',
- 'secure'=>is_https_request(),
- 'httponly'=>true,
- 'samesite'=>'Strict'
- ]);
}
/* =====================================================
@@ -173,28 +194,7 @@ if (!empty($_POST['loginpassword'])) {
login_user();
- // Handle "Remember Me" if checked
- if (!empty($_POST['PWRemember'])) {
- // Generate random token (64-byte hex = 128 chars, use 64 chars)
- $token = bin2hex(random_bytes(32));
-
- // Call API to save token hash to Parameters table
- $save_response = call_api('/auth/remember-me/save', [
- 'token' => $token
- ]);
-
- // If API call successful, set persistent cookie
- if ($save_response && isset($save_response['success']) && $save_response['success']) {
- setcookie(COOKIE_NAME, $token, [
- 'expires' => time() + 604800,
- 'path' => '/',
- 'secure' => is_https_request(),
- 'httponly' => true,
- 'samesite' => 'Strict'
- ]);
- }
- }
-
+ // Redirect to target page, preserving deep link hash if present
safe_redirect(append_hash($redirectTo));
}
}
@@ -203,20 +203,6 @@ if (!empty($_POST['loginpassword'])) {
Remember Me Validation
===================================================== */
-if (!is_authenticated() && !empty($_COOKIE[COOKIE_NAME])) {
-
- // Call API to validate token against stored hash
- $validate_response = call_api('/auth/validate-remember', [
- 'token' => $_COOKIE[COOKIE_NAME]
- ]);
-
- // If API returns valid token, authenticate and redirect
- if ($validate_response && isset($validate_response['valid']) && $validate_response['valid'] === true) {
- login_user();
- safe_redirect(append_hash($redirectTo));
- }
-}
-
/* =====================================================
Already Logged In
===================================================== */
@@ -289,18 +275,7 @@ if ($nax_Password === '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923
-
-
-
-
-
-
-
+
diff --git a/test/ui/test_ui_login.py b/test/ui/test_ui_login.py
index ee944846..1de1ba5c 100644
--- a/test/ui/test_ui_login.py
+++ b/test/ui/test_ui_login.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
Login Page UI Tests
-Tests login functionality, Remember Me, and deep link support
+Tests login functionality and deep link support after login
"""
import sys
@@ -20,7 +20,7 @@ from .test_helpers import BASE_URL, wait_for_page_load, wait_for_element_by_css
def get_login_password():
"""Get login password from config file or environment
-
+
Returns the plaintext password that should be used for login.
For test/dev environments, tries common test passwords and defaults.
Returns None if password cannot be determined (will skip test).
@@ -28,20 +28,20 @@ def get_login_password():
# Try environment variable first (for testing)
if os.getenv("LOGIN_PASSWORD"):
return os.getenv("LOGIN_PASSWORD")
-
+
# SHA256 hash of "password" - the default test password (from index.php)
DEFAULT_PASSWORD_HASH = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'
-
+
# List of passwords to try in order
passwords_to_try = ["123456", "password", "test", "admin"]
-
+
# Try common config file locations
config_paths = [
"/data/config/app.conf",
"/app/back/app.conf",
os.path.expanduser("~/.netalertx/app.conf")
]
-
+
for config_path in config_paths:
try:
if os.path.exists(config_path):
@@ -55,7 +55,7 @@ def get_login_password():
# Remove quotes
value = value.strip('"').strip("'")
print(f"✓ Found password config: {value[:32]}...")
-
+
# If it's the default, use the default password
if value == DEFAULT_PASSWORD_HASH:
print(f" Using default password: '123456'")
@@ -69,10 +69,10 @@ def get_login_password():
except (FileNotFoundError, IOError, PermissionError) as e:
print(f"⚠ Error reading {config_path}: {e}")
continue
-
+
# If we couldn't determine the password from config, try default password
print(f"ℹ Password not determinable from config, trying default passwords...")
-
+
# For now, return first test password to try
# Tests will skip if login fails
return None
@@ -80,20 +80,20 @@ def get_login_password():
def perform_login(driver, password=None):
"""Helper function to perform login with optional password fallback
-
+
Args:
driver: Selenium WebDriver
password: Password to try. If None, will try default test password
"""
if password is None:
password = "123456" # Default test password
-
+
password_input = driver.find_element(By.NAME, "loginpassword")
password_input.send_keys(password)
-
+
submit_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
submit_button.click()
-
+
# Wait for page to respond to form submission
# This might either redirect or show login error
time.sleep(1)
@@ -104,11 +104,11 @@ def test_login_page_loads(driver):
"""Test: Login page loads successfully"""
driver.get(f"{BASE_URL}/index.php")
wait_for_page_load(driver)
-
+
# Check that login form is present
password_field = driver.find_element(By.NAME, "loginpassword")
assert password_field, "Password field should be present"
-
+
submit_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
assert submit_button, "Submit button should be present"
@@ -118,295 +118,129 @@ def test_login_redirects_to_devices(driver):
import pytest
password = get_login_password()
# Use password if found, otherwise helper will use default "password"
-
+
driver.get(f"{BASE_URL}/index.php")
wait_for_page_load(driver)
-
+
perform_login(driver, password)
-
+
# Wait for redirect to complete (server-side redirect is usually instant)
time.sleep(1)
-
+
# Should be redirected to devices page
if '/devices.php' not in driver.current_url:
pytest.skip(f"Login failed or not configured. URL: {driver.current_url}")
-
+
assert '/devices.php' in driver.current_url, \
f"Expected redirect to devices.php, got {driver.current_url}"
def test_login_with_deep_link_preserves_hash(driver):
- """Test: Login with deep link (?next=...) preserves the URL fragment hash"""
+ """Test: Login with deep link (?next=...) preserves the URL fragment hash
+
+ When a user logs in from a deep link URL (e.g., ?next=base64(devices.php%23device-123)),
+ they should be redirected to the target page with the hash fragment intact.
+ """
import base64
import pytest
-
+
password = get_login_password()
-
+
# Create a deep link to devices.php#device-123
deep_link_path = "/devices.php#device-123"
encoded_path = base64.b64encode(deep_link_path.encode()).decode()
-
+
# Navigate to login with deep link
driver.get(f"{BASE_URL}/index.php?next={encoded_path}")
wait_for_page_load(driver)
-
+
perform_login(driver, password)
-
- # Wait for JavaScript redirect to complete (up to 5 seconds)
- for i in range(50):
- current_url = driver.current_url
- if '/devices.php' in current_url or '/index.php' not in current_url:
- break
- time.sleep(0.1)
-
+
+ # Wait for redirect to complete (server-side redirect + potential JS handling)
+ time.sleep(2)
+
# Check that we're on the right page with the hash preserved
current_url = driver.current_url
- if '/devices.php' not in current_url:
- pytest.skip(f"Login failed or not configured. URL: {current_url}")
+ print(f"URL after login with deep link: {current_url}")
+ if '/devices.php' not in current_url:
+ pytest.skip(f"Login failed or redirect not configured. URL: {current_url}")
+
+ # Verify the hash fragment is preserved
assert '#device-123' in current_url, f"Expected #device-123 hash in URL, got {current_url}"
-def test_login_with_deep_link_to_device_tree(driver):
- """Test: Login with deep link to network tree page"""
+def test_login_with_deep_link_to_network_page(driver):
+ """Test: Login with deep link to network.php page preserves hash
+
+ User can login with a deep link to the network page (e.g., network.php#settings-panel),
+ and should be redirected to that page with the hash fragment intact.
+ """
import base64
import pytest
-
+
password = get_login_password()
-
+
# Create a deep link to network.php#settings-panel
deep_link_path = "/network.php#settings-panel"
encoded_path = base64.b64encode(deep_link_path.encode()).decode()
-
+
# Navigate to login with deep link
driver.get(f"{BASE_URL}/index.php?next={encoded_path}")
wait_for_page_load(driver)
-
+
perform_login(driver, password)
-
- # Wait for JavaScript redirect to complete (up to 5 seconds)
- for i in range(50):
- current_url = driver.current_url
- if '/network.php' in current_url or '/index.php' not in current_url:
- break
- time.sleep(0.1)
-
+
+ # Wait for redirect to complete
+ time.sleep(2)
+
# Check that we're on the right page with the hash preserved
current_url = driver.current_url
- if '/network.php' not in current_url:
- pytest.skip(f"Login failed or not configured. URL: {current_url}")
+ print(f"URL after login with network.php deep link: {current_url}")
+ if '/network.php' not in current_url:
+ pytest.skip(f"Login failed or redirect not configured. URL: {current_url}")
+
+ # Verify the hash fragment is preserved
assert '#settings-panel' in current_url, f"Expected #settings-panel hash in URL, got {current_url}"
-def test_remember_me_checkbox_present(driver):
- """Test: Remember Me checkbox is present on login form"""
- driver.get(f"{BASE_URL}/index.php")
- wait_for_page_load(driver)
-
- remember_me_checkbox = driver.find_element(By.NAME, "PWRemember")
- assert remember_me_checkbox, "Remember Me checkbox should be present"
-def test_remember_me_login_creates_cookie(driver):
- """Test: Login with Remember Me checkbox creates persistent cookie
-
- Remember Me now uses a simple cookie-based approach (no API calls).
- When logged in with Remember Me checked, a NetAlertX_SaveLogin cookie
- is set with a 7-day expiration. On next page load, the cookie
- automatically authenticates the user without requiring password re-entry.
- """
- import pytest
- password = get_login_password()
-
- driver.get(f"{BASE_URL}/index.php")
- wait_for_page_load(driver)
-
- # Use JavaScript to check the checkbox reliably
- checkbox = driver.find_element(By.NAME, "PWRemember")
- driver.execute_script("arguments[0].checked = true;", checkbox)
- driver.execute_script("arguments[0].click();", checkbox) # Trigger any change handlers
-
- # Verify checkbox is actually checked after clicking
- time.sleep(0.5)
- is_checked = checkbox.is_selected()
- print(f"✓ Checkbox checked via JavaScript: {is_checked}")
-
- if not is_checked:
- pytest.skip("Could not check Remember Me checkbox")
-
- perform_login(driver, password)
-
- # Wait for redirect
- time.sleep(2)
-
- # Main assertion: login should work with Remember Me checked
- assert '/devices.php' in driver.current_url or '/network.php' in driver.current_url, \
- f"Login with Remember Me should redirect to app, got {driver.current_url}"
-
- # Secondary check: verify Remember Me cookie (NetAlertX_SaveLogin) was set
- cookies = driver.get_cookies()
- cookie_names = [cookie['name'] for cookie in cookies]
-
- print(f"Cookies found: {cookie_names}")
-
- # Check for the Remember Me cookie
- remember_me_cookie = None
- for cookie in cookies:
- if cookie['name'] == 'NetAlertX_SaveLogin':
- remember_me_cookie = cookie
- break
-
- if remember_me_cookie:
- print(f"✓ Remember Me cookie successfully set: {remember_me_cookie['name']}")
- print(f" Value (truncated): {remember_me_cookie['value'][:32]}...")
- print(f" Expires: {remember_me_cookie.get('expiry', 'Not set')}")
- print(f" HttpOnly: {remember_me_cookie.get('httpOnly', False)}")
- print(f" Secure: {remember_me_cookie.get('secure', False)}")
- print(f" SameSite: {remember_me_cookie.get('sameSite', 'Not set')}")
- else:
- print("ℹ Remember Me cookie (NetAlertX_SaveLogin) not set in test environment")
- print(" This is expected if Remember Me checkbox was not properly checked")
-def test_remember_me_with_deep_link_preserves_hash(driver):
- """Test: Remember Me persistent login preserves URL fragments via cookies
-
- Remember Me now uses cookies only (no API validation required):
- 1. Login with Remember Me checkbox → NetAlertX_SaveLogin cookie set
- 2. Browser stores cookie persistently (7 days)
- 3. On next page load, cookie presence auto-authenticates user
- 4. Deep link with hash fragment preserved through redirect
-
- This simulates browser restart by clearing the session cookie (keeping Remember Me cookie).
- """
- import base64
- import pytest
-
- password = get_login_password()
-
- # First, set up a Remember Me session
- driver.get(f"{BASE_URL}/index.php")
- wait_for_page_load(driver)
-
- # Use JavaScript to check the checkbox reliably
- checkbox = driver.find_element(By.NAME, "PWRemember")
- driver.execute_script("arguments[0].checked = true;", checkbox)
- driver.execute_script("arguments[0].click();", checkbox) # Trigger any change handlers
-
- # Verify checkbox is actually checked
- time.sleep(0.5)
- is_checked = checkbox.is_selected()
- print(f"Checkbox checked for Remember Me test: {is_checked}")
-
- if not is_checked:
- pytest.skip("Could not check Remember Me checkbox")
-
- perform_login(driver, password)
-
- # Wait and check if login succeeded
- time.sleep(2)
- if '/index.php' in driver.current_url and '/devices.php' not in driver.current_url:
- pytest.skip(f"Initial login failed. Cannot test Remember Me.")
-
- # Verify Remember Me cookie was set
- cookies = driver.get_cookies()
- remember_me_found = False
- for cookie in cookies:
- if cookie['name'] == 'NetAlertX_SaveLogin':
- remember_me_found = True
- print(f"✓ Remember Me cookie found: {cookie['name']}")
- break
-
- if not remember_me_found:
- pytest.skip("Remember Me cookie was not set during login")
-
- # Simulate browser restart by clearing session cookies (but keep Remember Me cookie)
- # Get all cookies, filter out session-related ones, keep Remember Me cookie
- remember_me_cookie = None
- for cookie in cookies:
- if cookie['name'] == 'NetAlertX_SaveLogin':
- remember_me_cookie = cookie
- break
-
- # Clear all cookies
- driver.delete_all_cookies()
-
- # Restore Remember Me cookie to simulate browser restart
- if remember_me_cookie:
- try:
- driver.add_cookie({
- 'name': remember_me_cookie['name'],
- 'value': remember_me_cookie['value'],
- 'path': remember_me_cookie.get('path', '/'),
- 'secure': remember_me_cookie.get('secure', False),
- 'domain': remember_me_cookie.get('domain', None),
- 'httpOnly': remember_me_cookie.get('httpOnly', False),
- 'sameSite': remember_me_cookie.get('sameSite', 'Strict')
- })
- except Exception as e:
- pytest.skip(f"Could not restore Remember Me cookie: {e}")
-
- # Now test deep link with Remember Me cookie (simulated browser restart)
- deep_link_path = "/devices.php#device-456"
- encoded_path = base64.b64encode(deep_link_path.encode()).decode()
-
- driver.get(f"{BASE_URL}/index.php?next={encoded_path}")
- wait_for_page_load(driver)
-
- # Wait a moment for Remember Me cookie validation and redirect
- time.sleep(2)
-
- # Check current URL - should be on devices with hash
- current_url = driver.current_url
- print(f"Current URL after Remember Me auto-login: {current_url}")
-
- # Verify we're logged in and on the right page
- assert '/index.php' not in current_url or '/devices.php' in current_url or '/network.php' in current_url, \
- f"Expected app page after Remember Me auto-login, got {current_url}"
-
def test_login_without_next_parameter(driver):
"""Test: Login without ?next parameter defaults to devices.php"""
import pytest
password = get_login_password()
-
+
driver.get(f"{BASE_URL}/index.php")
wait_for_page_load(driver)
-
+
perform_login(driver, password)
-
+
# Wait for redirect to complete
time.sleep(1)
-
+
# Should redirect to default devices page
current_url = driver.current_url
if '/devices.php' not in current_url:
pytest.skip(f"Login failed or not configured. URL: {current_url}")
-
+
assert '/devices.php' in current_url, f"Expected default redirect to devices.php, got {current_url}"
-def test_url_hash_hidden_input_populated(driver):
- """Test: URL fragment hash is populated in hidden url_hash input field"""
- import base64
+def test_url_hash_hidden_input_present(driver):
+ """Test: URL fragment hash field is present in login form
- # Create a deep link
- deep_link_path = "/devices.php#device-789"
- encoded_path = base64.b64encode(deep_link_path.encode()).decode()
-
- # Navigate to login with deep link
- driver.get(f"{BASE_URL}/index.php?next={encoded_path}")
+ The hidden url_hash input field is used to capture and preserve
+ URL hash fragments during form submission and redirect.
+ """
+ driver.get(f"{BASE_URL}/index.php")
wait_for_page_load(driver)
-
- # Wait a bit for JavaScript to execute and populate the hash
- time.sleep(1)
-
- # Get the hidden input value - note: this tests JavaScript functionality
+
+ # Verify the hidden input field exists
url_hash_input = driver.find_element(By.ID, "url_hash")
- url_hash_value = url_hash_input.get_attribute("value")
-
- # The JavaScript should have populated this with window.location.hash
- # However, since we're navigating to index.php, the hash won't be present at page load
- # So this test verifies the mechanism exists and would work
assert url_hash_input, "Hidden url_hash input field should be present"
+ assert url_hash_input.get_attribute("type") == "hidden", "url_hash should be a hidden input field"