1
- from fastapi import FastAPI , Request , Form , status , Depends
1
+ import os
2
+ import secrets
3
+ from fastapi import FastAPI , Request , Form , status , Depends , HTTPException
2
4
from fastapi .responses import HTMLResponse , JSONResponse
3
5
from fastapi .templating import Jinja2Templates
4
6
from fastapi .staticfiles import StaticFiles
5
7
from fastapi .security import HTTPBasic , HTTPBasicCredentials
6
- import secrets
7
8
8
9
from . import dbtools
9
10
13
14
templates = Jinja2Templates (directory = "app/templates" )
14
15
app .mount ("/static" , StaticFiles (directory = "app/static" ), name = "static" )
15
16
16
- USERNAME = "admin"
17
- PASSWORD = "change_this" # Set a strong secret in prod
17
+ USERNAME = os .getenv ("APP_ADMIN_USER" , "admin" )
18
+ PASSWORD = os .getenv ("APP_ADMIN_PASS" , None )
19
+
20
+ def sanitize_error (error ):
21
+ # Prevent detailed DB error/trace from leaking in API response
22
+ if not error :
23
+ return None
24
+ return "Backend error. See server logs for details." if "Traceback" in error or "Exception" in error else error
18
25
19
26
def get_current_user (credentials : HTTPBasicCredentials = Depends (security )):
20
27
correct_username = secrets .compare_digest (credentials .username , USERNAME )
21
28
correct_password = secrets .compare_digest (credentials .password , PASSWORD )
22
29
if not (correct_username and correct_password ):
23
- raise JSONResponse (status_code = status .HTTP_401_UNAUTHORIZED , content = { "detail" : " Invalid credentials"} )
30
+ raise HTTPException (status_code = status .HTTP_401_UNAUTHORIZED , detail = " Invalid credentials" )
24
31
return credentials .username
25
32
26
33
@app .get ("/" , response_class = HTMLResponse )
27
34
def form (request : Request , user : str = Depends (get_current_user )):
28
- # No results on initial GET
29
35
return templates .TemplateResponse ("index.html" , {"request" : request , "user" : user , "result" : None })
30
36
31
37
@app .post ("/test-latency" , response_class = HTMLResponse )
@@ -43,6 +49,27 @@ def test_latency(
43
49
custom_sql : str = Form ("" ),
44
50
user : str = Depends (get_current_user )
45
51
):
52
+ custom_sql = (custom_sql or "" ).strip ()
53
+ if custom_sql and len (custom_sql ) > 5000 :
54
+ result = {
55
+ "success" : False ,
56
+ "error" : "SQL query too long (limit 5000 chars)" ,
57
+ "latency_stats" : {}, "details" : []
58
+ }
59
+ return templates .TemplateResponse ("index.html" , {
60
+ "request" : request ,
61
+ "user" : user ,
62
+ "result" : result ,
63
+ "dbtype" : dbtype ,
64
+ "host" : host ,
65
+ "port" : port ,
66
+ "username" : username ,
67
+ "database" : database ,
68
+ "url" : url ,
69
+ "interval" : interval ,
70
+ "period" : period ,
71
+ "custom_sql" : custom_sql
72
+ })
46
73
result = dbtools .run_latency_test (
47
74
dbtype = dbtype ,
48
75
host = host ,
@@ -55,6 +82,9 @@ def test_latency(
55
82
period = period ,
56
83
custom_sql = custom_sql
57
84
)
85
+ # Sanitize error for UI as well
86
+ if not result .get ("success" ) and result .get ("error" ):
87
+ result ["error" ] = sanitize_error (result ["error" ])
58
88
return templates .TemplateResponse ("index.html" , {
59
89
"request" : request ,
60
90
"user" : user ,
@@ -85,16 +115,26 @@ def api_test_latency(
85
115
credentials : HTTPBasicCredentials = Depends (security )
86
116
):
87
117
get_current_user (credentials )
88
- result = dbtools .run_latency_test (
89
- dbtype = dbtype ,
90
- host = host ,
91
- port = port ,
92
- username = username ,
93
- password = password ,
94
- database = database ,
95
- url = url ,
96
- interval = interval ,
97
- period = period ,
98
- custom_sql = custom_sql
99
- )
100
- return JSONResponse (result )
118
+ custom_sql = (custom_sql or "" ).strip ()
119
+ if custom_sql and len (custom_sql ) > 5000 :
120
+ return JSONResponse ({"success" : False , "error" : "SQL query too long (limit 5000 chars)" })
121
+ try :
122
+ result = dbtools .run_latency_test (
123
+ dbtype = dbtype ,
124
+ host = host ,
125
+ port = port ,
126
+ username = username ,
127
+ password = password ,
128
+ database = database ,
129
+ url = url ,
130
+ interval = interval ,
131
+ period = period ,
132
+ custom_sql = custom_sql
133
+ )
134
+ # Sanitize error
135
+ if not result .get ("success" ) and result .get ("error" ):
136
+ result ["error" ] = sanitize_error (result ["error" ])
137
+ return JSONResponse (result )
138
+ except Exception as e :
139
+ # Never leak stack trace
140
+ return JSONResponse ({"success" : False , "error" : "Internal error. See server logs." })
0 commit comments