From 99341f7d537ff93f77f6a1577d320f7b1f2b4989 Mon Sep 17 00:00:00 2001 From: yackob03 Date: Fri, 27 Sep 2013 19:29:01 -0400 Subject: [PATCH] Send a confirmation email when an account is created. Links don't do anything yet. --- app.py | 10 +++++++--- config.py | 15 +++++++++++++++ data/database.py | 8 ++++++++ data/model.py | 7 ++++++- endpoints/index.py | 7 +++++-- endpoints/web.py | 10 ++++++++++ requirements.txt | 3 ++- util/email.py | 20 ++++++++++++++++++++ 8 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 config.py create mode 100644 util/email.py diff --git a/app.py b/app.py index 3f72d3cd0..ea52822ee 100644 --- a/app.py +++ b/app.py @@ -3,15 +3,19 @@ import logging from flask import Flask from flask.ext.principal import Principal from flask.ext.login import LoginManager - +from flask.ext.mail import Mail +from config import ProductionConfig app = Flask(__name__) +app.config.from_object(ProductionConfig()) + logger = logging.getLogger(__name__) Principal(app, use_sessions=True) -app.secret_key = '1cb18882-6d12-440d-a4cc-b7430fb5f884' - login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'signin' + +mail = Mail() +mail.init_app(app) diff --git a/config.py b/config.py new file mode 100644 index 000000000..fdb96fdf9 --- /dev/null +++ b/config.py @@ -0,0 +1,15 @@ +class FlaskConfig(object): + SECRET_KEY = '1cb18882-6d12-440d-a4cc-b7430fb5f884' + +class MailConfig(object): + MAIL_SERVER = 'email-smtp.us-east-1.amazonaws.com' + MAIL_USE_TLS = True + MAIL_PORT = 587 + MAIL_USERNAME = 'AKIAIXV5SDGCPVMU3N4Q' + MAIL_PASSWORD = 'AhmX/vWE91uQ2RtcEKTkfNrzZehEjPNXOXeOXgQNfLao' + DEFAULT_MAIL_SENDER = 'support@fluxmonkey.io' + MAIL_FAIL_SILENTLY = False + TESTING = False + +class ProductionConfig(FlaskConfig, MailConfig): + pass \ No newline at end of file diff --git a/data/database.py b/data/database.py index c8c5aab5a..50ca6055a 100644 --- a/data/database.py +++ b/data/database.py @@ -66,6 +66,14 @@ class AccessToken(BaseModel): created = DateTimeField(default=datetime.now) +class EmailConfirmation(BaseModel): + code = CharField(default=random_string_generator()) + user = ForeignKeyField(User) + pw_reset = BooleanField(default=False) + email_confirm = BooleanField(default=False) + created = DateTimeField(default=datetime.now) + + class Image(BaseModel): # This class is intentionally denormalized. Even though images are supposed # to be globally unique we can't treat them as such for permissions and diff --git a/data/model.py b/data/model.py index ec9d85bb8..24f1279b7 100644 --- a/data/model.py +++ b/data/model.py @@ -28,9 +28,14 @@ def create_user(username, password, email): try: new_user = User.create(username=username, password_hash=pw_hash, email=email) + return new_user except Exception as ex: raise DataModelException(ex.message) - return new_user + + +def create_confirm_email_code(user): + code = EmailConfirmation(user=user, email_confirm=True) + return code def get_user(username): diff --git a/endpoints/index.py b/endpoints/index.py index 8a02c70bd..4fb52cb7f 100644 --- a/endpoints/index.py +++ b/endpoints/index.py @@ -11,6 +11,7 @@ from app import app from auth.auth import (process_auth, get_authenticated_user, get_validated_token) from util.names import parse_namespace_repository, parse_repository_name +from util.email import send_confirmation_email from auth.permissions import (ModifyRepositoryPermission, ReadRepositoryPermission, UserPermission) @@ -46,8 +47,10 @@ def generate_headers(f): @app.route('/v1/users/', methods=['POST']) def create_user(): user_data = request.get_json() - model.create_user(user_data['username'], user_data['password'], - user_data['email']) + new_user = model.create_user(user_data['username'], user_data['password'], + user_data['email']) + code = model.create_confirm_email_code(new_user) + send_confirmation_email(new_user.username, new_user.email, code.code) return make_response('Created', 201) diff --git a/endpoints/web.py b/endpoints/web.py index c7564bb1f..c2a549b5d 100644 --- a/endpoints/web.py +++ b/endpoints/web.py @@ -56,6 +56,16 @@ def signin(): abort(403) +@app.route('/confirm', methods=['GET']) +def confirm_email(): + pass + + +@app.route('/reset', methods=['GET']) +def password_reset(): + pass + + @app.route('/signin', methods=['GET']) def render_signin_page(): return send_file('templates/signin.html') diff --git a/requirements.txt b/requirements.txt index 9b80bab6d..560d03df5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ peewee flask py-bcrypt Flask-Principal -Flask-Login \ No newline at end of file +Flask-Login +Flask-Mail \ No newline at end of file diff --git a/util/email.py b/util/email.py new file mode 100644 index 000000000..2d7dc950e --- /dev/null +++ b/util/email.py @@ -0,0 +1,20 @@ +from flask.ext.mail import Message + +from app import mail, app + + +CONFIRM_MESSAGE = """ +This email address was recently used to register the username '%s' +at Quay.io.
+
+To confirm this email address, please click the following link:
+http://quay.io/confirm?token=%s +""" + + +def send_confirmation_email(username, email, token): + msg = Message('Welcome to Quay! Please confirm your email.', + sender='support@fluxmonkey.io', # Why do I need this? + recipients=[email]) + msg.html = CONFIRM_MESSAGE % (username, token, token) + mail.send(msg)