Audit trail
This commit is contained in:
@@ -22,6 +22,7 @@ from backend.app.services.email_verification import create_verification_token
|
||||
from backend.app.services.theme_service import THEMES, get_enabled_themes, save_enabled_themes
|
||||
from backend.app.services.secret_store import encrypt_secret
|
||||
from backend.app.core.config import settings
|
||||
from backend.app.services.audit_service import record_audit_event
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -95,7 +96,7 @@ def list_users(_: dict = Depends(require_admin)):
|
||||
|
||||
|
||||
@router.post('/users/{user_id}/otp/reset')
|
||||
def reset_user_otp(user_id: str, _: dict = Depends(require_admin)):
|
||||
def reset_user_otp(user_id: str, current_user: dict = Depends(require_admin)):
|
||||
with get_connection() as conn:
|
||||
target = conn.execute('SELECT id FROM users WHERE id = ?', (user_id,)).fetchone()
|
||||
if target is None:
|
||||
@@ -106,11 +107,12 @@ def reset_user_otp(user_id: str, _: dict = Depends(require_admin)):
|
||||
)
|
||||
conn.execute('DELETE FROM otp_recovery_codes WHERE user_id = ?', (user_id,))
|
||||
conn.commit()
|
||||
record_audit_event(current_user['id'], 'otp_reset', 'user', user_id)
|
||||
return {'status': 'otp_reset', 'enabled': False, 'user_id': user_id}
|
||||
|
||||
|
||||
@router.post('/users', status_code=201)
|
||||
def create_user(payload: AdminUserCreate, _: dict = Depends(require_admin)):
|
||||
def create_user(payload: AdminUserCreate, current_user: dict = Depends(require_admin)):
|
||||
username = payload.username.strip()
|
||||
email = payload.email.strip()
|
||||
if not username or not email or len(payload.password) < 8:
|
||||
@@ -141,6 +143,7 @@ def create_user(payload: AdminUserCreate, _: dict = Depends(require_admin)):
|
||||
send_verification_email(row['email'], row['username'], verification_url)
|
||||
except Exception as error:
|
||||
raise HTTPException(status_code=503, detail=f'User created but verification email could not be sent: {error}') from error
|
||||
record_audit_event(current_user['id'], 'user_created', 'user', row['id'], details={'is_admin': bool(payload.is_admin)})
|
||||
return public_user(row)
|
||||
|
||||
|
||||
@@ -166,19 +169,21 @@ def get_admin_themes(_: dict = Depends(require_admin)):
|
||||
|
||||
|
||||
@router.put('/themes')
|
||||
def update_admin_themes(payload: AdminThemesUpdate, _: dict = Depends(require_admin)):
|
||||
def update_admin_themes(payload: AdminThemesUpdate, current_user: dict = Depends(require_admin)):
|
||||
try:
|
||||
enabled = save_enabled_themes(payload.themes)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=422, detail=str(error)) from error
|
||||
record_audit_event(current_user['id'], 'themes_updated', 'application', details={'themes': enabled})
|
||||
return {'themes': THEMES, 'enabled': enabled}
|
||||
|
||||
|
||||
@router.put('/smtp')
|
||||
def update_admin_smtp_settings(payload: AdminSmtpUpdate, _: dict = Depends(require_admin)):
|
||||
def update_admin_smtp_settings(payload: AdminSmtpUpdate, current_user: dict = Depends(require_admin)):
|
||||
current = get_smtp_settings()
|
||||
values = validate_smtp_values(payload, current)
|
||||
save_smtp_settings(values)
|
||||
record_audit_event(current_user['id'], 'smtp_settings_updated', 'application', details={'host': values['smtp_host'], 'port': values['smtp_port'], 'username': values['smtp_username'], 'tls': values['smtp_use_tls']})
|
||||
return public_smtp_settings(values)
|
||||
|
||||
|
||||
@@ -259,6 +264,7 @@ def update_user_privileges(
|
||||
'SELECT id, username, email, is_admin, avatar_url, bio, created_at, email_verified FROM users WHERE id = ?',
|
||||
(user_id,),
|
||||
).fetchone()
|
||||
record_audit_event(current_user['id'], 'user_privileges_updated', 'user', user_id, details={'is_admin': bool(payload.is_admin)})
|
||||
return public_user(row)
|
||||
|
||||
|
||||
@@ -281,13 +287,15 @@ def delete_user(user_id: str, current_user: dict = Depends(require_admin)):
|
||||
conn.execute('DELETE FROM links WHERE user_id = ?', (user_id,))
|
||||
conn.execute('DELETE FROM users WHERE id = ?', (user_id,))
|
||||
conn.commit()
|
||||
record_audit_event(current_user['id'], 'user_deleted', 'user', user_id)
|
||||
return {'status': 'deleted', 'id': user_id}
|
||||
|
||||
|
||||
@router.delete('/labels/{label_id}')
|
||||
def admin_delete_label(label_id: str, _: dict = Depends(require_admin)):
|
||||
def admin_delete_label(label_id: str, current_user: dict = Depends(require_admin)):
|
||||
if not delete_label(label_id, is_admin=True):
|
||||
raise HTTPException(status_code=404, detail='Label not found')
|
||||
record_audit_event(current_user['id'], 'label_deleted', 'label', label_id)
|
||||
return {'status': 'deleted', 'id': label_id}
|
||||
|
||||
|
||||
@@ -346,7 +354,7 @@ def get_plugin(plugin_name: str, _: dict = Depends(require_admin)):
|
||||
def update_plugin(
|
||||
plugin_name: str,
|
||||
payload: AdminPluginUpdate,
|
||||
_: dict = Depends(require_admin),
|
||||
current_user: dict = Depends(require_admin),
|
||||
):
|
||||
with get_connection() as conn:
|
||||
current = conn.execute(
|
||||
@@ -375,6 +383,7 @@ def update_plugin(
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
record_audit_event(current_user['id'], 'plugin_updated', 'plugin', plugin_name, details={'enabled': enabled})
|
||||
return {
|
||||
'name': plugin_name,
|
||||
'enabled': enabled,
|
||||
|
||||
Reference in New Issue
Block a user