Login system pt.2

This commit is contained in:
Flare Starfall 2026-03-19 00:30:35 +01:00
parent 5989f0d97e
commit 1fcb701083
8 changed files with 108 additions and 25 deletions

7
.vscode/launch.json vendored
View File

@ -4,12 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"configurations": [ "configurations": [
{ {
"name": "Launch Starfall", "clientOS": "unix",
"console": "integratedTerminal", "console": "integratedTerminal",
"name": "Launch Starfall",
"preLaunchTask": "install_reqs",
"program": "${workspaceFolder}/app.py", "program": "${workspaceFolder}/app.py",
"request": "launch", "request": "launch",
"type": "debugpy", "type": "debugpy"
"clientOS": "unix"
} }
], ],
"version": "0.2.0" "version": "0.2.0"

21
.vscode/tasks.json vendored Normal file
View File

@ -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"
}

View File

@ -1,3 +1,5 @@
from typing import Any
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from starfall.db import db from starfall.db import db
@ -9,5 +11,17 @@ class User(db.Model):
username: Mapped[str] = mapped_column() username: Mapped[str] = mapped_column()
password: Mapped[str] = mapped_column() password: Mapped[str] = mapped_column()
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs) 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

View File

@ -1,8 +1,9 @@
from flask_babel import _, lazy_gettext from flask_babel import LazyString, lazy_gettext
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField from wtforms import PasswordField, StringField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
from starfall.db.schema.users import User
from starfall.web.blueprints.base import BaseBlueprint from starfall.web.blueprints.base import BaseBlueprint
from starfall.web.controllers.base import BaseController from starfall.web.controllers.base import BaseController
@ -36,3 +37,16 @@ class LoginController(BaseController):
@classmethod @classmethod
def apply(cls, bp: BaseBlueprint): def apply(cls, bp: BaseBlueprint):
bp.data["form"] = LoginForm() 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 "", ""

View File

@ -1,8 +1,9 @@
from flask import request from flask import flash, request
from flask_babel import LazyString, lazy_gettext from flask_babel import lazy_gettext
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import EmailField, PasswordField, StringField 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 import db
from starfall.db.schema.users import User from starfall.db.schema.users import User
@ -26,12 +27,27 @@ class RegisterForm(FlaskForm):
password: PasswordField = PasswordField( password: PasswordField = PasswordField(
label=lazy_gettext("page.register.form.password"), # pyright: ignore[reportArgumentType] label=lazy_gettext("page.register.form.password"), # pyright: ignore[reportArgumentType]
render_kw={ render_kw={
"autocomplete": "password",
"class": "form-control", "class": "form-control",
"placeholder": lazy_gettext("page.register.form.password"), "placeholder": lazy_gettext("page.register.form.password"),
}, },
validators=[ validators=[
DataRequired(message=lazy_gettext("page.register.error.password")), # pyright: ignore[reportArgumentType] 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( email: EmailField = EmailField(
@ -51,22 +67,31 @@ class RegisterController(BaseController):
@classmethod @classmethod
def apply(cls, bp: BaseBlueprint): def apply(cls, bp: BaseBlueprint):
if "POST" == request.method: if "POST" == request.method:
bp.data["status_class"], bp.data["status"] = cls.handle_form() cls.handle_form()
bp.data["form"] = RegisterForm() bp.data["form"] = RegisterForm()
@classmethod @classmethod
def handle_form(cls) -> tuple[str | None, LazyString | None]: def handle_form(cls):
form = RegisterForm() form = RegisterForm()
if not form.validate_on_submit(): if not form.validate_on_submit():
return None, None return
try:
user = User( user = User(
username=str(form.username.data), username=str(form.username.data),
password=cls.encrypt_password(str(form.password.data)), password=cls.encrypt_password(str(form.password.data)),
email=str(form.email.data), email=str(form.email.data),
) )
try:
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.add(user)
db.session.commit() db.session.commit()
except Exception: except Exception:
return "error", lazy_gettext("page.register.status.email_already_exists") flash(lazy_gettext("page.register.status.insert_error"), "danger") # pyright: ignore[reportArgumentType]
return "success", lazy_gettext("page.register.status.success")

View File

@ -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) #}
<div class="alert alert-{{category}} alert-dismissible" role="alert">
{{message}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="base.label.close"></button>
</div>
{%endfor%}
{%endif%}
{%endwith%}

View File

@ -9,9 +9,7 @@
<h1 class="text-center text-primary-emphasis">{{_('page.login.title')}}</h1> <h1 class="text-center text-primary-emphasis">{{_('page.login.title')}}</h1>
<form class="border border-secondary bg-secondary-subtle rounded p-3" method="POST"> <form class="border border-secondary bg-secondary-subtle rounded p-3" method="POST">
{%if bp.data["status"]%} {%include "components/flash.jinja"%}
<div class="alert alert-{{bp.data['status_class']}}" role="alert">{{bp.data["status"]}}</div>
{%endif%}
{{bp.data["form"].hidden_tag() }} {{bp.data["form"].hidden_tag() }}
{{form.field(bp.data["form"].username)}} {{form.field(bp.data["form"].username)}}
{{form.field(bp.data["form"].password)}} {{form.field(bp.data["form"].password)}}

View File

@ -9,12 +9,11 @@
<h1 class="text-center text-primary-emphasis">{{_('page.register.title')}}</h1> <h1 class="text-center text-primary-emphasis">{{_('page.register.title')}}</h1>
<form class="border border-secondary bg-secondary-subtle rounded p-3" method="POST"> <form class="border border-secondary bg-secondary-subtle rounded p-3" method="POST">
{%if bp.data["status"]%} {%include "components/flash.jinja"%}
<div class="alert alert-{{bp.data['status_class']}}" role="alert">{{bp.data["status"]}}</div>
{%endif%}
{{bp.data["form"].hidden_tag() }} {{bp.data["form"].hidden_tag() }}
{{form.field(input=bp.data["form"].username, id="username", prefix="@")}} {{form.field(input=bp.data["form"].username, id="username", prefix="@")}}
{{form.field(input=bp.data["form"].password)}} {{form.field(input=bp.data["form"].password)}}
{{form.field(input=bp.data["form"].retype_password)}}
{{form.field(input=bp.data["form"].email)}} {{form.field(input=bp.data["form"].email)}}
<button type="submit" class="my-3 btn btn-primary text-center">{{_('page.register.form.submit')}}</button> <button type="submit" class="my-3 btn btn-primary text-center">{{_('page.register.form.submit')}}</button>
</form> </form>