diff --git a/templates/users/activate.html b/templates/users/activate.html new file mode 100644 index 0000000..f297ed6 --- /dev/null +++ b/templates/users/activate.html @@ -0,0 +1,3 @@ +Welcome! Thanks for signing up. Please follow this link to activate your account: +

{{ confirm_url }}

+Cheers! \ No newline at end of file diff --git a/users/email.py b/users/email.py new file mode 100644 index 0000000..3ddff0a --- /dev/null +++ b/users/email.py @@ -0,0 +1,13 @@ +from flask_mail import Message + +from app import app +from extentions import mail + + +def send_email(to, subject, template): + msg = Message( + subject=subject, + recipients=[to], + html=template + ) + mail.send(msg) diff --git a/users/token.py b/users/token.py new file mode 100644 index 0000000..6b15333 --- /dev/null +++ b/users/token.py @@ -0,0 +1,21 @@ +from itsdangerous import URLSafeTimedSerializer + +from app import app + + +def generate_confirmation_token(email): + serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) + return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT']) + + +def confirm_token(token, expiration=3600): + serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) + try: + email = serializer.loads( + token, + salt=app.config['SECURITY_PASSWORD_SALT'], + max_age=expiration + ) + except: + return False + return email \ No newline at end of file diff --git a/users/views.py b/users/views.py index 28a60d2..32261f7 100644 --- a/users/views.py +++ b/users/views.py @@ -1,38 +1,64 @@ import datetime -from flask import jsonify, request +from flask import jsonify, render_template, request, url_for from flask_jwt_extended import (create_access_token, create_refresh_token, get_jwt_identity, jwt_refresh_token_required, jwt_required) from extentions import db from users import users +from users.email import send_email from users.forms import ChangePasswordForm, LoginForm, RegisterForm from users.models import User +from users.token import confirm_token, generate_confirmation_token from utils.decorators import json_only @users.route('/signup', methods=['POST']) -def create_user(): +def register(): form = RegisterForm() if not form.validate_on_submit(): return {'errors': form.errors}, 400 new_user = User(form.full_name.data, form.email.data, form.password.data) db.session.add(new_user) db.session.commit() - return {'message': 'account created successfully'}, 201 + token = generate_confirmation_token(new_user.Email) + subject = "DailyDiet | Please confirm your email" + confirm_url = url_for('users.confirm_email', token=token, _external=True) + html = html = render_template('users/activate.html', confirm_url=confirm_url) + send_email(new_user.Email, subject, html) + return {'msg': 'Account created successfully!'}, 201 + + +@users.route('/confirm/', methods=['GET']) +def confirm_email(token): + try: + email = confirm_token(token) + except: + return {'error': 'The confirmation link is invalid or has expired.'}, 400 + user = User.query.filter_by(Email=email).first_or_404() + if user.Confirmed: + return {'msg': 'Account already confirmed. Please login.'} + else: + user.Confirmed = True + user.ConfirmedOn = datetime.datetime.now() + db.session.add(user) + db.session.commit() + return {'msg': 'You have confirmed your account. Thanks!'} @users.route('/signin', methods=['POST']) def login(): form = LoginForm() + if not form.validate_on_submit(): + return {'errors': form.errors}, 400 email = form.email.data password = form.password.data user = User.query.filter(User.Email.ilike(email)).first() if not user: - return {'error': 'email or password does not match'}, 403 + return {'error': 'Email/Password does not match.'}, 403 if not user.check_password(password): - return {'error': 'email or password does not match'}, 403 + return {'error': 'Email/Password does not match.'}, 403 access_token = create_access_token(identity=user.Email, fresh=True) refresh_token = create_refresh_token(identity=user.Email) return {'access_token': access_token, 'refresh_token': refresh_token}, 200 @@ -52,7 +78,7 @@ def change_password(): user = User.query.filter(User.Email.ilike(identity)).first() form = ChangePasswordForm() if not user.check_password(form.old_password.data): - return {'error': 'old password does not match'}, 403 + return {'error': 'Old password does not match.'}, 403 if not form.validate_on_submit(): return {'errors': form.errors}, 400 user.set_password(form.new_password.data)