Compare commits

...

10 Commits

Author SHA1 Message Date
tcprod
c1a8523404 Merge remote-tracking branch 'refs/remotes/origin/bugfix/password-reset'
# Conflicts:
#	README.md
#	VERSION
#	src/EzggLanManager.py
#	src/ezgg_lan_manager/pages/TournamentDetailsPage.py
#	src/ezgg_lan_manager/pages/__init__.py
2026-02-16 17:58:16 +01:00
tcprod
81671f8ab2 add patches to README.md and comment to patch 2026-02-12 17:37:46 +01:00
tcprod
ca58f9d74c fix reset password 2026-02-11 08:05:15 +00:00
tcprod
0b9c073900 fix reset password 2026-02-11 08:05:15 +00:00
tcprod
5a45af4465 fix reset password 2026-02-11 08:05:15 +00:00
David Rodenkirchen
24866966f4 bump to version 0.2.1 2026-02-08 00:43:03 +00:00
tcprod
c73755f3b5 fix formatting total balance bug in email 2026-02-08 00:40:58 +00:00
tcprod
baaa438e5e fix formatting balance bug in email 2026-02-08 00:40:58 +00:00
David Rodenkirchen
aa3691a59f Fix bug where users without ticket could register for tournament 2026-02-08 01:26:55 +01:00
David Rodenkirchen
f713443c20 Add easteregg 2026-02-04 20:23:57 +01:00
10 changed files with 32 additions and 41 deletions

View File

@ -8,16 +8,14 @@ This repository contains the code for the EZGG LAN Manager.
### Prerequisites ### Prerequisites
- Working Installation of MariaDB Server (version `10.6.25` or later) - Working Installation of MySQL 5 or latest MariaDB Server (`mariadb-server` for Debian-based Linux, `XAMPP` for Windows)
+ MySQL should work too, but there are no guarantees.
- Python 3.9 or higher - Python 3.9 or higher
- PyCharm or similar IDE (optional) - PyCharm or similar IDE (optional)
### Step 1: Preparing Database ### Step 1: Preparing Database
To prepare the database, apply the SQL file located in `sql/create_database.sql` to your database server. This is easily accomplished with the MYSQL Workbench, but it can be also done by piping the file into the mariadb-server executable. To prepare the database, apply the SQL file located in `sql/create_database.sql` to your database server. This is easily accomplished with the MYSQL Workbench, but it can be also done by pipeing the file into the mariadb-server executable.
After the database is created, apply the SQL patches located in `sql/*patch.sql` to support the fallback password- and tournament functionality.
After creating the database, apply all patches found in `sql/*_patch.sql` in their numeric order.
Optionally, you can now execute the script `create_demo_database_content.py`, found in `src/ezgg_lan_manager/helpers`. Be aware that it can be buggy sometimes, especially if you overwrite existing data. Optionally, you can now execute the script `create_demo_database_content.py`, found in `src/ezgg_lan_manager/helpers`. Be aware that it can be buggy sometimes, especially if you overwrite existing data.
@ -46,4 +44,3 @@ FLUSH PRIVILEGES;
``` ```
3. Make sure to **NOT** use the default passwords! 3. Make sure to **NOT** use the default passwords!
4. Apply the `create_database.sql` when starting the MariaDB container for the first time. 4. Apply the `create_database.sql` when starting the MariaDB container for the first time.
5. Apply the patches (`sql/*_patch.sql`) when starting the MariaDB container for the first time.

View File

@ -1 +1 @@
0.3.0 0.2.1

5
sql/users_patch.sql Normal file
View File

@ -0,0 +1,5 @@
-- Apply this patch after using create_database.sql to extend the schema to support fallback passwords
ALTER TABLE users
ADD COLUMN user_fallback_password VARCHAR(255) DEFAULT NULL
AFTER user_password;

View File

