Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions hardwarecheckout/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down
84 changes: 63 additions & 21 deletions hardwarecheckout/controllers/login.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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 = []
Expand Down
6 changes: 6 additions & 0 deletions hardwarecheckout/forms/register_form.py
Original file line number Diff line number Diff line change
@@ -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)])
8 changes: 7 additions & 1 deletion hardwarecheckout/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,33 @@
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')
sockets = db.relationship('Socket', back_populates='user')

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:
Expand Down
8 changes: 8 additions & 0 deletions hardwarecheckout/static/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions hardwarecheckout/templates/includes/nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<a class="ui item" href="{{ url_for('get_user') }}"><i class="user icon"></i>{{ user.email }}</a>
<a class="ui item" href="{{ url_for('logout') }}"><i class="sign out icon"></i>Logout</a>
{% else %}
<a class="ui item" href="{{ url_for('register_page') }}"><i class="user icon"></i>Register</a>
<a class="ui item" href="{{ url_for('login_page') }}"><i class="sign in icon"></i>Login</a>
{% endif %}
</div>
Expand Down
8 changes: 8 additions & 0 deletions hardwarecheckout/templates/pages/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ <h2 class="ui header"> {{ config["HACKATHON_NAME"] }} Hardware </h2>
<input type="password" name="password" placeholder="**********">
</div>
<input class="ui fluid primary submit button" type="submit" value="Login">
<a class="ui fluid secondary submit button" href="{{ url_for('register_page') }}">Register</a>
</form>
{% if error %}
<div class="ui error message">Login Errors:
Expand All @@ -24,6 +25,13 @@ <h2 class="ui header"> {{ config["HACKATHON_NAME"] }} Hardware </h2>
{% endfor %}
</div>
{% endif %}
{% if success %}
<div class="ui success message">
{% for item in success %}
<div>{{item}}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
Expand Down
32 changes: 32 additions & 0 deletions hardwarecheckout/templates/pages/register.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% extends "includes/wrapper.html" %}
{% block title %} Register {% endblock %}

{% block content %}
<div class="ui middle aligned center aligned grid">
<div id="register-wrapper" class="ui segment column">
<h2 class="ui header"> {{ config["HACKATHON_NAME"] }} Hardware </h2>
<form id="register-form" class="ui form" action='/register' method='POST'>
<div class="field">
<label>Email</label>
<input type="text" name="email" placeholder="[email protected]" autofocus>
</div>
<div class="field">
<label>Password</label>
<input type="password" name="password" placeholder="**********">
</div>
<div class="field">
<label>Confirm Password</label>
<input type="password" name="confirm" placeholder="**********">
</div>
<input class="ui fluid primary submit button" type="submit" value="Register">
</form>
{% if error %}
<div class="ui error message">Registration Errors:
{% for item in error %}
<div>{{item}}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
20 changes: 20 additions & 0 deletions hardwarecheckout/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@
from jose import jws
from jose.exceptions import JWSError
import os
import time
from hardwarecheckout.config import *
from hardwarecheckout.constants import *
from hardwarecheckout.models.user import *
from flask import (redirect, request, jsonify, url_for)
from functools import wraps
from datetime import datetime
from babel import dates
import yagmail

def gen_uuid():
return str(uuid.uuid4()).replace('-', '')

def gen_token(id, duration=604800):
try:
expiry = int(time.time()) + duration
return jws.sign(id, SECRET)
except JWSError:
return None

def verify_token(token):
try:
return jws.verify(token, SECRET, algorithms=['HS256'])
Expand All @@ -28,6 +37,17 @@ def safe_redirect(endpoint, request):
), 401
return redirect(url_for(endpoint))

def send_verification_email(to, token):
yag = yagmail.SMTP(user=SMTP_USER, password=SMTP_PASSWORD, host=SMTP_HOST)
subject = "[%s] Verify your email" % (HACKATHON_NAME)
content = """
Thanks for registering for Hardware Checkout at %s!

Click <a href="%s">here</a> to verify your email.

The %s Team""" % (HACKATHON_NAME, DOMAIN + "/verify?token=" + token, HACKATHON_NAME)
yag.send(to, subject, content)

def requires_auth():
def decorator(f):
@wraps(f)
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ wrapt==1.10.11
WTForms==2.0
WTForms-Alchemy==0.15.0
WTForms-Components==0.10.3
yagmail==0.11.214