1- # -*- coding: utf-8 -*-
21"""System configuration endpoints."""
32
43from __future__ import annotations
54
65import logging
76import os
87
9- from fastapi import APIRouter , Depends , HTTPException , Query
8+ from fastapi import APIRouter , Depends , HTTPException , Query , Request
109
1110from api .deps import get_system_config_service
1211from api .v1 .schemas .common import ErrorResponse
2928 ValidateSystemConfigRequest ,
3029 ValidateSystemConfigResponse ,
3130)
31+ from src .auth import COOKIE_NAME , is_auth_enabled , refresh_auth_state , verify_session
3232from src .services .system_config_service import (
3333 ConfigConflictError ,
3434 ConfigImportError ,
4141router = APIRouter ()
4242
4343
44- def _ensure_desktop_mode () -> None :
45- """Restrict desktop backup/restore endpoints to desktop runtime only."""
46- if os .getenv ("DSA_DESKTOP_MODE" , "" ).strip ().lower () != "true" :
47- raise HTTPException (
44+ class EnvBackupAccessDenied (Exception ):
45+ """Raised when raw `.env` backup access is not allowed for this request."""
46+
47+ def __init__ (self , * , status_code : int , message : str ) -> None :
48+ super ().__init__ (message )
49+ self .status_code = status_code
50+ self .message = message
51+
52+
53+ def _allow_env_backup_access (request : Request ) -> None :
54+ """Gate raw .env backup/restore to explicit secure modes.
55+
56+ - Desktop runtime keeps existing local behavior via DSA_DESKTOP_MODE.
57+ - Non-desktop runtime must have admin auth enabled and a valid session.
58+ """
59+ if os .getenv ("DSA_DESKTOP_MODE" ) == "true" :
60+ return
61+
62+ refresh_auth_state ()
63+ if not is_auth_enabled ():
64+ raise EnvBackupAccessDenied (
4865 status_code = 403 ,
49- detail = {
50- "error" : "desktop_only_feature" ,
51- "message" : "This endpoint is only available in desktop mode" ,
52- },
66+ message = "System config backup is disabled; enable admin authentication first" ,
5367 )
5468
69+ cookie_val = request .cookies .get (COOKIE_NAME )
70+ if cookie_val and verify_session (cookie_val ):
71+ return
72+
73+ raise EnvBackupAccessDenied (
74+ status_code = 401 ,
75+ message = "System config backup requires a valid admin session" ,
76+ )
77+
78+
79+ def _raise_env_backup_access_error (exc : EnvBackupAccessDenied ) -> None :
80+ raise HTTPException (
81+ status_code = exc .status_code ,
82+ detail = {
83+ "error" : "env_backup_access_denied" ,
84+ "message" : exc .message ,
85+ },
86+ )
87+
5588
5689@router .get (
5790 "/config" ,
@@ -170,29 +203,35 @@ def update_system_config(
170203 "/config/export" ,
171204 response_model = ExportSystemConfigResponse ,
172205 responses = {
173- 200 : {"description" : "Desktop env exported" },
206+ 200 : {"description" : "Env exported" },
174207 401 : {"description" : "Unauthorized" , "model" : ErrorResponse },
175- 403 : {"description" : "Desktop mode only " , "model" : ErrorResponse },
208+ 403 : {"description" : "Env backup disabled " , "model" : ErrorResponse },
176209 500 : {"description" : "Internal server error" , "model" : ErrorResponse },
177210 },
178- summary = "Export desktop env backup" ,
179- description = "Desktop-only endpoint that returns the raw saved .env content." ,
211+ summary = "Export env backup" ,
212+ description = "Return the raw saved .env content for configuration backup ." ,
180213)
181- def export_desktop_system_config (
214+ def export_system_config (
215+ request : Request ,
182216 service : SystemConfigService = Depends (get_system_config_service ),
183217) -> ExportSystemConfigResponse :
184- """Export the active `.env` file for desktop backup."""
185- _ensure_desktop_mode ()
218+ """Export the active `.env` file for config backup."""
186219 try :
187- payload = service .export_desktop_env ()
220+ _allow_env_backup_access (request )
221+ except EnvBackupAccessDenied as exc :
222+ logger .warning ("System config export blocked: %s" , exc )
223+ _raise_env_backup_access_error (exc )
224+
225+ try :
226+ payload = service .export_env ()
188227 return ExportSystemConfigResponse .model_validate (payload )
189228 except Exception as exc :
190- logger .error ("Failed to export desktop system configuration: %s" , exc , exc_info = True )
229+ logger .error ("Failed to export system configuration: %s" , exc , exc_info = True )
191230 raise HTTPException (
192231 status_code = 500 ,
193232 detail = {
194233 "error" : "internal_error" ,
195- "message" : "Failed to export desktop system configuration" ,
234+ "message" : "Failed to export system configuration" ,
196235 },
197236 )
198237
@@ -201,7 +240,7 @@ def export_desktop_system_config(
201240 "/config/import" ,
202241 response_model = UpdateSystemConfigResponse ,
203242 responses = {
204- 200 : {"description" : "Desktop env imported" },
243+ 200 : {"description" : "Env imported" },
205244 400 : {
206245 "description" : "Import failed" ,
207246 "content" : {
@@ -216,21 +255,27 @@ def export_desktop_system_config(
216255 },
217256 },
218257 401 : {"description" : "Unauthorized" , "model" : ErrorResponse },
219- 403 : {"description" : "Desktop mode only " , "model" : ErrorResponse },
258+ 403 : {"description" : "Env backup disabled " , "model" : ErrorResponse },
220259 409 : {"description" : "Version conflict" , "model" : SystemConfigConflictResponse },
221260 500 : {"description" : "Internal server error" , "model" : ErrorResponse },
222261 },
223- summary = "Import desktop env backup" ,
224- description = "Desktop-only endpoint that merges raw .env text into the saved configuration." ,
262+ summary = "Import env backup" ,
263+ description = "Merge raw .env text into the saved configuration with config version conflict protection ." ,
225264)
226- def import_desktop_system_config (
265+ def import_system_config (
227266 request : ImportSystemConfigRequest ,
267+ request_obj : Request ,
228268 service : SystemConfigService = Depends (get_system_config_service ),
229269) -> UpdateSystemConfigResponse :
230- """Import a desktop `.env` backup into the active config."""
231- _ensure_desktop_mode ()
270+ """Import a `.env` backup into the active config."""
271+ try :
272+ _allow_env_backup_access (request_obj )
273+ except EnvBackupAccessDenied as exc :
274+ logger .warning ("System config import blocked: %s" , exc )
275+ _raise_env_backup_access_error (exc )
276+
232277 try :
233- payload = service .import_desktop_env (
278+ payload = service .import_env (
234279 config_version = request .config_version ,
235280 content = request .content ,
236281 reload_now = request .reload_now ,
@@ -263,12 +308,12 @@ def import_desktop_system_config(
263308 },
264309 )
265310 except Exception as exc :
266- logger .error ("Failed to import desktop system configuration: %s" , exc , exc_info = True )
311+ logger .error ("Failed to import system configuration: %s" , exc , exc_info = True )
267312 raise HTTPException (
268313 status_code = 500 ,
269314 detail = {
270315 "error" : "internal_error" ,
271- "message" : "Failed to import desktop system configuration" ,
316+ "message" : "Failed to import system configuration" ,
272317 },
273318 )
274319
0 commit comments