add confirmation via unique link

This commit is contained in:
pstruebi
2022-06-14 17:35:52 +02:00
parent e287a09e1b
commit 4518f0b290
7 changed files with 48 additions and 28 deletions

47
auth.py
View File

@@ -1,4 +1,6 @@
from datetime import datetime
import functools import functools
import logging
from django import db from django import db
from flask import ( from flask import (
@@ -8,8 +10,8 @@ from werkzeug.security import check_password_hash, generate_password_hash
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
from db import get_db from db import get_db
from app import app import app
import calendar_interface
bp = Blueprint('auth', __name__, url_prefix='/auth') bp = Blueprint('auth', __name__, url_prefix='/auth')
@@ -29,6 +31,7 @@ def register():
error = 'Password is required.' error = 'Password is required.'
elif not email: elif not email:
error = 'Email is required.' error = 'Email is required.'
# TODO: check passwrd complexity
if error is None: if error is None:
try: try:
@@ -37,10 +40,19 @@ def register():
(username, email, generate_password_hash(password)), (username, email, generate_password_hash(password)),
) )
db.commit() db.commit()
except db.IntegrityError: except db.IntegrityError:
error = f"User mit email {email} existiert bereits." error = f"User mit email {email} existiert bereits."
else: else:
return redirect(url_for("auth.login")) token = generate_confirmation_token(email)
confirm_url = url_for('auth.confirm_email', token=token, _external=True)
logging.info("Generated confirmation url: %s", confirm_url)
content = render_template('auth/confirm_mail.html', confirm_url=confirm_url, username=username, email=email)
subject = "Registrierung für Planner von %s" % email
calendar_interface.send_mail("struebin.patrick@gmail.com", subject, content)
flash("Registrierung erfolgreich.")
return redirect(url_for("index"))
flash(error) flash(error)
@@ -96,6 +108,7 @@ def login_required(view): # use this as decorator
@functools.wraps(view) @functools.wraps(view)
def wrapped_view(**kwargs): def wrapped_view(**kwargs):
if g.user is None: if g.user is None:
flash("Benutzer bitte anmelden.")
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
elif not g.user["confirmed"]: elif not g.user["confirmed"]:
flash("Benutzer noch nicht freigeschaltet.") flash("Benutzer noch nicht freigeschaltet.")
@@ -107,17 +120,17 @@ def login_required(view): # use this as decorator
def generate_confirmation_token(email): def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) serializer = URLSafeTimedSerializer(app.app.config['SECRET_KEY'])
return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT']) return serializer.dumps(email, salt=app.app.config['SECURITY_PASSWORD_SALT'])
def confirm_token(token, expiration=3600): def confirm_token(token, expiration=3600*14*28): # 2 Wochen expiration
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) serializer = URLSafeTimedSerializer(app.app.config['SECRET_KEY'])
try: try:
email = serializer.loads( email = serializer.loads(
token, token,
salt=app.config['SECURITY_PASSWORD_SALT'], salt=app.app.config['SECURITY_PASSWORD_SALT'],
# max_age=expiration max_age=expiration
) )
except: except:
return False return False
@@ -125,22 +138,22 @@ def confirm_token(token, expiration=3600):
@bp.route('/confirm/<token>') @bp.route('/confirm/<token>')
@login_required # @login_required # TODO: notwendig? security ?
def confirm_email(token): def confirm_email(token):
try: try:
email = confirm_token(token) email = confirm_token(token)
except: except:
flash('The confirmation link is invalid or has expired.', 'danger') outcome = 'The confirmation link is invalid or has expired.'
if g.user["confirmed"]: if g.user["confirmed"]:
flash('Account already confirmed. Please login.', 'success') outcome = 'Account already confirmed.'
else: else:
db = get_db() db = get_db()
db.execute( db.execute(
"UPDATE user SET confirmed = '1' where email = ?", "UPDATE user SET confirmed = '1', confirmation_date = ? where email = ?",
(email,) (str(datetime.now()), email)
) )
db.commit() db.commit()
outcome= "User wurde erfolgreich freigeschaltet."
flash('You have confirmed your account. Thanks!', 'success') return render_template('auth/confirmation_successful.html', email=email, outcome=outcome)
return redirect(url_for('main.home'))

View File

@@ -85,10 +85,7 @@ def execute_patch_request(token, endpoint, data):
def send_mail(to, subject, content, user_id=USER_ID): def send_mail(to, subject, content, user_id=USER_ID):
token=get_access_token() token=get_access_token()
with open("templates/auth/activate.html") as f:
content=f.read()
mail = { mail = {
"message": { "message": {
"subject": subject, "subject": subject,

View File

@@ -5,5 +5,6 @@ CREATE TABLE user (
username TEXT NOT NULL, username TEXT NOT NULL,
email TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,
confirmed BOOLEAN DEFAULT FALSE confirmed BOOLEAN DEFAULT FALSE,
confirmation_date TEXT
); );

View File

@@ -1,4 +0,0 @@
<p>Welcome! Thanks for signing up. Please follow this link to activate your account:</p>
<p><a href="{{ confirm_url }}">{{ confirm_url }}</a></p>
<br>
<p>Cheers!</p>

View File

@@ -0,0 +1,6 @@
<p>{{username}} with email {{email}} wants to be confirmed.</p>
<p>follow this link only if the user can be trusted to activate the account:</p>
<p><a href="{{ confirm_url }}"> {{ confirm_url }}
</a></p>
<br>
<p>Cheers!</p>

View File

@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %}Benutzer mit email: {{email}}.{% endblock %}</h1>
<h1>{% block title %}{{outcome}}.{% endblock %}</h1>
{% endblock %}

View File

@@ -6,10 +6,10 @@
{% block content %} {% block content %}
<form method="post"> <form method="post">
<label for="email">Email (Account login)</label>
<input type="email" name="email" id="email" required>
<label for="username">Name</label> <label for="username">Name</label>
<input name="username" id="username" required> <input name="username" id="username" required>
<label for="email">Email</label>
<input type="email" name="email" id="email" required>
<label for="password">Password</label> <label for="password">Password</label>
<input type="password" name="password" id="password" required> <input type="password" name="password" id="password" required>
<input type="submit" value="Register"> <input type="submit" value="Register">