diff --git a/hardwarecheckout/config.py b/hardwarecheckout/config.py index ff8a679..1f19d57 100644 --- a/hardwarecheckout/config.py +++ b/hardwarecheckout/config.py @@ -12,6 +12,12 @@ # URL of Quill instance (for auth integration) QUILL_URL = os.environ['QUILL'] +# Required for email verification +DOMAIN = os.environ['EMAIL_DOMAIN'] +SMTP_HOST = os.environ['SMTP_HOST'] +SMTP_USER = os.environ['SMTP_USER'] +SMTP_PASSWORD = os.environ['SMTP_PASSWORD'] + # Random Secret for JWTs - must match Quill secret SECRET = os.environ['SECRET'] diff --git a/hardwarecheckout/controllers/login.py b/hardwarecheckout/controllers/login.py index 51e22a9..27626ad 100644 --- a/hardwarecheckout/controllers/login.py +++ b/hardwarecheckout/controllers/login.py @@ -1,18 +1,65 @@ from hardwarecheckout import app from hardwarecheckout import config from hardwarecheckout.models.user import * -from hardwarecheckout.utils import verify_token +from hardwarecheckout.utils import verify_token, gen_uuid, send_verification_email, gen_token import requests import datetime import json +import uuid from urlparse import urljoin from hardwarecheckout.forms.login_form import LoginForm +from hardwarecheckout.forms.register_form import RegisterForm from flask import ( redirect, render_template, request, url_for ) +from werkzeug.security import generate_password_hash, \ + check_password_hash + +@app.route('/verify') +def verify_page(): + if request.args.get('token'): + user = User.query.filter_by(verification_token=request.args.get('token')).first() + if user: + user.verified_email = True + db.session.commit() + response = app.make_response(redirect('/login?v=1')) + return response + + return "Token not found", 400 + +@app.route('/register') +def register_page(): + # Check if already logged in + if 'jwt' in request.cookies: + try: + decode_token(request.cookies['jwt']) + return redirect('/inventory') + except Exception as e: + pass + + return render_template('pages/register.html') + +@app.route('/register', methods=['POST']) +def register_handler(): + form = RegisterForm(request.form) + if form.validate(): + if User.query.filter_by(email=request.form['email']).first(): + return render_template('pages/register.html', error=["Email address already in use"]) + verification_token = uuid.uuid4().hex + user = User(gen_uuid(), request.form['email'], generate_password_hash(request.form['password']), verification_token, False) + db.session.add(user) + db.session.commit() + send_verification_email(request.form['email'], verification_token) + response = app.make_response(redirect('/login?r=1')) + return response + errors = [] + for field, error in form.errors.items(): + errors.append(field + ": " + "\n".join(error) + "\n") + + return render_template('pages/register.html', error=errors) @app.route('/login') def login_page(): @@ -23,35 +70,30 @@ def login_page(): return redirect('/inventory') except Exception as e: pass - - return render_template('pages/login.html') + + success = None + if request.args.get('r'): + success = ["Account created! Check your email to verify your account."] + elif request.args.get('v'): + success = ["Account verified! Login to continue."] + + return render_template('pages/login.html', success=success) @app.route('/login', methods=['POST']) def login_handler(): """Log user in""" form = LoginForm(request.form) if form.validate(): - url = urljoin(config.QUILL_URL, '/auth/login') - r = requests.post(url, data={'email':request.form['email'], 'password':request.form['password']}) - try: - r = json.loads(r.text) - except ValueError as e: - return render_template('pages/login.html', error=[str(e)]) - - if 'message' in r: - return render_template('pages/login.html', error=[r['message']]) + user = User.query.filter_by(email=request.form['email']).first() - quill_id = verify_token(r['token']) - if not quill_id: - return render_template('pages/login.html', error=['Invalid token returned by registration']) - - if User.query.filter_by(quill_id=quill_id).count() == 0: - user = User(quill_id, request.form['email'], r['user']['admin']) - db.session.add(user) - db.session.commit() + if not user or not check_password_hash(user.password_hash, request.form['password']): + return render_template('pages/login.html', error=["Invalid username or password"]) + + if not user.verified_email: + return render_template('pages/login.html', error=["Please verify your email to login"]) response = app.make_response(redirect('/inventory')) - response.set_cookie('jwt', r['token']) + response.set_cookie('jwt', gen_token(user.quill_id)) return response errors = [] diff --git a/hardwarecheckout/forms/register_form.py b/hardwarecheckout/forms/register_form.py new file mode 100644 index 0000000..e2310d9 --- /dev/null +++ b/hardwarecheckout/forms/register_form.py @@ -0,0 +1,6 @@ +from wtforms import Form, StringField, PasswordField, FileField, validators + +class RegisterForm(Form): + email = StringField('email_address', [validators.input_required(), validators.email()]) + password = PasswordField('password', [validators.input_required(), validators.length(min=6), validators.equal_to('confirm', message='Passwords must match')]) + confirm = PasswordField('confirm', [validators.input_required(), validators.length(min=6)]) \ No newline at end of file diff --git a/hardwarecheckout/models/user.py b/hardwarecheckout/models/user.py index fb8bcb8..b2c5f7e 100644 --- a/hardwarecheckout/models/user.py +++ b/hardwarecheckout/models/user.py @@ -3,11 +3,14 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True) quill_id = db.Column(db.String(), unique=True) + verified_email = db.Column(db.Boolean) + verification_token = db.Column(db.String(), unique=True) is_admin = db.Column(db.Boolean) location = db.Column(db.String(120)) name = db.Column(db.String()) phone = db.Column(db.String(255)) email = db.Column(db.String()) + password_hash = db.Column(db.String()) notifications = db.Column(db.Boolean) have_their_id = db.Column(db.Boolean) requests = db.relationship('Request', back_populates='user') @@ -15,15 +18,18 @@ class User(db.Model): items = db.relationship('Item', backref='user') - def __init__(self, quill_id, email, is_admin): + def __init__(self, quill_id, email, password_hash, verification_token, is_admin): self.quill_id = quill_id self.email = email + self.password_hash = password_hash self.is_admin = is_admin self.name = '' self.location = '' self.phone = '' self.notifications = False self.have_their_id = False + self.verified_email = False + self.verification_token = verification_token def requires_id(self): for item in self.items: diff --git a/hardwarecheckout/static/css/app.css b/hardwarecheckout/static/css/app.css index 98e1bbf..cceaed2 100644 --- a/hardwarecheckout/static/css/app.css +++ b/hardwarecheckout/static/css/app.css @@ -6,6 +6,14 @@ text-align: left; vertical-align: middle; } +#register-wrapper { + max-width: 450px; + margin-top: 35px; } + +#register-form { + text-align: left; + vertical-align: middle; } + .footer { color: #777; padding-bottom: 1em; diff --git a/hardwarecheckout/templates/includes/nav.html b/hardwarecheckout/templates/includes/nav.html index 8abb484..1512160 100644 --- a/hardwarecheckout/templates/includes/nav.html +++ b/hardwarecheckout/templates/includes/nav.html @@ -11,6 +11,7 @@ {{ user.email }} Logout {% else %} + Register Login {% endif %} diff --git a/hardwarecheckout/templates/pages/login.html b/hardwarecheckout/templates/pages/login.html index f3bc072..08551e4 100644 --- a/hardwarecheckout/templates/pages/login.html +++ b/hardwarecheckout/templates/pages/login.html @@ -16,6 +16,7 @@