Add email support

This commit is contained in:
rubenwardy 2018-05-14 00:40:34 +01:00
parent acc48c7262
commit 73fa5d1186
No known key found for this signature in database
GPG Key ID: A1E29D52FF81513C
8 changed files with 91 additions and 25 deletions

@ -1,13 +1,12 @@
from flask import * from flask import *
from flask_user import * from flask_user import *
import flask_menu as menu import flask_menu as menu
from flask_mail import Mail
from flask.ext import markdown from flask.ext import markdown
from flask_github import GitHub from flask_github import GitHub
from flask_wtf.csrf import CsrfProtect from flask_wtf.csrf import CsrfProtect
import os import os
app = Flask(__name__) app = Flask(__name__)
app.config.from_pyfile(os.environ["FLASK_CONFIG"]) app.config.from_pyfile(os.environ["FLASK_CONFIG"])
@ -15,6 +14,7 @@ menu.Menu(app=app)
markdown.Markdown(app, extensions=["fenced_code"], safe_mode=True, output_format="html5") markdown.Markdown(app, extensions=["fenced_code"], safe_mode=True, output_format="html5")
github = GitHub(app) github = GitHub(app)
csrf = CsrfProtect(app) csrf = CsrfProtect(app)
mail = Mail(app)
from . import models, tasks from . import models, tasks
from .views import * from .views import *

