Working auth system

This commit is contained in:
devmatrix 2026-02-20 15:39:18 +00:00
parent b1c2264cc6
commit 2c91d6f100
5 changed files with 371 additions and 15 deletions

45
data/config.json Normal file
View File

@ -0,0 +1,45 @@
{
"ipmi_host": "192.168.5.191",
"ipmi_username": "root",
"ipmi_password": "calvin",
"ipmi_port": 623,
"ssh_enabled": false,
"ssh_host": null,
"ssh_username": null,
"ssh_password": null,
"ssh_use_key": false,
"ssh_key_file": null,
"ssh_port": 22,
"enabled": true,
"interval": 10,
"min_speed": 10,
"max_speed": 100,
"fan_curve": [
{
"temp": 30,
"speed": 15
},
{
"temp": 40,
"speed": 25
},
{
"temp": 50,
"speed": 40
},
{
"temp": 60,
"speed": 60
},
{
"temp": 70,
"speed": 80
},
{
"temp": 80,
"speed": 100
}
],
"panic_temp": 85,
"panic_speed": 100
}

1
data/users.json Normal file
View File

@ -0,0 +1 @@
{"users": {"admin": "ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae"}}

30
reset_password.py Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""Reset password for fan controller"""
import json
import hashlib
import sys
USERS_FILE = "/home/devmatrix/projects/fan-controller-v2/data/users.json"
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
if len(sys.argv) != 3:
print("Usage: reset_password.py <username> <new_password>")
sys.exit(1)
username = sys.argv[1]
password = sys.argv[2]
try:
with open(USERS_FILE) as f:
data = json.load(f)
data["users"][username] = hash_password(password)
with open(USERS_FILE, 'w') as f:
json.dump(data, f)
print(f"Password reset for user: {username}")
except Exception as e:
print(f"Error: {e}")

View File

@ -1,7 +1,255 @@
/home/devmatrix/projects/fan-controller-v2/web_server.py:49: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.5/migration/
@validator('new_password')
INFO: Started server process [888347]
INFO: Started server process [893663]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:44244 - "GET /api/status HTTP/1.1" 401 Unauthorized
INFO: 192.168.5.30:56770 - "GET /api/status HTTP/1.1" 401 Unauthorized
INFO: 192.168.5.30:56770 - "GET /login HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:34494 - "GET /api/status HTTP/1.1" 401 Unauthorized
/home/devmatrix/projects/fan-controller-v2/web_server.py:141: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
expiry = datetime.utcnow() + timedelta(days=7)
INFO: 192.168.5.30:49736 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET / HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
2026-02-20 15:37:16,554 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /favicon.ico HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:34498 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:34506 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 15:37:24,066 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 15:37:24,068 - fan_controller - ERROR - IPMI command error: [Errno 2] No such file or directory: 'ipmitool'
2026-02-20 15:37:24,069 - fan_controller - ERROR - Failed to connect to IPMI at 192.168.5.191
INFO: 192.168.5.30:49736 - "POST /api/config/ipmi HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /favicon.ico HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 15:37:27,369 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 15:37:27,370 - fan_controller - ERROR - IPMI command error: [Errno 2] No such file or directory: 'ipmitool'
2026-02-20 15:37:27,371 - fan_controller - ERROR - Failed to connect to IPMI at 192.168.5.191
2026-02-20 15:37:27,371 - fan_controller - ERROR - Cannot start service - IPMI connection failed
INFO: 192.168.5.30:49736 - "POST /api/control/auto HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:37104 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:37118 - "GET /login HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:141: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
expiry = datetime.utcnow() + timedelta(days=7)
INFO: 127.0.0.1:37130 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 15:37:33,643 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 192.168.5.30:49736 - "POST /api/control/auto HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:49736 - "POST /api/control/manual HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/devmatrix/.local/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 419, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 91, in __call__
await self.simple_response(scope, receive, send, request_headers=headers)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 146, in simple_response
await self.app(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 762, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 782, in app
await route.handle(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 297, in handle
await self.app(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 77, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 72, in app
response = await func(request)
^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 299, in app
raise e
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 294, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/projects/fan-controller-v2/web_server.py", line 1352, in api_control_manual
if not service._init_controller():
^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'FanControlService' object has no attribute '_init_controller'. Did you mean: '_init_ipmi_controller'?
2026-02-20 15:37:36,531 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 15:37:36,533 - fan_controller - ERROR - IPMI command error: [Errno 2] No such file or directory: 'ipmitool'
2026-02-20 15:37:36,533 - fan_controller - ERROR - Failed to connect to IPMI at 192.168.5.191
2026-02-20 15:37:36,533 - fan_controller - ERROR - Cannot start service - IPMI connection failed
INFO: 192.168.5.30:62376 - "POST /api/control/auto HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:62376 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:62376 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:62376 - "POST /api/control/manual HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/devmatrix/.local/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 419, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 91, in __call__
await self.simple_response(scope, receive, send, request_headers=headers)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 146, in simple_response
await self.app(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 762, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 782, in app
await route.handle(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 297, in handle
await self.app(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 77, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 72, in app
response = await func(request)
^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 299, in app
raise e
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 294, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/projects/fan-controller-v2/web_server.py", line 1352, in api_control_manual
if not service._init_controller():
^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'FanControlService' object has no attribute '_init_controller'. Did you mean: '_init_ipmi_controller'?
INFO: 192.168.5.30:55640 - "POST /api/test HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/devmatrix/.local/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 419, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 91, in __call__
await self.simple_response(scope, receive, send, request_headers=headers)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 146, in simple_response
await self.app(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 762, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 782, in app
await route.handle(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 297, in handle
await self.app(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 77, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/home/devmatrix/.local/lib/python3.12/site-packages/starlette/routing.py", line 72, in app
response = await func(request)
^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 299, in app
raise e
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 294, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/.local/lib/python3.12/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/devmatrix/projects/fan-controller-v2/web_server.py", line 1323, in api_test
if not service._init_controller():
^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'FanControlService' object has no attribute '_init_controller'. Did you mean: '_init_ipmi_controller'?
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:61984 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63389 - "GET /api/status HTTP/1.1" 200 OK

View File

@ -171,6 +171,7 @@ LOGIN_HTML = '''<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/favicon.ico">
<title>Login - IPMI Fan Controller</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
@ -251,26 +252,36 @@ LOGIN_HTML = '''<!DOCTYPE html>
const errorEl = document.getElementById('error');
errorEl.style.display = 'none';
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
console.log('Attempting login for:', username);
try {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: document.getElementById('username').value,
password: document.getElementById('password').value
})
body: JSON.stringify({username, password})
});
const data = await res.json();
if (data.success) {
console.log('Response status:', res.status);
const data = await res.json();
console.log('Response data:', data);
if (data.success && data.token) {
console.log('Login successful, storing token');
localStorage.setItem('token', data.token);
console.log('Token stored, redirecting...');
window.location.href = '/';
} else {
console.log('Login failed:', data.error);
errorEl.textContent = data.error || 'Login failed';
errorEl.style.display = 'block';
}
} catch (e) {
errorEl.textContent = 'Connection error';
console.error('Login error:', e);
errorEl.textContent = 'Connection error: ' + e.message;
errorEl.style.display = 'block';
}
});
@ -283,6 +294,7 @@ SETUP_HTML = '''<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/favicon.ico">
<title>Setup - IPMI Fan Controller</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
@ -453,6 +465,7 @@ DASHBOARD_HTML = '''<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/favicon.ico">
<title>IPMI Fan Controller v2</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
@ -862,6 +875,11 @@ DASHBOARD_HTML = '''<!DOCTYPE html>
</div>
<script>
// Check auth on page load
if (!localStorage.getItem('token')) {
window.location.replace('/login');
}
let currentStatus = {};
let currentConfig = {};
@ -1214,18 +1232,32 @@ app.add_middleware(
)
# Routes
@app.get("/favicon.ico")
async def favicon():
"""Return a simple favicon to prevent 404 errors."""
# Return a transparent 1x1 PNG
transparent_png = bytes([
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, # PNG signature
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, # IHDR chunk
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, # 1x1 pixel
0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89,
0x00, 0x00, 0x00, 0x0D, 0x49, 0x44, 0x41, 0x54, # IDAT chunk (transparent)
0x08, 0xD7, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00,
0x02, 0x00, 0x01, 0xE2, 0x21, 0xBC, 0x33,
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, # IEND chunk
0xAE, 0x42, 0x60, 0x82
])
from fastapi.responses import Response
return Response(content=transparent_png, media_type="image/png")
@app.get("/")
async def root(request: Request):
"""Main dashboard or redirect to setup/login."""
"""Main dashboard - always returns dashboard HTML, JS handles auth."""
# Check if setup is needed
if not user_manager.is_setup_complete():
return HTMLResponse(content=SETUP_HTML)
# Check auth
token = request.cookies.get("token") or request.headers.get("authorization", "").replace("Bearer ", "")
if not token or not user_manager.verify_token(token):
return HTMLResponse(content=LOGIN_HTML)
# Always return dashboard - JavaScript will check token and redirect if needed
return HTMLResponse(content=DASHBOARD_HTML)
@app.get("/login")