Files
MCM/tui/screens/login_screen.py
itdrui.de 6eb27a62b1 feat: Multi-User-Unterstützung mit JWT-Authentifizierung
- User-Modell (username, password_hash, role admin/user, is_active)
- Standard-Admin-Benutzer wird beim ersten Start automatisch angelegt
- JWT-Tokens (HS256) für Benutzer-Sessions, konfigurierbare Ablaufzeit
- API-Key bleibt für service-to-service-Calls (backward-compatible)
- POST /api/v1/auth/login → JWT-Token
- GET  /api/v1/auth/me   → aktueller Benutzer
- CRUD /api/v1/users/    → Benutzerverwaltung (nur Admin)
- TUI zeigt Login-Screen beim Start; nach Erfolg → MainScreen
- Passwort-Hashing mit bcrypt (python-jose für JWT)
2026-03-04 20:55:13 +01:00

72 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Input, Label, Static
class LoginScreen(Screen):
"""Anmeldebildschirm erscheint beim Start der TUI."""
BINDINGS = [Binding("ctrl+c", "quit_app", "Beenden")]
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with Vertical(id="login-container"):
yield Static("Anmeldung", id="login-title")
yield Label("Benutzername:")
yield Input(placeholder="admin", id="username-input")
yield Label("Passwort:")
yield Input(placeholder="", password=True, id="password-input")
yield Static("", id="login-error")
yield Button("Anmelden", variant="primary", id="login-btn")
yield Footer()
def on_mount(self) -> None:
self.query_one("#username-input", Input).focus()
def on_input_submitted(self, event: Input.Submitted) -> None:
if event.input.id == "username-input":
self.query_one("#password-input", Input).focus()
elif event.input.id == "password-input":
self._do_login()
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "login-btn":
self._do_login()
def _do_login(self) -> None:
import asyncio
asyncio.create_task(self._login_async())
async def _login_async(self) -> None:
username = self.query_one("#username-input", Input).value.strip()
password = self.query_one("#password-input", Input).value
if not username or not password:
self.query_one("#login-error", Static).update("Benutzername und Passwort eingeben.")
return
btn = self.query_one("#login-btn", Button)
btn.disabled = True
self.query_one("#login-error", Static).update("")
try:
ok = await self.app._api_client.login(username, password) # type: ignore[attr-defined]
except Exception:
ok = False
btn.disabled = False
if ok:
from tui.screens.main_screen import MainScreen
self.app.switch_screen(MainScreen())
else:
self.query_one("#login-error", Static).update("Ungültige Zugangsdaten oder Server nicht erreichbar.")
self.query_one("#password-input", Input).clear()
self.query_one("#password-input", Input).focus()
def action_quit_app(self) -> None:
self.app.exit()