fix: disable public signup and restore local login flow
This commit is contained in:
19
app/cli.py
19
app/cli.py
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import click
|
||||
|
||||
from .extensions import db
|
||||
from .models import BadgeDefinition
|
||||
from .models import BadgeDefinition, User
|
||||
from .services.monthly import archive_months_missing_up_to_previous
|
||||
from .services.notifications import send_due_notifications, send_monthly_winner_notifications
|
||||
|
||||
@@ -54,6 +54,22 @@ def register_cli(app) -> None:
|
||||
seed_badges()
|
||||
click.echo("Datenbank und Standard-Badges sind bereit.")
|
||||
|
||||
@app.cli.command("create-user")
|
||||
@click.option("--name", prompt=True, help="Anzeigename")
|
||||
@click.option("--email", prompt=True, help="E-Mail")
|
||||
@click.option("--password", prompt=True, hide_input=True, confirmation_prompt=True, help="Passwort")
|
||||
def create_user_command(name: str, email: str, password: str):
|
||||
existing = User.query.filter_by(email=email.lower().strip()).first()
|
||||
if existing:
|
||||
click.echo("Es existiert bereits ein Nutzer mit dieser E-Mail.")
|
||||
raise SystemExit(1)
|
||||
|
||||
user = User(name=name.strip(), email=email.lower().strip())
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
click.echo(f"Nutzer {user.email} wurde angelegt.")
|
||||
|
||||
@app.cli.command("archive-months")
|
||||
def archive_months_command():
|
||||
archive_months_missing_up_to_previous()
|
||||
@@ -68,4 +84,3 @@ def register_cli(app) -> None:
|
||||
def notify_monthly_winner_command():
|
||||
result = send_monthly_winner_notifications()
|
||||
click.echo(f"Winner-Push: sent={result.sent} skipped={result.skipped} failed={result.failed}")
|
||||
|
||||
|
||||
15
app/forms.py
15
app/forms.py
@@ -5,21 +5,23 @@ from flask_wtf.file import FileAllowed, FileField
|
||||
from wtforms import (
|
||||
BooleanField,
|
||||
DateField,
|
||||
EmailField,
|
||||
IntegerField,
|
||||
PasswordField,
|
||||
SelectField,
|
||||
StringField,
|
||||
SelectField,
|
||||
SubmitField,
|
||||
TextAreaField,
|
||||
)
|
||||
from wtforms.validators import DataRequired, Email, EqualTo, Length, NumberRange, Optional, ValidationError
|
||||
from wtforms.validators import DataRequired, EqualTo, Length, NumberRange, Optional, Regexp, ValidationError
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
EMAIL_LIKE = Regexp(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", message="Bitte gib eine gültige E-Mail-Adresse ein.")
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
email = EmailField("E-Mail", validators=[DataRequired(), Email(), Length(max=255)])
|
||||
email = StringField("E-Mail", validators=[DataRequired(), EMAIL_LIKE, Length(max=255)])
|
||||
password = PasswordField("Passwort", validators=[DataRequired(), Length(min=6, max=128)])
|
||||
remember_me = BooleanField("Angemeldet bleiben")
|
||||
submit = SubmitField("Einloggen")
|
||||
@@ -27,7 +29,7 @@ class LoginForm(FlaskForm):
|
||||
|
||||
class RegisterForm(FlaskForm):
|
||||
name = StringField("Name", validators=[DataRequired(), Length(min=2, max=120)])
|
||||
email = EmailField("E-Mail", validators=[DataRequired(), Email(), Length(max=255)])
|
||||
email = StringField("E-Mail", validators=[DataRequired(), EMAIL_LIKE, Length(max=255)])
|
||||
password = PasswordField("Passwort", validators=[DataRequired(), Length(min=6, max=128)])
|
||||
password_confirm = PasswordField(
|
||||
"Passwort wiederholen",
|
||||
@@ -76,7 +78,7 @@ class TaskForm(FlaskForm):
|
||||
|
||||
class SettingsProfileForm(FlaskForm):
|
||||
name = StringField("Name", validators=[DataRequired(), Length(min=2, max=120)])
|
||||
email = EmailField("E-Mail", validators=[DataRequired(), Email(), Length(max=255)])
|
||||
email = StringField("E-Mail", validators=[DataRequired(), EMAIL_LIKE, Length(max=255)])
|
||||
password = PasswordField("Neues Passwort", validators=[Optional(), Length(min=6, max=128)])
|
||||
avatar = FileField(
|
||||
"Avatar",
|
||||
@@ -96,4 +98,3 @@ class SettingsProfileForm(FlaskForm):
|
||||
return
|
||||
if User.query.filter_by(email=value).first():
|
||||
raise ValidationError("Diese E-Mail-Adresse wird bereits verwendet.")
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ from ..models import User
|
||||
bp = Blueprint("auth", __name__)
|
||||
|
||||
|
||||
def registration_open() -> bool:
|
||||
return User.query.count() == 0
|
||||
|
||||
|
||||
@bp.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
@@ -24,13 +28,16 @@ def login():
|
||||
flash(f"Willkommen zurück, {user.name}.", "success")
|
||||
return redirect(url_for("tasks.my_tasks"))
|
||||
flash("Die Kombination aus E-Mail und Passwort passt leider nicht.", "error")
|
||||
return render_template("auth/login.html", form=form)
|
||||
return render_template("auth/login.html", form=form, registration_open=registration_open())
|
||||
|
||||
|
||||
@bp.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for("tasks.my_tasks"))
|
||||
if not registration_open():
|
||||
flash("Freie Registrierung ist deaktiviert.", "info")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
form = RegisterForm()
|
||||
if form.validate_on_submit():
|
||||
@@ -50,4 +57,3 @@ def logout():
|
||||
logout_user()
|
||||
flash("Du bist jetzt abgemeldet.", "info")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 358 B |
Binary file not shown.
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -1,6 +1,5 @@
|
||||
const CACHE_NAME = "putzliga-shell-v1";
|
||||
const CACHE_NAME = "putzliga-shell-v2";
|
||||
const ASSETS = [
|
||||
"/my-tasks",
|
||||
"/static/css/style.css",
|
||||
"/static/js/app.js",
|
||||
"/static/images/logo.svg",
|
||||
@@ -24,6 +23,24 @@ self.addEventListener("activate", (event) => {
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") return;
|
||||
const url = new URL(event.request.url);
|
||||
const isStaticAsset =
|
||||
url.origin === self.location.origin &&
|
||||
(
|
||||
url.pathname.startsWith("/static/") ||
|
||||
url.pathname === "/manifest.json" ||
|
||||
url.pathname === "/service-worker.js"
|
||||
);
|
||||
|
||||
if (event.request.mode === "navigate") {
|
||||
event.respondWith(fetch(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isStaticAsset) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request).then((cached) => {
|
||||
if (cached) return cached;
|
||||
@@ -32,8 +49,7 @@ self.addEventListener("fetch", (event) => {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match("/my-tasks"));
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,8 +41,11 @@
|
||||
{{ form.submit(class_="button button--wide") }}
|
||||
</form>
|
||||
<p class="inline-note">Demo-Logins nach dem Seeden: `anna@putzliga.local` / `putzliga123` und `ben@putzliga.local` / `putzliga123`.</p>
|
||||
<p class="inline-note">Noch kein Konto? <a href="{{ url_for('auth.register') }}">Neu registrieren</a></p>
|
||||
{% if registration_open %}
|
||||
<p class="inline-note">Es gibt noch keinen Nutzer. <a href="{{ url_for('auth.register') }}">Ersten Account anlegen</a></p>
|
||||
{% else %}
|
||||
<p class="inline-note">Freie Registrierung ist deaktiviert.</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user