diff --git a/.vscode/launch.json b/.vscode/launch.json index 4a3403e..99b5b15 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,12 +4,13 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "configurations": [ { - "name": "Launch Starfall", + "clientOS": "unix", "console": "integratedTerminal", + "name": "Launch Starfall", + "preLaunchTask": "install_reqs", "program": "${workspaceFolder}/app.py", "request": "launch", - "type": "debugpy", - "clientOS": "unix" + "type": "debugpy" } ], "version": "0.2.0" diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..110732a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,21 @@ +{ + "tasks": [ + { + "command": "./.venv/bin/activate", + "label": "activate", + "type": "shell" + }, + { + "args": [ + "install", + "--upgrade", + "-r", + "requirements.txt" + ], + "command": "./.venv/bin/pip", + "label": "install_reqs", + "type": "shell" + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/starfall/db/schema/users.py b/starfall/db/schema/users.py index ba65f90..5033ad0 100644 --- a/starfall/db/schema/users.py +++ b/starfall/db/schema/users.py @@ -1,3 +1,5 @@ +from typing import Any + from sqlalchemy.orm import Mapped, mapped_column from starfall.db import db @@ -9,5 +11,17 @@ class User(db.Model): username: Mapped[str] = mapped_column() password: Mapped[str] = mapped_column() - def __init__(self, **kwargs) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) + + def auth(self): + return ( + User.query.filter_by(username=self.username, password=self.password).first() + is not None + ) + + def username_free(self): + return User.query.filter_by(username=self.username).first() is None + + def email_free(self): + return User.query.filter_by(email=self.email).first() is None diff --git a/starfall/web/controllers/secure/login.py b/starfall/web/controllers/secure/login.py index a5fef83..07d57a8 100644 --- a/starfall/web/controllers/secure/login.py +++ b/starfall/web/controllers/secure/login.py @@ -1,8 +1,9 @@ -from flask_babel import _, lazy_gettext +from flask_babel import LazyString, lazy_gettext from flask_wtf import FlaskForm from wtforms import PasswordField, StringField from wtforms.validators import DataRequired +from starfall.db.schema.users import User from starfall.web.blueprints.base import BaseBlueprint from starfall.web.controllers.base import BaseController @@ -36,3 +37,16 @@ class LoginController(BaseController): @classmethod def apply(cls, bp: BaseBlueprint): bp.data["form"] = LoginForm() + + @classmethod + def handle_form(cls) -> tuple[str, LazyString | str]: + form = LoginForm() + if not form.validate_on_submit(): + return "", "" + + user = User( + username=str(form.username.data), + password=cls.encrypt_password(str(form.password.data)), + ) + + return "", "" diff --git a/starfall/web/controllers/secure/register.py b/starfall/web/controllers/secure/register.py index e2d6e94..3a030df 100644 --- a/starfall/web/controllers/secure/register.py +++ b/starfall/web/controllers/secure/register.py @@ -1,8 +1,9 @@ -from flask import request -from flask_babel import LazyString, lazy_gettext +from flask import flash, request +from flask_babel import lazy_gettext from flask_wtf import FlaskForm from wtforms import EmailField, PasswordField, StringField -from wtforms.validators import DataRequired +from wtforms.fields.simple import PasswordField +from wtforms.validators import DataRequired, EqualTo, Length from starfall.db import db from starfall.db.schema.users import User @@ -26,12 +27,27 @@ class RegisterForm(FlaskForm): password: PasswordField = PasswordField( label=lazy_gettext("page.register.form.password"), # pyright: ignore[reportArgumentType] render_kw={ - "autocomplete": "password", "class": "form-control", "placeholder": lazy_gettext("page.register.form.password"), }, validators=[ DataRequired(message=lazy_gettext("page.register.error.password")), # pyright: ignore[reportArgumentType] + Length(8, message=lazy_gettext("page.register.error.password_too_short")), # pyright: ignore[reportArgumentType] + ], + ) + retype_password: PasswordField = PasswordField( + label=lazy_gettext("page.register.form.retype_password"), # pyright: ignore[reportArgumentType] + render_kw={ + "class": "form-control", + "placeholder": lazy_gettext("page.register.form.retype_password"), + }, + validators=[ + DataRequired(message=lazy_gettext("page.register.error.retype_password")), # pyright: ignore[reportArgumentType] + Length(8, message=lazy_gettext("page.register.error.password_too_short")), # pyright: ignore[reportArgumentType] + EqualTo( + "password", + message=lazy_gettext("page.register.error.password_not_matching"), # pyright: ignore[reportArgumentType] + ), ], ) email: EmailField = EmailField( @@ -51,22 +67,31 @@ class RegisterController(BaseController): @classmethod def apply(cls, bp: BaseBlueprint): if "POST" == request.method: - bp.data["status_class"], bp.data["status"] = cls.handle_form() + cls.handle_form() bp.data["form"] = RegisterForm() @classmethod - def handle_form(cls) -> tuple[str | None, LazyString | None]: + def handle_form(cls): form = RegisterForm() if not form.validate_on_submit(): - return None, None - user = User( - username=str(form.username.data), - password=cls.encrypt_password(str(form.password.data)), - email=str(form.email.data), - ) + return + try: + user = User( + username=str(form.username.data), + password=cls.encrypt_password(str(form.password.data)), + email=str(form.email.data), + ) + + if not user.username_free(): + flash(lazy_gettext("page.register.status.username_exists"), "danger") # pyright: ignore[reportArgumentType] + return + + if not user.email_free(): + flash(lazy_gettext("page.register.status.email_exists"), "danger") # pyright: ignore[reportArgumentType] + return + db.session.add(user) db.session.commit() except Exception: - return "error", lazy_gettext("page.register.status.email_already_exists") - return "success", lazy_gettext("page.register.status.success") + flash(lazy_gettext("page.register.status.insert_error"), "danger") # pyright: ignore[reportArgumentType] diff --git a/web/templates/components/flash.jinja b/web/templates/components/flash.jinja new file mode 100644 index 0000000..18b160b --- /dev/null +++ b/web/templates/components/flash.jinja @@ -0,0 +1,11 @@ +{%with messages = get_flashed_messages(with_categories=true)%} +{%if messages%} +{%for category, message in messages%} +{# Categories: danger (Error), success (Success), info (Message) #} + +{%endfor%} +{%endif%} +{%endwith%} \ No newline at end of file diff --git a/web/templates/secure/login.jinja b/web/templates/secure/login.jinja index 5e7f350..776d363 100644 --- a/web/templates/secure/login.jinja +++ b/web/templates/secure/login.jinja @@ -9,9 +9,7 @@

{{_('page.login.title')}}

- {%if bp.data["status"]%} - - {%endif%} + {%include "components/flash.jinja"%} {{bp.data["form"].hidden_tag() }} {{form.field(bp.data["form"].username)}} {{form.field(bp.data["form"].password)}} diff --git a/web/templates/secure/register.jinja b/web/templates/secure/register.jinja index 7ee0708..99c5bab 100644 --- a/web/templates/secure/register.jinja +++ b/web/templates/secure/register.jinja @@ -9,12 +9,11 @@

{{_('page.register.title')}}

- {%if bp.data["status"]%} - - {%endif%} + {%include "components/flash.jinja"%} {{bp.data["form"].hidden_tag() }} {{form.field(input=bp.data["form"].username, id="username", prefix="@")}} {{form.field(input=bp.data["form"].password)}} + {{form.field(input=bp.data["form"].retype_password)}} {{form.field(input=bp.data["form"].email)}}