gitea user and pswd, some pages removed, logs

This commit is contained in:
JiriUhlir
2026-06-17 11:39:52 +02:00
parent 23bbcad835
commit 7897e3b876
18 changed files with 601 additions and 457 deletions
+96 -3
View File
@@ -1,4 +1,5 @@
import html
import re
from urllib.parse import quote
from fastapi import APIRouter, Depends, Form, HTTPException, Request
@@ -6,12 +7,25 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from app.auth import require_user
from app.db.audit import log_audit_event
from app.db.users import ROLES, count_enabled_admins, delete_user, get_user, list_users, set_user_enabled, update_user_role
from app.gitea_provisioning import sync_gitea_user
from app.db.users import (
ROLES,
count_enabled_admins,
delete_user,
get_user,
list_users,
set_gitea_username,
set_user_enabled,
update_user_role,
)
from app.gitea_provisioning import apply_gitea_credentials, sync_gitea_user
from app.templates.layout import page
router = APIRouter()
# Gitea handle: začíná alfanumerickým znakem, dál povolen . _ -; max 40 znaků.
GITEA_USERNAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$")
MIN_GITEA_PASSWORD_LEN = 8
def require_admin(user: dict) -> None:
if (user.get("role") or "").lower() != "admin":
@@ -100,11 +114,30 @@ def users_page(request: Request, message: str = "", error: str = "", user=Depend
gitea_sync_status = managed_user.get("gitea_sync_status", "not_required") or "not_required"
gitea_sync_error = html.escape(managed_user.get("gitea_sync_error", "") or "")
gitea_synced_at = html.escape(managed_user.get("gitea_synced_at", "") or "")
gitea_username_value = (managed_user.get("gitea_username") or "").strip()
current_gitea_username = gitea_username_value or (f"portal-{user_id}" if gitea_user_id else "")
gitea_details = f"ID {html.escape(str(gitea_user_id))}" if gitea_user_id else "No account"
if current_gitea_username:
gitea_details += f"<br><small>@{html.escape(current_gitea_username)}</small>"
if gitea_synced_at:
gitea_details += f"<br><small>{gitea_synced_at}</small>"
if gitea_sync_error:
gitea_details += f'<br><small class="text-danger">{gitea_sync_error}</small>'
gitea_cred_form = f"""
<details class="gitea-cred">
<summary>Změnit jméno/heslo</summary>
<form method="post" action="/portal/admin/users/{user_id_html}/gitea-credentials" class="metadata-form"
onsubmit="return confirm('Změnit Gitea přihlašovací údaje uživatele {username}?');">
<label>Gitea jméno</label>
<input name="gitea_username" value="{html.escape(current_gitea_username, quote=True)}" placeholder="portal-{user_id_html}" autocomplete="off">
<label>Nové heslo</label>
<input type="password" name="gitea_password" placeholder="prázdné = beze změny" autocomplete="new-password">
<div class="form-actions">
<button type="submit"><i class="fa-solid fa-key" aria-hidden="true"></i> Uložit</button>
</div>
</form>
</details>
"""
toggle_label = "Disable" if enabled else "Enable"
toggle_action = "disable" if enabled else "enable"
@@ -123,7 +156,7 @@ def users_page(request: Request, message: str = "", error: str = "", user=Depend
</form>
</td>
<td>{render_enabled_pill(enabled)}</td>
<td>{render_sync_pill(gitea_sync_status)}<br><small>{gitea_details}</small></td>
<td>{render_sync_pill(gitea_sync_status)}<br><small>{gitea_details}</small>{gitea_cred_form}</td>
<td>{created_at}</td>
<td>{last_login_at}</td>
<td class="actions-cell service-actions">
@@ -297,6 +330,66 @@ def sync_gitea_user_action(user_id: int, user=Depends(require_user)):
return RedirectResponse(url="/portal/admin/users?message=" + quote("Gitea synchronized."), status_code=303)
@router.post("/admin/users/{user_id}/gitea-credentials")
def update_gitea_credentials_action(
user_id: int,
gitea_username: str = Form(""),
gitea_password: str = Form(""),
user=Depends(require_user),
):
require_admin(user)
target = get_user(user_id)
if not target:
raise HTTPException(status_code=404, detail="User not found")
new_username = (gitea_username or "").strip()
new_password = gitea_password or ""
if not new_username and not new_password:
return RedirectResponse(
url="/portal/admin/users?error=" + quote("Zadejte Gitea jméno nebo nové heslo."),
status_code=303,
)
if new_username and not GITEA_USERNAME_RE.match(new_username):
return RedirectResponse(
url="/portal/admin/users?error=" + quote("Neplatné Gitea jméno (povoleno A-Z, 0-9, . _ -, max 40 znaků)."),
status_code=303,
)
if new_password and len(new_password) < MIN_GITEA_PASSWORD_LEN:
return RedirectResponse(
url="/portal/admin/users?error=" + quote(f"Heslo musí mít alespoň {MIN_GITEA_PASSWORD_LEN} znaků."),
status_code=303,
)
result = apply_gitea_credentials(target, new_username, new_password)
if result.get("status") == "error":
return RedirectResponse(
url="/portal/admin/users?error=" + quote(f"Gitea: {result.get('error')}"),
status_code=303,
)
applied = result.get("applied") or []
if "username" in applied:
set_gitea_username(user_id, result.get("gitea_username") or new_username)
log_audit_event(
user,
action="user.gitea.credentials_updated",
target_type="user",
target_id=user_id,
metadata={
"username": target.get("username"),
"gitea_username": result.get("gitea_username"),
"applied": applied, # heslo se nikdy nezaznamenává
},
)
changed = ", ".join(applied) if applied else "beze změny"
return RedirectResponse(
url="/portal/admin/users?message=" + quote(f"Gitea přihlašovací údaje aktualizovány ({changed})."),
status_code=303,
)
@router.post("/admin/users/{user_id}/delete")
def delete_user_action(user_id: int, user=Depends(require_user)):
require_admin(user)