Browse Source

registration: ADD email validation

pull/61/head
Darks 2 months ago
parent
commit
8bf825f9ea
Signed by: Darks <l.gatin@neuf.fr> GPG Key ID: F61F10FA138E797C
10 changed files with 141 additions and 11 deletions
  1. +2
    -0
      REQUIREMENTS.md
  2. +2
    -0
      app/__init__.py
  3. +2
    -0
      app/models/users.py
  4. +35
    -3
      app/routes/account/account.py
  5. +16
    -8
      app/routes/account/login.py
  6. +14
    -0
      app/templates/email/activate.html
  7. +9
    -0
      app/templates/email/activate.md
  8. +24
    -0
      app/utils/send_mail.py
  9. +9
    -0
      config.py
  10. +28
    -0
      migrations/versions/acf72cf31eea_added_email_validated_property_in_model_.py

+ 2
- 0
REQUIREMENTS.md View File

@@ -8,10 +8,12 @@ La liste de paquets fourni est pour Archlinux, les paquets peuvent avoir des nom
python3
python-flask
python-flask-login
python-flask-mail
python-flask-migrate
python-flask-script
python-flask-sqlalchemy
python-flask-wtf
python-itsdangerous
python-ldap
python-uwsgi
python-psycopg2


+ 2
- 0
app/__init__.py View File

@@ -2,6 +2,7 @@ from flask import Flask, g
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_mail import Mail
from config import Config
import time

@@ -14,6 +15,7 @@ if Config.SECRET_KEY == "a-random-secret-key":

db = SQLAlchemy(app)
migrate = Migrate(app, db)
mail = Mail(app)

login = LoginManager(app)
login.login_view = 'login'


+ 2
- 0
app/models/users.py View File

@@ -76,6 +76,7 @@ class Member(User):
norm = db.Column(db.Unicode(V5Config.USER_NAME_MAXLEN), index=True,
unique=True)
email = db.Column(db.Unicode(120), index=True, unique=True)
email_confirmed = db.Column(db.Boolean)
password_hash = db.Column(db.String(255))
xp = db.Column(db.Integer)
register_date = db.Column(db.Date, default=date.today)
@@ -118,6 +119,7 @@ class Member(User):
self.name = name
self.norm = unicode_names.normalize(name)
self.email = email
self.email_confirmed = not V5Config.ENABLE_EMAIL_CONFIRMATION
if not V5Config.USE_LDAP:
self.set_password(password)
# Workflow with LDAP enabled is User → Postgresql → LDAP → set password


+ 35
- 3
app/routes/account/account.py View File

@@ -1,10 +1,12 @@
from flask import redirect, url_for, request, flash
from flask import redirect, url_for, request, flash, abort
from flask_login import login_required, current_user, logout_user
from app import app, db
from app.forms.account import UpdateAccountForm, RegistrationForm, DeleteAccountForm
from app.models.users import Member
from app.utils.render import render
from app.utils.send_mail import send_validation_mail
import app.utils.ldap as ldap
from itsdangerous import URLSafeTimedSerializer
from config import V5Config


@@ -66,13 +68,43 @@ def register():
ldap.add_member(member)
ldap.set_password(member, form.password.data)
flash('Inscription réussie', 'ok')

# Email validation message
send_validation_mail(member.name, member.email)

return redirect(url_for('validation') + "?email=" + form.email.data)
return render('account/register.html', title='Register', form=form)


@app.route('/register/validation/', methods=['GET', 'POST'])
@app.route('/inscription/validation', methods=['GET'])
def validation():
mail = request.args['email']
try:
mail = request.args['email']
except Exception as e:
print("Error: {e}")
abort(404)

if current_user.is_authenticated:
return redirect(url_for('index'))
return render('account/validation.html', mail=mail)

@app.route('/inscription/validation/<token>', methods=['GET'])
def activate_account(token):
if current_user.is_authenticated:
return redirect(url_for('index'))

try:
ts = URLSafeTimedSerializer(app.config["SECRET_KEY"])
email = ts.loads(token, salt="email-confirm-key", max_age=86400)
except Exception as e:
print(f"Error: {e}")
abort(404)

m = Member.query.filter_by(email=email).first_or_404()
m.email_confirmed = True

db.session.add(m)
db.session.commit()

flash("L'email a bien été confirmé", "ok")
return redirect(url_for('login'))

+ 16
- 8
app/routes/account/login.py View File

@@ -6,11 +6,19 @@ from app.forms.login import LoginForm
from app.models.users import Member
from app.models.privs import Group
from app.utils.render import render
from app.utils.send_mail import send_validation_mail
from config import V5Config


