add login

This commit is contained in:
David Rodenkirchen 2024-08-25 22:31:29 +02:00
parent 8a47e95c2a
commit e6b7f4ca85
8 changed files with 216 additions and 60 deletions

View File

@ -2,10 +2,11 @@ import logging
from pathlib import Path from pathlib import Path
from rio import App, Theme, Color, Font, Page from rio import App, Theme, Color, Font, Page, Session
from from_root import from_root from from_root import from_root
from src.ez_lan_manager import pages, init_services from src.ez_lan_manager import pages, init_services
from src.ez_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
@ -27,6 +28,10 @@ if __name__ == "__main__":
services = init_services() services = init_services()
lan_info = services[2].get_lan_info() lan_info = services[2].get_lan_info()
async def on_session_start(session: Session) -> None:
await session.set_title(lan_info.name)
session.attach(SessionStorage())
app = App( app = App(
name="EZ LAN Manager", name="EZ LAN Manager",
pages=[ pages=[
@ -89,12 +94,22 @@ if __name__ == "__main__":
name="Imprint", name="Imprint",
page_url="imprint", page_url="imprint",
build=lambda: pages.PlaceholderPage(placeholder_name="Impressum & DSGVO"), build=lambda: pages.PlaceholderPage(placeholder_name="Impressum & DSGVO"),
),
Page(
name="Register",
page_url="register",
build=lambda: pages.PlaceholderPage(placeholder_name="Registrierung"),
),
Page(
name="ForgotPassword",
page_url="forgot-password",
build=lambda: pages.PlaceholderPage(placeholder_name="Passwort vergessen"),
) )
], ],
theme=theme, theme=theme,
assets_dir=Path(__file__).parent / "assets", assets_dir=Path(__file__).parent / "assets",
default_attachments=services, default_attachments=services,
on_session_start= lambda s: s.set_title(lan_info.name), on_session_start=on_session_start,
icon=from_root("src/ez_lan_manager/assets/img/favicon.png"), icon=from_root("src/ez_lan_manager/assets/img/favicon.png"),
meta_tags={ meta_tags={
"robots": "INDEX,FOLLOW", "robots": "INDEX,FOLLOW",

View File

@ -1,40 +1,26 @@
from rio import * from rio import *
from src.ez_lan_manager import ConfigurationService from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
from src.ez_lan_manager.components.LoginBox import LoginBox from src.ez_lan_manager.components.LoginBox import LoginBox
from src.ez_lan_manager.components.UserInfoBox import UserInfoBox
from src.ez_lan_manager.types.SessionStorage import SessionStorage
class DesktopNavigationButton(Component):
STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
label: str
target_url: str
open_new_tab: bool = False
def build(self) -> Component:
return Link(
content=Button(
content=Text(self.label, style=self.STYLE),
shape="rectangle",
style="minor",
color="secondary",
grow_x=True,
margin_left=0.6,
margin_right=0.6,
margin_top=0.6
),
target_url=self.target_url,
open_in_new_tab=self.open_new_tab
)
class DesktopNavigation(Component): class DesktopNavigation(Component):
async def refresh_cb(self) -> None:
self.box = self.login_box if self.session[SessionStorage].user_id is None else self.user_info_box
await self.force_refresh()
def build(self) -> Component: def build(self) -> Component:
self.user_info_box = UserInfoBox()
self.login_box = LoginBox(self.refresh_cb)
self.box = self.login_box if self.session[SessionStorage].user_id is None else self.user_info_box
lan_info = self.session[ConfigurationService].get_lan_info() lan_info = self.session[ConfigurationService].get_lan_info()
return Card( return Card(
Column( Column(
Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)), Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)),
Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2), Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2),
LoginBox(), self.box,
DesktopNavigationButton("News", "./news"), DesktopNavigationButton("News", "./news"),
Spacer(min_height=1), Spacer(min_height=1),
DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"), DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"),

View File