@ -172,11 +172,6 @@ if __name__ == "__main__":
url_segment="tournament-rules", url_segment="tournament-rules",
build=pages.TournamentRulesPage, build=pages.TournamentRulesPage,
), ),
ComponentPage(
name="Teams",
url_segment="teams",
build=pages.TeamsPage,
),
ComponentPage( ComponentPage(
name="ConwaysGameOfLife", name="ConwaysGameOfLife",
url_segment="conway", url_segment="conway",

View File

@ -27,7 +27,7 @@ class ForgotPasswordPage(Component):
user = await user_service.get_user(self.email_input.text.strip()) user = await user_service.get_user(self.email_input.text.strip())
if user is not None: if user is not None:
new_password = "".join(choices(user_service.ALLOWED_USER_NAME_SYMBOLS, k=16)) new_password = "".join(choices(user_service.ALLOWED_USER_NAME_SYMBOLS, k=16))
user.user_password = sha256(new_password.encode(encoding="utf-8")).hexdigest() user.user_fallback_password = sha256(new_password.encode(encoding="utf-8")).hexdigest()
await user_service.update_user(user) await user_service.update_user(user)
await mailing_service.send_email( await mailing_service.send_email(
subject=f"Dein neues Passwort für {lan_info.name}", subject=f"Dein neues Passwort für {lan_info.name}",

View File

@ -1,4 +1,3 @@
from asyncio import sleep
from typing import Optional, Union, Literal from typing import Optional, Union, Literal
from from_root import from_root from from_root import from_root
@ -9,6 +8,7 @@ from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserSe
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow
from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text
from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Tournament import Tournament from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts
@ -45,14 +45,6 @@ class TournamentDetailsPage(Component):
self.loading_done() self.loading_done()
@staticmethod
async def artificial_delay() -> None:
await sleep(0.8) # https://medium.com/design-bootcamp/ux-psychology-of-artificial-waiting-enhancing-user-experiences-through-deliberate-delays-d7822faf3930
async def update(self) -> None:
self.tournament = await self.session[TournamentService].get_tournament_by_id(self.tournament.id)
self.current_tournament_user_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
def open_close_participant_revealer(self, _: PointerEvent) -> None: def open_close_participant_revealer(self, _: PointerEvent) -> None:
self.participant_revealer_open = not self.participant_revealer_open self.participant_revealer_open = not self.participant_revealer_open
@ -68,14 +60,13 @@ class TournamentDetailsPage(Component):
else: else:
try: try:
await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id) await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id)
await self.artificial_delay()
self.is_success = True self.is_success = True
self.message = f"Erfolgreich angemeldet!" self.message = f"Erfolgreich angemeldet!"
except Exception as e: except Exception as e:
self.is_success = False self.is_success = False
self.message = f"Fehler: {e}" self.message = f"Fehler: {e}"
await self.update()
self.loading = False self.loading = False
await self.on_populate()
async def unregister_pressed(self) -> None: async def unregister_pressed(self) -> None:
self.loading = True self.loading = True
@ -84,14 +75,13 @@ class TournamentDetailsPage(Component):
try: try:
await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id) await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id)
await self.artificial_delay()
self.is_success = True self.is_success = True
self.message = f"Erfolgreich abgemeldet!" self.message = f"Erfolgreich abgemeldet!"
except Exception as e: except Exception as e:
self.is_success = False self.is_success = False
self.message = f"Fehler: {e}" self.message = f"Fehler: {e}"
await self.update()
self.loading = False self.loading = False
await self.on_populate()
async def tree_button_clicked(self) -> None: async def tree_button_clicked(self) -> None:
pass # ToDo: Implement tournament tree view pass # ToDo: Implement tournament tree view
@ -213,7 +203,7 @@ class TournamentDetailsPage(Component):
content=Rectangle( content=Rectangle(
content=TournamentDetailsInfoRow( content=TournamentDetailsInfoRow(
"Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾", "Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾",
f"{len(self.current_tournament_user_list)} / {self.tournament.max_participants}", f"{len(self.tournament.participants)} / {self.tournament.max_participants}",
value_color=self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color, value_color=self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color,
key_color=self.session.theme.secondary_color key_color=self.session.theme.secondary_color
), ),

View File

@ -23,4 +23,3 @@ from .OverviewPage import OverviewPage
from .TournamentDetailsPage import TournamentDetailsPage from .TournamentDetailsPage import TournamentDetailsPage
from .TournamentRulesPage import TournamentRulesPage from .TournamentRulesPage import TournamentRulesPage
from .ConwayPage import ConwayPage from .ConwayPage import ConwayPage
from .TeamsPage import TeamsPage

View File

@ -76,14 +76,15 @@ class DatabaseService:
user_name=data[1], user_name=data[1],
user_mail=data[2], user_mail=data[2],
user_password=data[3], user_password=data[3],
user_first_name=data[4], user_fallback_password=data[4],
user_last_name=data[5], user_first_name=data[5],
user_birth_day=data[6], user_last_name=data[6],
is_active=bool(data[7]), user_birth_day=data[7],
is_team_member=bool(data[8]), is_active=bool(data[8]),
is_admin=bool(data[9]), is_team_member=bool(data[9]),
created_at=data[10], is_admin=bool(data[10]),
last_updated_at=data[11] created_at=data[11],
last_updated_at=data[12]
) )
@staticmethod @staticmethod
@ -186,10 +187,10 @@ class DatabaseService:
async with conn.cursor(aiomysql.Cursor) as cursor: async with conn.cursor(aiomysql.Cursor) as cursor:
try: try:
await cursor.execute( await cursor.execute(
"UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_first_name=%s, " "UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_fallback_password=%s,"
"user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s, is_admin=%s " "user_first_name=%s, user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s,"
"WHERE (user_id=%s)", " is_admin=%s WHERE (user_id=%s)",
(user.user_name, user.user_mail.lower(), user.user_password, (user.user_name, user.user_mail.lower(), user.user_password, user.user_fallback_password,
user.user_first_name, user.user_last_name, user.user_birth_day, user.user_first_name, user.user_last_name, user.user_birth_day,
user.is_active, user.is_team_member, user.is_admin, user.is_active, user.is_team_member, user.is_admin,
user.user_id) user.user_id)

View File

@ -59,9 +59,12 @@ class UserService:
async def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool: async def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool:
user = await self.get_user(user_name_or_mail) user = await self.get_user(user_name_or_mail)
user_password_hash = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
if not user: if not user:
return False return False
return user.user_password == sha256(password_clear_text.encode(encoding="utf-8")).hexdigest() if user.user_fallback_password and user.user_fallback_password == user_password_hash:
return True
return user.user_password == user_password_hash
def _check_for_disallowed_char(self, name: str) -> Optional[str]: def _check_for_disallowed_char(self, name: str) -> Optional[str]:

View File

@ -9,6 +9,7 @@ class User:
user_name: str user_name: str
user_mail: str user_mail: str
user_password: str user_password: str
user_fallback_password: Optional[str]
user_first_name: Optional[str] user_first_name: Optional[str]
user_last_name: Optional[str] user_last_name: Optional[str]
user_birth_day: Optional[date] user_birth_day: Optional[date]