@app.route('/connexion', methods=['GET', 'POST'])
def login():
# If something failed, return abort("Message")
def _abort(msg):
flash(msg, 'error')
if request.referrer:
return redirect(request.referrer)
return redirect(url_for('index'))

if current_user.is_authenticated:
return redirect(url_for('index'))

@@ -24,17 +32,17 @@ def login():

# Check if member can login
if member is not None and "No login" in [g.name for g in member.groups]:
flash('Cet utilisateur ne peut pas se connecter', 'error')
if request.referrer:
return redirect(request.referrer)
return redirect(url_for('index'))
return _abort('Cet utilisateur ne peut pas se connecter')

# Check if password is ok
if member is None or not member.check_password(form.password.data):
flash('Pseudo ou mot de passe invalide', 'error')
if request.referrer:
return redirect(request.referrer)
return redirect(url_for('index'))
return _abort('Pseudo ou mot de passe invalide')

# Check if user is activated
if member.email_confirmed == False:
# Send another mail
send_validation_mail(member.name, member.email)
return _abort(f"Email non confirmé. Un mail de confirmation a de nouveau été envoyé à l'adresse {member.email}")

# Login & update time-based trophies
login_user(member, remember=form.remember_me.data,


+ 14
- 0
app/templates/email/activate.html View File

@@ -0,0 +1,14 @@
<html>
<body>
<div>Bonjour {{ name }},</div>

<div>Bienvenue sur Planète Casio !</div>

<div>Afin de pouvoir vous connecter et utiliser nos services, merci de confirmer cette adresse email en cliquant <a href="{{ confirm_url }}">ici</a>.</div>
<div>Si ça ne fonctionne pas, utilisez ce lien : <a href="{{ confirm_url }}">{{ confirm_url }}</a></div>

<div>À bientôt sur Planète Casio !</div>

<div>L'équipe du site.</div>
</body>
</html>

+ 9
- 0
app/templates/email/activate.md View File

@@ -0,0 +1,9 @@
Bonjour {{ name }},

Bienvenue sur Planète Casio !

Afin de pouvoir vous connecter et utiliser nos services, merci de confirmer cette adresse email en cliquant sur ce lien : {{ confirm_url }}

À bientôt sur Planète Casio !

L'équipe du site.

+ 24
- 0
app/utils/send_mail.py View File

@@ -0,0 +1,24 @@
from flask import url_for, render_template
from flask_mail import Message
from app import app, mail
from itsdangerous import URLSafeTimedSerializer
from config import V5Config

def send_mail(dest, subject, html="", body=""):
m = Message(recipients=[dest], subject=subject, html=html, body=body)
mail.send(m)

def send_validation_mail(name, email):
ts = URLSafeTimedSerializer(app.config["SECRET_KEY"])
token = ts.dumps(email, salt='email-confirm-key')
confirm_url = f"https://{V5Config.DOMAIN}" \
+ url_for('activate_account', token=token)
subject = "Confirmez votre email pour compléter l'inscription"
html = render_template('email/activate.html', confirm_url=confirm_url,
name=name)
body = render_template('email/activate.md', confirm_url=confirm_url,
name=name)
if V5Config.SEND_MAILS:
send_mail(member.email, subject, html=html, body=body)
else:
print(f"Email confirmation url: {confirm_url}")

+ 9
- 0
config.py View File

@@ -16,9 +16,14 @@ class Config(object):
+ LocalConfig.DB_NAME
SQLALCHEMY_TRACK_MODIFICATIONS = False

MAIL_DEFAULT_SENDER = "noreply@v5.planet-casio.com"
MAIL_SUPPRESS_SEND = None


class DefaultConfig(object):
"""Every value here can be overrided in the local_config.py class"""
# Domain
DOMAIN = "v5.planet-casio.com"
# Length allocated to privilege names (slugs)
PRIVS_MAXLEN = 64
# Forbidden user names
@@ -57,6 +62,10 @@ class DefaultConfig(object):
AVATARS_FOLDER = '/avatar/folder/'
# Enable guest post
ENABLE_GUEST_POST = True
# Disable email confimation
ENABLE_EMAIL_CONFIRMATION = True
# Send emails
SEND_MAILS = True

class V5Config(LocalConfig, DefaultConfig):
# Values put here cannot be overidden with local_config


+ 28
- 0
migrations/versions/acf72cf31eea_added_email_validated_property_in_model_.py View File

@@ -0,0 +1,28 @@
"""Added email_validated property in model `Member`

Revision ID: acf72cf31eea
Revises: 6fd4c15b8a7b
Create Date: 2020-07-21 20:29:44.876704

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'acf72cf31eea'
down_revision = '6fd4c15b8a7b'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('member', sa.Column('email_confirmed', sa.Boolean(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('member', 'email_confirmed')
# ### end Alembic commands ###

Loading…
Cancel
Save