@ -0,0 +1,24 @@
from rio import Component, TextStyle, Color, Link, Button, Text
class DesktopNavigationButton(Component):
STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
label: str
target_url: str
open_new_tab: bool = False
def build(self) -> Component:
return Link(
content=Button(
content=Text(self.label, style=self.STYLE),
shape="rectangle",
style="minor",
color="secondary",
grow_x=True,
margin_left=0.6,
margin_right=0.6,
margin_top=0.6
),
target_url=self.target_url,
open_in_new_tab=self.open_new_tab
)

View File

@ -1,47 +1,78 @@
from rio import Component, Card, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput from typing import Callable
from rio import Component, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput
from src.ez_lan_manager import UserService
from src.ez_lan_manager.types.SessionStorage import SessionStorage
class LoginBox(Component): class LoginBox(Component):
TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9) TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
refresh_cb: Callable
async def _on_login_pressed(self) -> None:
self.login_button.is_loading = True
user_name = self.user_name_input.text.lower()
if self.session[UserService].is_login_valid(user_name, self.password_input.text):
self.session[SessionStorage].user_id = self.session[UserService].get_user(user_name).user_id
self.user_name_input.is_valid = True
self.password_input.is_valid = True
self.login_button.is_loading = False
await self.refresh_cb()
else:
self.user_name_input.is_valid = False
self.password_input.is_valid = False
self.login_button.is_loading = False
def build(self) -> Component: def build(self) -> Component:
self.user_name_input = TextInput(
text="",
label="Benutzername",
accessibility_label = "Benutzername",
min_height=0.5,
on_confirm=lambda _: self._on_login_pressed()
)
self.password_input = TextInput(
text="",
label="Passwort",
accessibility_label="Passwort",
is_secret=True,
on_confirm=lambda _: self._on_login_pressed()
)
self.login_button = Button(
Text("LOGIN", style=self.TEXT_STYLE, justify="center"),
shape="rectangle",
style="minor",
color="secondary",
margin_bottom=0.4,
on_press=self._on_login_pressed
)
self.register_button = Button(
Text("REG", style=self.TEXT_STYLE, justify="center"),
shape="rectangle",
style="minor",
color="secondary",
on_press=lambda: self.session.navigate_to("./register")
)
self.forgot_password_button = Button(
Text("LST PWD",style=self.TEXT_STYLE, justify="center"),
shape="rectangle",
style="minor",
color="secondary",
on_press=lambda: self.session.navigate_to("./forgot-password")
)
return Rectangle( return Rectangle(
content=Column( content=Column(
TextInput( self.user_name_input,
text="", self.password_input,
label="Benutzername",
accessibility_label = "Benutzername",
min_height=0.5
),
TextInput(
text="",
label="Passwort",
accessibility_label="Passwort",
is_secret=True
),
Column( Column(
Row( Row(
Button( self.login_button
Text("LOGIN", style=self.TEXT_STYLE, justify="center"),
shape="rectangle",
style="minor",
color="secondary",
margin_bottom=0.4
)
), ),
Row( Row(
Button( self.register_button,
Text("REG", style=self.TEXT_STYLE, justify="center"),
shape="rectangle",
style="minor",
color="secondary"
),
Spacer(), Spacer(),
Button( self.forgot_password_button,
Text("LST PWD",style=self.TEXT_STYLE, justify="center"),
shape="rectangle",
style="minor",
color="secondary"
),
proportions=(49, 2, 49) proportions=(49, 2, 49)
) )
), ),

View File

@ -0,0 +1,63 @@
from random import choice
from rio import Component, Card, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput, Link
from src.ez_lan_manager import UserService, AccountingService
from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton
from src.ez_lan_manager.types.SessionStorage import SessionStorage
class StatusButton(Component):
STYLE = TextStyle(fill=Color.from_hex("121212"), font_size=0.5)
label: str
target_url: str
enabled: bool
def build(self) -> Component:
return Link(
content=Button(
content=Text(self.label, style=self.STYLE, justify="center"),
shape="rectangle",
style="major",
color="success" if self.enabled else "danger",
grow_x=True,
margin_left=0.6,
margin_right=0.6,
margin_top=0.6
),
target_url=self.target_url,
align_y=0.5,
grow_y=False
)
class UserInfoBox(Component):
@staticmethod
def get_greeting() -> str:
return choice(["Grüße", "Hallo", "Willkommen", "Moin", "Ahoi"])
def build(self) -> Component:
user = self.session[UserService].get_user(self.session[SessionStorage].user_id)
if user is None: # Noone logged in
return Text("")
a_s = self.session[AccountingService]
return Rectangle(
content=Column(
Text(f"{self.get_greeting()},", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9), justify="center"),
Text(f"{user.user_name}", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=1.2), justify="center"),
Row(
StatusButton(label="TICKET", target_url="", enabled=True),
StatusButton(label="SITZPLATZ", target_url="", enabled=False),
proportions=(50, 50),
grow_y=False
),
UserInfoBoxButton("Profil bearbeiten", "./edit-profile"),
UserInfoBoxButton(f"Guthaben: {a_s.make_euro_string_from_int(a_s.get_balance(user.user_id))}", "./account"),
UserInfoBoxButton("Ausloggen", "./logout")
),
fill=Color.TRANSPARENT,
min_height=8,
min_width=12,
align_x=0.5,
margin_top=0.3,
margin_bottom=2
)

View File

@ -0,0 +1,24 @@
from rio import Component, TextStyle, Color, Link, Button, Text
class UserInfoBoxButton(Component):
STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.6)
label: str
target_url: str
open_new_tab: bool = False
def build(self) -> Component:
return Link(
content=Button(
content=Text(self.label, style=self.STYLE),
shape="rectangle",
style="minor",
color="secondary",
grow_x=True,
margin_left=0.6,
margin_right=0.6,
margin_top=0.6
),
target_url=self.target_url,
open_in_new_tab=self.open_new_tab
)

View File

@ -16,9 +16,12 @@ class UserService:
def __init__(self, db_service: DatabaseService) -> None: def __init__(self, db_service: DatabaseService) -> None:
self._db_service = db_service self._db_service = db_service
def get_user(self, accessor: Union[str, int]) -> Optional[User]: def get_user(self, accessor: Optional[Union[str, int]]) -> Optional[User]:
if accessor is None:
return
if isinstance(accessor, int): if isinstance(accessor, int):
return self._db_service.get_user_by_id(accessor) return self._db_service.get_user_by_id(accessor)
accessor = accessor.lower()
if "@" in accessor: if "@" in accessor:
return self._db_service.get_user_by_mail(accessor) return self._db_service.get_user_by_mail(accessor)
return self._db_service.get_user_by_name(accessor) return self._db_service.get_user_by_name(accessor)
@ -34,6 +37,8 @@ class UserService:
if disallowed_char: if disallowed_char:
raise NameNotAllowedError(disallowed_char) raise NameNotAllowedError(disallowed_char)
user_name = user_name.lower()
hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest() hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
return self._db_service.create_user(user_name, user_mail, hashed_pw) return self._db_service.create_user(user_name, user_mail, hashed_pw)
@ -41,6 +46,7 @@ class UserService:
disallowed_char = self._check_for_disallowed_char(user.user_name) disallowed_char = self._check_for_disallowed_char(user.user_name)
if disallowed_char: if disallowed_char:
raise NameNotAllowedError(disallowed_char) raise NameNotAllowedError(disallowed_char)
user.user_name = user.user_name.lower()
return self._db_service.update_user(user) return self._db_service.update_user(user)
def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool: def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool:

View File

@ -0,0 +1,7 @@
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=False)
class SessionStorage:
user_id: Optional[int] = None # DEBUG: Put user ID here to skip login