Catalyst / admin/Synapse-Cortex 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Cortex

public

Self Hosted ITSM Tool with RBAC/Tenanting and MFA

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
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)