@ -49,6 +49,7 @@ class Permission(enum.Enum):
APPROVE_NEW = "APPROVE_NEW" APPROVE_NEW = "APPROVE_NEW"
CHANGE_RELEASE_URL = "CHANGE_RELEASE_URL" CHANGE_RELEASE_URL = "CHANGE_RELEASE_URL"
CHANGE_RANK = "CHANGE_RANK" CHANGE_RANK = "CHANGE_RANK"
CHANGE_EMAIL = "CHANGE_EMAIL"
EDIT_EDITREQUEST = "EDIT_EDITREQUEST" EDIT_EDITREQUEST = "EDIT_EDITREQUEST"
# Only return true if the permission is valid for *all* contexts # Only return true if the permission is valid for *all* contexts
@ -119,9 +120,17 @@ class User(db.Model, UserMixin):
return user.rank.atLeast(UserRank.EDITOR) return user.rank.atLeast(UserRank.EDITOR)
elif perm == Permission.CHANGE_RANK: elif perm == Permission.CHANGE_RANK:
return user.rank.atLeast(UserRank.MODERATOR) return user.rank.atLeast(UserRank.MODERATOR)
elif perm == Permission.CHANGE_EMAIL:
return user == self or user.rank.atLeast(UserRank.MODERATOR)
else: else:
raise Exception("Permission {} is not related to users".format(perm.name)) raise Exception("Permission {} is not related to users".format(perm.name))
class UserEmailVerification(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
email = db.Column(db.String(100))
token = db.Column(db.String(32))
user = db.relationship("User", foreign_keys=[user_id])
class Notification(db.Model): class Notification(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

@ -41,4 +41,4 @@ def make_celery(app):
celery = make_celery(app) celery = make_celery(app)
from . import importtasks, forumtasks from . import importtasks, forumtasks, emails

11
app/tasks/emails.py Normal file

@ -0,0 +1,11 @@
from flask import *
from flask_mail import Message
from app import mail
from app.tasks import celery
@celery.task()
def sendVerifyEmail(newEmail, token):
msg = Message("Verify email address", recipients=[newEmail])
msg.body = "This is a verification email!"
msg.html = render_template("emails/verify.html", token=token)
mail.send(msg)

@ -0,0 +1,17 @@
<h1>Hello!</h1>
<p>
This email has been sent to you because someone (hopefully you)
has entered your email address as a user's email.
</p>
<p>
If this was you, then please click this link to verify the address:
<a href="{{ url_for('verify_email_page', token=token, _external=True) }}">
{{ url_for('verify_email_page', token=token, _external=True) }}
</a>
</p>
<p>
If it wasn't you, then just delete this email.
</p>

@ -42,24 +42,6 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% if user == current_user %}
<tr>
<td>Email:</td>
<td>
{{ user.email }} |
<a href="">{% if user.email %}change{% else %}add{% endif %}</a>
&#x1f512;
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<a href="{{ url_for('user.change_password') }}">
{% if user.password %}Change password{% else %}Add password{% endif %}
</a> &#x1f512;
</td>
</tr>
{% endif %}
</table> </table>
</div> </div>
@ -90,10 +72,14 @@
<div class="col-sm-6 col-md-5 col-lg-4"> <div class="col-sm-6 col-md-5 col-lg-4">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{{ render_field(form.display_name, tabindex=240) }} {{ render_field(form.display_name, tabindex=230) }}
{% if user.checkPerm(current_user, "CHANGE_EMAIL") %}
{{ render_field(form.email, tabindex=240) }}
{% endif %}
{% if user.checkPerm(current_user, "CHANGE_RANK") %} {% if user.checkPerm(current_user, "CHANGE_RANK") %}
{{ render_field(form.rank, tabindex=240) }} {{ render_field(form.rank, tabindex=250) }}
{% endif %} {% endif %}
{{ render_submit_field(form.submit, tabindex=280) }} {{ render_submit_field(form.submit, tabindex=280) }}

@ -10,10 +10,12 @@ from wtforms import *
from wtforms.validators import * from wtforms.validators import *
from .utils import rank_required, randomString from .utils import rank_required, randomString
from app.tasks.forumtasks import checkForumAccount from app.tasks.forumtasks import checkForumAccount
from app.tasks.emails import sendVerifyEmail
# Define the User profile form # Define the User profile form
class UserProfileForm(FlaskForm): class UserProfileForm(FlaskForm):
display_name = StringField("Display name") display_name = StringField("Display name", [InputRequired(), Length(2, 20)])
email = StringField("Email")
rank = SelectField("Rank", [InputRequired()], choices=UserRank.choices(), coerce=UserRank.coerce, default=UserRank.NEW_MEMBER) rank = SelectField("Rank", [InputRequired()], choices=UserRank.choices(), coerce=UserRank.coerce, default=UserRank.NEW_MEMBER)
submit = SubmitField("Save") submit = SubmitField("Save")
@ -48,6 +50,21 @@ def user_profile_page(username):
else: else:
flash("Can't promote a user to a rank higher than yourself!", "error") flash("Can't promote a user to a rank higher than yourself!", "error")
if user.checkPerm(current_user, Permission.CHANGE_EMAIL):
newEmail = form["email"].data
if newEmail != user.email:
token = randomString(32)
ver = UserEmailVerification()
ver.user = user
ver.token = token
ver.email = newEmail
db.session.add(ver)
db.session.commit()
task = sendVerifyEmail.delay(newEmail, token)
return redirect(url_for("check_task", id=task.id, r=url_for("user_profile_page", username=username)))
# Save user_profile # Save user_profile
db.session.commit() db.session.commit()
@ -96,3 +113,19 @@ def user_claim_page():
flash("Unknown claim type", "error") flash("Unknown claim type", "error")
return render_template("users/claim.html", username=username, key=randomString(32)) return render_template("users/claim.html", username=username, key=randomString(32))
@app.route("/users/verify/")
def verify_email_page():
token = request.args.get("token")
ver = UserEmailVerification.query.filter_by(token=token).first()
if ver is None:
flash("Unknown verification token!", "error")
else:
ver.user.email = ver.email
db.session.delete(ver)
db.session.commit()
if current_user.is_authenticated:
return redirect(url_for("user_profile_page", username=current_user.username))
else:
return redirect(url_for("home_page"))

@ -1,4 +1,6 @@
USER_APP_NAME="Content DB" USER_APP_NAME="Content DB"
SERVER_NAME="content.minetest.net"
BASE_URL="http://" + SERVER_NAME
SECRET_KEY="" SECRET_KEY=""
WTF_CSRF_SECRET_KEY="" WTF_CSRF_SECRET_KEY=""
@ -8,9 +10,17 @@ SQLALCHEMY_DATABASE_URI = "sqlite:///../db.sqlite"
GITHUB_CLIENT_ID = "" GITHUB_CLIENT_ID = ""
GITHUB_CLIENT_SECRET = "" GITHUB_CLIENT_SECRET = ""
BASE_URL="http://localhost:5000/" CELERY_BROKER_URL='redis://localhost:6379'
CELERY_RESULT_BACKEND='redis://localhost:6379'
UPLOAD_FOLDER="tmp" UPLOAD_FOLDER="tmp"
USER_ENABLE_REGISTER = False USER_ENABLE_REGISTER = False
USER_ENABLE_CHANGE_USERNAME = False USER_ENABLE_CHANGE_USERNAME = False
s
MAIL_USERNAME=""
MAIL_PASSWORD=""
MAIL_DEFAULT_SENDER=""
MAIL_SERVER=""
MAIL_PORT=587
MAIL_USE_TLS=True