feat: add optional dev mode and analyzer tool upload role

This commit is contained in:
Luftmensch
2026-09-03 16:27:05 +07:00
parent e528419f9a
commit 0249b9eda9
5 changed files with 68 additions and 13 deletions
+16 -5
View File
@@ -290,7 +290,7 @@
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">🏠 Trang chủ</a>
<a href="{{ url_for('my_files') }}" class="nav-tab"
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
{% if user_info and user_info.role == 'admin' %}
{% if user_info and user_info.role in ['admin', 'analyzer'] %}
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">⚙️ Quản trị</a>
{% endif %}
@@ -403,11 +403,13 @@
<div style="display: inline-flex; gap: 6px; align-items: center;">
<a href="{{ url_for('download_tool', version_key=tool.version_key) }}"
class="btn btn-secondary" style="padding: 5px 10px; font-size: 12px; white-space: nowrap;">⬇️ Tải</a>
{% if user_info and user_info.role == 'admin' %}
<form action="tools/delete/{{ tool.version_key }}" method="post" style="display: inline; margin: 0;"
onsubmit="return confirm('Bạn có chắc muốn xóa công cụ này?')">
<button type="submit" class="btn btn-danger"
style="padding: 5px 10px; font-size: 12px; white-space: nowrap;">🗑️ Xóa</button>
</form>
{% endif %}
</div>
{% else %}
<span style="color: var(--text-muted);">-</span>
@@ -425,6 +427,7 @@
{% endif %}
</div>
{% if user_info and user_info.role == 'admin' %}
<!-- Upload Documentation Form -->
<div class="card">
<div class="card-header">
@@ -516,6 +519,9 @@
{% endif %}
</div>
{% endif %}
{% if user_info and user_info.role == 'admin' %}
<!-- Email Notification Settings -->
<div class="card">
<div class="card-header">
@@ -583,6 +589,9 @@
</div>
</div>
{% endif %}
{% if user_info and user_info.role == 'admin' %}
<!-- User Email Stats -->
<div class="card">
<div class="card-header">
@@ -623,15 +632,17 @@
</div>
{% endif %}
</div>
{% endif %}
</div>
<script>
// Drag and drop support
const uploadForm = document.getElementById('uploadForm');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadForm.addEventListener(eventName, preventDefaults, false);
});
if (uploadForm) {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadForm.addEventListener(eventName, preventDefaults, false);
});
}
function preventDefaults(e) {
e.preventDefault();
+1 -1
View File
@@ -980,7 +980,7 @@
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">🏠 Trang chủ</a>
<a href="{{ url_for('my_files') }}" class="nav-tab"
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
{% if user_info and user_info.role == 'admin' %}
{% if user_info and user_info.role in ['admin', 'analyzer'] %}
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">⚙️ Quản trị</a>
{% endif %}
+47 -6
View File
@@ -53,6 +53,22 @@ def _email_enabled() -> bool:
app = FastAPI(title="Audit Hardening Tool", redirect_slashes=False)
security = HTTPBasic()
# Development mode: allows unauthenticated file upload only.
# Keep disabled by default; enable explicitly with AUDIT_DEV_MODE=true.
DEV_MODE = os.getenv("AUDIT_DEV_MODE", "false").strip().lower() in {"1", "true", "yes", "on"}
DEV_USER = {
"username": "dev_guest",
"full_name": "Development Guest",
"role": "dev_guest",
"unit_id": "",
"unit_name": "Development",
"auth_method": "dev_mode",
"can_view_outputs": True,
"can_download": True,
}
if DEV_MODE:
print(Fore.YELLOW + "[DEV MODE] Unauthenticated file upload is ENABLED; admin routes remain protected" + Fore.RESET)
# Add middleware to handle X-Forwarded-* headers from reverse proxy (Caddy)
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
@@ -1078,10 +1094,16 @@ async def logout(request: Request):
@app.get("/", response_class=HTMLResponse, name="index")
async def index(request: Request):
# Kiểm tra đăng nhập bắt buộc
session_id = get_or_create_session(request)
user_info = sessions.get(session_id, {}).get('user_info')
if not user_info and DEV_MODE:
user_info = dict(DEV_USER)
sessions[session_id]['username'] = DEV_USER['username']
sessions[session_id]['user_info'] = user_info
sessions[session_id]['unit_id'] = ''
sessions[session_id]['unit_name'] = 'Development'
if not user_info:
# Chưa đăng nhập → redirect về trang login
response = RedirectResponse(url=str(request.url_for('login')), status_code=303)
@@ -1639,9 +1661,15 @@ async def api_upload_async(
os_type: str = Form(...),
encrypted_files: List[UploadFile] = File(...)
):
# Bắt buộc đăng nhập để xác định unit_id người upload
# Authenticated users and explicit dev mode may upload; admin routes stay protected.
session_id = get_or_create_session(request)
user_info = sessions.get(session_id, {}).get('user_info')
if not user_info and DEV_MODE:
user_info = dict(DEV_USER)
sessions[session_id]['username'] = DEV_USER['username']
sessions[session_id]['user_info'] = user_info
sessions[session_id]['unit_id'] = ''
sessions[session_id]['unit_name'] = 'Development'
if not user_info:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -1764,6 +1792,11 @@ async def upload_files(
# Lấy hoặc tạo session
session_id = get_or_create_session(request, response)
if DEV_MODE and not sessions.get(session_id, {}).get('user_info'):
sessions[session_id]['username'] = DEV_USER['username']
sessions[session_id]['user_info'] = dict(DEV_USER)
sessions[session_id]['unit_id'] = ''
sessions[session_id]['unit_name'] = 'Development'
# Bắt đầu đo thời gian xử lý
start_time = time.time()
@@ -2530,16 +2563,24 @@ async def download_doc(doc_key: str):
# ============== ADMIN ROUTES ==============
def require_admin_session(request: Request):
"""Check session auth and return admin user or None"""
"""Check session auth and return admin user or None."""
user = get_session_user(request)
if user and user.get('role') == 'admin':
return user
return None
def require_tool_manager_session(request: Request):
"""Allow admin or analyzer to access the tool upload page."""
user = get_session_user(request)
if user and user.get('role') in {'admin', 'analyzer'}:
return user
return None
@app.get("/admin/tools", response_class=HTMLResponse, name="admin_tools")
async def admin_tools_page(request: Request, msg: str = None, msg_type: str = "success"):
"""Admin page for managing tools - requires session authentication"""
user = require_admin_session(request)
user = require_tool_manager_session(request)
if not user:
return RedirectResponse(url=request.url_for('login'), status_code=303)
@@ -2789,8 +2830,8 @@ async def admin_upload_tool(
tool_name: str = Form(...),
tool_file: UploadFile = File(...)
):
"""Upload a new tool - requires admin session authentication"""
user = require_admin_session(request)
"""Upload a new tool - requires admin or analyzer session."""
user = require_tool_manager_session(request)
if not user:
return RedirectResponse(url=request.url_for('login'), status_code=303)