admin / Synapse-Cortex
publicSelf Hosted ITSM Tool with RBAC/Tenanting and MFA
Synapse-Cortex / synapse-cortex / app / routers / settings.py
5305 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | from fastapi import APIRouter, Depends, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from .. import mfa from ..audit import log_audit from ..database import get_db from ..deps import get_session_user from ..paths import TEMPLATES_DIR from ..security import hash_password, verify_password router = APIRouter() templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) @router.get("/settings", response_class=HTMLResponse) def settings_page(request: Request, user=Depends(get_session_user)): if not user: return RedirectResponse("/login", status_code=303) return templates.TemplateResponse( "settings_profile.html", {"request": request, "user": user, "active": "settings", "error": None, "success": None}, ) @router.post("/settings/password", response_class=HTMLResponse) def change_password( request: Request, db: Session = Depends(get_db), user=Depends(get_session_user), current_password: str = Form(...), new_password: str = Form(...), ): if not user: return RedirectResponse("/login", status_code=303) if not verify_password(current_password, user.hashed_password): return templates.TemplateResponse( "settings_profile.html", {"request": request, "user": user, "active": "settings", "error": "Current password is incorrect.", "success": None}, status_code=400, ) if len(new_password) < 8: return templates.TemplateResponse( "settings_profile.html", {"request": request, "user": user, "active": "settings", "error": "New password must be at least 8 characters.", "success": None}, status_code=400, ) user.hashed_password = hash_password(new_password) db.commit() return templates.TemplateResponse( "settings_profile.html", {"request": request, "user": user, "active": "settings", "error": None, "success": "Password updated."}, ) @router.get("/settings/mfa/enroll", response_class=HTMLResponse) def mfa_enroll_form(request: Request, db: Session = Depends(get_db), user=Depends(get_session_user)): if not user: return RedirectResponse("/login", status_code=303) if user.mfa_enabled: return RedirectResponse("/settings", status_code=303) # A fresh secret each time the page loads keeps a stale/abandoned QR code # from ever being usable later. user.mfa_secret = mfa.generate_secret() db.commit() uri = mfa.provisioning_uri(user.mfa_secret, user.email) qr_data_url = mfa.qr_data_url(uri) return templates.TemplateResponse( "mfa_enroll.html", { "request": request, "user": user, "active": "settings", "error": None, "qr_data_url": qr_data_url, "secret": user.mfa_secret, }, ) @router.post("/settings/mfa/enroll", response_class=HTMLResponse) def mfa_enroll_verify( request: Request, db: Session = Depends(get_db), user=Depends(get_session_user), code: str = Form(...), ): if not user: return RedirectResponse("/login", status_code=303) if user.mfa_enabled: return RedirectResponse("/settings", status_code=303) if not mfa.verify_code(user.mfa_secret, code): uri = mfa.provisioning_uri(user.mfa_secret, user.email) return templates.TemplateResponse( "mfa_enroll.html", { "request": request, "user": user, "active": "settings", "error": "Invalid code. Try again.", "qr_data_url": mfa.qr_data_url(uri), "secret": user.mfa_secret, }, status_code=400, ) user.mfa_enabled = True log_audit(db, tenant_id=user.tenant_id, user_id=user.id, action="mfa_enrolled") db.commit() return RedirectResponse("/settings", status_code=303) @router.get("/settings/mfa/disable", response_class=HTMLResponse) def mfa_disable_form(request: Request, user=Depends(get_session_user)): if not user: return RedirectResponse("/login", status_code=303) if not user.mfa_enabled: return RedirectResponse("/settings", status_code=303) return templates.TemplateResponse( "mfa_disable.html", {"request": request, "user": user, "active": "settings", "error": None} ) @router.post("/settings/mfa/disable", response_class=HTMLResponse) def mfa_disable_submit( request: Request, db: Session = Depends(get_db), user=Depends(get_session_user), current_password: str = Form(...), ): if not user: return RedirectResponse("/login", status_code=303) if not verify_password(current_password, user.hashed_password): return templates.TemplateResponse( "mfa_disable.html", {"request": request, "user": user, "active": "settings", "error": "Current password is incorrect."}, status_code=400, ) user.mfa_enabled = False user.mfa_secret = None log_audit(db, tenant_id=user.tenant_id, user_id=user.id, action="mfa_disabled") db.commit() return RedirectResponse("/settings", status_code=303) |