feat: Enhance app config and user feedback
Introduced changes to `app.py` and templates to enhance application configuration options, improve user feedback mechanisms, and bolster security practices. Key updates include: - Expanded Flask's configuration based on `settings.ini`, enabling `debug` mode and applying `ProxyFix` middleware conditionally to support reverse proxy setups. - Extended the functionality to include dynamic footer links, sourced from the configuration file, across all relevant templates. This contributes to a more dynamic and maintainable web interface. - Adjusted the rate limiting functionality from a 1-hour to a 1-day window, offering a more lenient and user-friendly request limitation system. - Implemented an error handling flow for user creation in Planka, providing clearer feedback when password requirements are not met, thus enhancing the user signup experience. - Added a new cron route for cleaning up stale requests from the database, aligning data retention practices with privacy concerns. These changes aim to provide a more configurable, user-friendly, and secure application, addressing feedback and evolving requirements.
This commit is contained in:
parent
79e83aa0a7
commit
89825cf85c
4 changed files with 105 additions and 12 deletions
73
app.py
73
app.py
|
@ -1,11 +1,13 @@
|
|||
from flask import Flask, request, redirect, url_for, render_template
|
||||
from flask import Flask, request, redirect, url_for, render_template, jsonify
|
||||
from plankapy import Planka, User, InvalidToken
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, PasswordField
|
||||
from wtforms.validators import DataRequired, Email, ValidationError
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from configparser import ConfigParser
|
||||
from random import SystemRandom
|
||||
from typing import List, Tuple
|
||||
|
||||
import sqlite3
|
||||
import smtplib
|
||||
|
@ -22,6 +24,12 @@ app.config["SECRET_KEY"] = "".join(
|
|||
config = ConfigParser()
|
||||
config.read("settings.ini")
|
||||
|
||||
if config.getboolean("App", "debug", fallback=False):
|
||||
app.debug = True
|
||||
|
||||
if config.getboolean("App", "proxyfix", fallback=False):
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||
|
||||
|
||||
def initialize_database():
|
||||
conn = sqlite3.connect("db.sqlite3")
|
||||
|
@ -49,7 +57,7 @@ def rate_limit(request):
|
|||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM requests
|
||||
WHERE ip = ? AND created_at > datetime('now', '-1 hour')
|
||||
WHERE ip = ? AND created_at > datetime('now', '-1 day')
|
||||
""",
|
||||
(request.remote_addr,),
|
||||
)
|
||||
|
@ -76,6 +84,16 @@ def get_mailserver():
|
|||
return mailserver
|
||||
|
||||
|
||||
def get_footer_links() -> List[Tuple[str, str]]:
|
||||
links = []
|
||||
|
||||
if "Footer" in config.sections():
|
||||
for key in config["Footer"]:
|
||||
links.append((key.capitalize(), config["Footer"][key]))
|
||||
|
||||
return links
|
||||
|
||||
|
||||
def send_email(email, token):
|
||||
mailserver = get_mailserver()
|
||||
sender = config.get("SMTP", "from", fallback=config["SMTP"]["username"])
|
||||
|
@ -126,6 +144,7 @@ def process_request(request):
|
|||
app=config["App"]["name"],
|
||||
title="Already Requested",
|
||||
subtitle="You have already requested access with this email address.",
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
token = str(uuid.uuid4())
|
||||
|
@ -154,7 +173,13 @@ class EmailForm(FlaskForm):
|
|||
@app.route("/", methods=["GET", "POST"])
|
||||
def start_request():
|
||||
if rate_limit(request):
|
||||
return render_template("rate_limit.html")
|
||||
return render_template(
|
||||
"rate_limit.html",
|
||||
app=config["App"]["name"],
|
||||
title="Rate Limited",
|
||||
subtitle="You have reached the rate limit for requests. Please try again later.",
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
form = EmailForm()
|
||||
|
||||
|
@ -167,6 +192,7 @@ def start_request():
|
|||
title="Request Access",
|
||||
subtitle="Please enter your email address to request access.",
|
||||
form=form,
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -177,6 +203,7 @@ def post_request():
|
|||
app=config["App"]["name"],
|
||||
title="Request Received",
|
||||
subtitle="Your request has been received. Please check your email for further instructions.",
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -187,8 +214,6 @@ class SignupForm(FlaskForm):
|
|||
password = PasswordField("Password", validators=[DataRequired()])
|
||||
submit = SubmitField("Submit")
|
||||
|
||||
email.render_kw = {"readonly": True}
|
||||
|
||||
def validate_username(self, field):
|
||||
planka = Planka(
|
||||
url=config["Planka"]["url"],
|
||||
|
@ -231,6 +256,7 @@ def confirm_request(token):
|
|||
app=config["App"]["name"],
|
||||
title="Invalid Token",
|
||||
subtitle="The token you provided is invalid.",
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
email = row[0]
|
||||
|
@ -252,7 +278,22 @@ def confirm_request(token):
|
|||
email=email,
|
||||
)
|
||||
|
||||
users.create(new_user)
|
||||
try:
|
||||
users.create(new_user)
|
||||
except InvalidToken:
|
||||
form.password.errors.append(
|
||||
"Your password did not meet Planka's requirements. Please try again."
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"signup.html",
|
||||
app=config["App"]["name"],
|
||||
title="Complete Signup",
|
||||
subtitle="Please confirm your email address by filling out the form below.",
|
||||
email=email,
|
||||
form=form,
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
|
@ -274,6 +315,7 @@ def confirm_request(token):
|
|||
subtitle="Please confirm your email address by filling out the form below.",
|
||||
email=email,
|
||||
form=form,
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -285,7 +327,26 @@ def post_signup():
|
|||
title="Signup Complete",
|
||||
subtitle="Your account has been created. You may now log in.",
|
||||
planka=config["Planka"]["url"],
|
||||
footer_links=get_footer_links(),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/cron")
|
||||
def cron():
|
||||
conn = sqlite3.connect("db.sqlite3")
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
DELETE FROM requests
|
||||
WHERE created_at < datetime('now', '-2 day')
|
||||
"""
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
|
||||
initialize_database()
|
||||
|
|
29
settings.dist.ini
Normal file
29
settings.dist.ini
Normal file
|
@ -0,0 +1,29 @@
|
|||
[App]
|
||||
# Name of the app
|
||||
Name = Private.coffee Planka
|
||||
|
||||
# Hostname of the app - the app always assumes that HTTPS is used
|
||||
Host = register.planka.private.coffee
|
||||
|
||||
# Set to 1 if you are using a reverse proxy in front of the app
|
||||
ProxyFix = 1
|
||||
|
||||
[SMTP]
|
||||
# SMTP server settings
|
||||
Host = mail.local
|
||||
Port = 587
|
||||
Username = planka@mail.local
|
||||
Password = verysecurepassword
|
||||
SSL = 1 # Set to 0 if you are not using SSL/TLS
|
||||
STARTTLS = 1 # Set to 0 if you are not using STARTTLS
|
||||
|
||||
[Planka]
|
||||
# URL and credentials for the Planka instance
|
||||
URL = https://planka.local
|
||||
Username = admin@mail.local
|
||||
Password = extremelysecurepassword
|
||||
|
||||
[Footer]
|
||||
Website = https://private.coffee
|
||||
Legal = https://private.coffee/legal.html
|
||||
Privacy = https://private.coffee/privacy.html
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ app }} - {{ title }}</title>
|
||||
<link rel="stylesheet" href="static/style.css" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
@ -22,7 +22,9 @@
|
|||
<footer>
|
||||
<div class="container">
|
||||
<p>
|
||||
© 2024 Private.coffee |
|
||||
© 2024 Private.coffee {% for link in footer_links %} |
|
||||
<a href="{{ link.1 }}">{{ link.0 }}</a>
|
||||
{% endfor %} |
|
||||
<a href="https://git.private.coffee/PrivateCoffee/planka-register"
|
||||
>Git</a
|
||||
>
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
<strong>Privacy notice:</strong>
|
||||
The provided data will be stored in our database to allow you to log in to
|
||||
our Planka instance. Your data will not be used for any other purpose.
|
||||
Note that your name will be visible to other users of the Planka instance.
|
||||
Note that your name, email address and any other data you add to your
|
||||
profile will be visible to other users of the Planka instance.
|
||||
</small>
|
||||
</p>
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }} {{ form.email.label }}: {{ form.email(size=20, disabled="disabled", value=email) }}<br />
|
||||
{{ form.hidden_tag() }} {{ form.email.label }}: {{ form.email(size=20,
|
||||
disabled="disabled", value=email) }}<br />
|
||||
{% for error in form.email.errors %}
|
||||
<span style="color: red">[{{ error }}]</span>
|
||||
{% endfor %}
|
||||
|
@ -29,8 +31,7 @@
|
|||
{{ form.password.label }}: {{ form.password(size=20) }}<br />
|
||||
{% for error in form.password.errors %}
|
||||
<span style="color: red">[{{ error }}]</span>
|
||||
{% endfor %}
|
||||
{{ form.submit() }}
|
||||
{% endfor %} {{ form.submit() }}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue