Files
ELM/src/elm/pages/LoginPage.py
T
David Rodenkirchen 36418470a6 make login more sturdy
2026-05-28 13:16:00 +02:00

110 lines
4.9 KiB
Python

from __future__ import annotations
from copy import copy
from typing import Any, Optional
from uuid import uuid4
from rio import Component, Column, Row, Text, Spacer, page, Color, Rectangle, TextInput, GuardEvent
from elm.types import UserSession, User
from elm.services import UserService, LocalData, LocalDataService, ConfigurationService
from elm.components import ElmButton
def login_page_guard(event: GuardEvent) -> Optional[str]:
try:
_ = event.session[UserSession].user_name
return "/"
except KeyError:
return None
@page(name="Login", url_segment="login", guard=login_page_guard)
class LoginPage(Component):
user_name: str = ""
password: str = ""
error_on_last_attempt: bool = False
login_in_progress: bool = False
async def on_login_confirmed(self, _: Any) -> None:
""" Handler for pressing ENTER inside the text inputs """
await self.on_login_pressed()
async def on_login_pressed(self) -> None:
self.login_in_progress = True
user_name = copy(self.user_name) # Prevents race condition name swap
is_valid = await self.session[UserService].is_login_valid(user_name, self.password)
if not is_valid: # Migrated users
user_name = user_name.lower().capitalize()
is_valid = await self.session[UserService].is_login_valid(user_name, self.password)
if is_valid:
user: User = await self.session[UserService].get_user(user_name)
self.error_on_last_attempt = False
user_session = UserSession(id=uuid4(), user_name=user.user_name, is_team_member=user.is_team_member)
self.session.attach(user_session)
token = self.session[LocalDataService].set_session(user_session)
self.session[LocalData].stored_session_token = token
self.session[UserSession].profile_picture = await self.load_user_picture()
self.session.attach(self.session[LocalData])
self.login_in_progress = False
self.session.navigate_to("./")
else:
self.login_in_progress = False
self.error_on_last_attempt = True
async def load_user_picture(self) -> bytes:
try:
user_picture = await self.session[UserService].get_user_picture(self.session[UserSession].user_name)
if user_picture is not None and len(user_picture) > 0:
return user_picture
except KeyError:
return self.session[ConfigurationService].DEFAULT_PROFILE_PICTURE
return self.session[ConfigurationService].DEFAULT_PROFILE_PICTURE
def on_register_pressed(self) -> None:
self.session.navigate_to("./register")
def on_lost_password_pressed(self) -> None:
self.session.navigate_to("./lost-pw")
def build(self) -> Component:
return Row(
Rectangle(
content=Column(
Rectangle(
content=Rectangle(
content=Text("Login", margin=0.5, selectable=False, overflow="wrap"),
fill=self.session.theme.header_box_background_color,
margin=0.4
),
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
),
Column(
TextInput(
text=self.bind().user_name,
label="Nutzername",
on_confirm=self.on_login_confirmed
),
TextInput(
text=self.bind().password,
label="Passwort",
is_secret=True,
on_confirm=self.on_login_confirmed
),
Text("Falscher Nutzername oder Passwort", fill=self.session.theme.danger_color, overflow="wrap", justify="center") if self.error_on_last_attempt else Spacer(grow_x=False, grow_y=False),
ElmButton(text="Login", style="small" if self.session.is_mobile() else "normal", on_press=self.on_login_pressed, is_loading=self.login_in_progress),
ElmButton(text="Passwort\nvergessen" if self.session.is_mobile() else "Passwort vergessen", style="small" if self.session.is_mobile() else "normal", on_press=self.on_lost_password_pressed),
ElmButton(text="Account anlegen", style="small" if self.session.is_mobile() else "normal", on_press=self.on_register_pressed),
margin=1,
spacing=1
),
Spacer()
),
fill=self.session.theme.box_color,
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
min_height=15
),
align_x=0.5,
align_y=0.5
)