From 0954e5b9fbfccb3e823b873a1b0459b034c584a1 Mon Sep 17 00:00:00 2001 From: Sandro Lutz <code@temparus.ch> Date: Sun, 12 Jul 2020 19:42:45 +0200 Subject: [PATCH] Add login pages --- app/auth.py | 45 ++++++++++++ app/bouncer/views.py | 15 ++-- app/controller.py | 26 ------- app/controllers/__init__.py | 4 +- app/controllers/user.py | 68 +++++++++++++++++-- app/exceptions.py | 11 +++ app/login/auth.py | 22 ------ app/login/views.py | 51 +++++++++++--- app/models/user.py | 5 +- app/static/css/style.css | 45 ++++++++---- app/templates/base.html | 3 +- app/templates/bouncer/home.html | 15 ---- app/templates/bouncer/home_anonymous.html | 21 ++++++ app/templates/bouncer/home_authenticated.html | 19 ++++++ app/templates/email/confirm.txt | 2 +- app/templates/error/401.html | 2 +- app/templates/error/403.html | 2 +- app/templates/error/404.html | 2 +- app/templates/error/500.html | 2 +- app/templates/login/confirm_email.html | 26 +++++++ .../login/confirm_email_confirmed.html | 20 ++++++ app/templates/login/login.html | 12 +++- app/templates/login/logout.html | 5 +- app/templates/login/register.html | 16 ++++- app/templates/login/register_success.html | 20 ++++++ instance/config.dev.py | 4 +- requirements.in | 3 +- requirements.txt | 12 ++-- 28 files changed, 357 insertions(+), 121 deletions(-) create mode 100644 app/auth.py delete mode 100644 app/controller.py delete mode 100644 app/login/auth.py delete mode 100644 app/templates/bouncer/home.html create mode 100644 app/templates/bouncer/home_anonymous.html create mode 100644 app/templates/bouncer/home_authenticated.html create mode 100644 app/templates/login/confirm_email.html create mode 100644 app/templates/login/confirm_email_confirmed.html create mode 100644 app/templates/login/register_success.html diff --git a/app/auth.py b/app/auth.py new file mode 100644 index 0000000..813dfaa --- /dev/null +++ b/app/auth.py @@ -0,0 +1,45 @@ +import json +from functools import wraps +from flask import request, redirect, abort, session +from app import db +from .models import User + + +def login_required(f): + """ + Requires that the user is logged in. + + This is a wrapper for the @login_required decorator. + + Error 403: shwon if trying to access with an api key (Authorization header). + """ + @wraps(f) + def wrapped(*args, **kwargs): + if not is_authenticated(): + abort(403) + + return f(*args, **kwargs) + return wrapped + + +def is_authenticated(): + return 'userID' in session and User.query.get(session['userID']) is not None + + +def get_authenticated_user(): + return User.query.get(session['userID']) if 'userID' in session else None + + +def login(email, password): + user = User.query.filter(User.email == email).first() + if user.check_password(password): + session['userID'] = user._id + return True + return False + + +def logout(): + if is_authenticated(): + session['userID'] = None + return True + return False diff --git a/app/bouncer/views.py b/app/bouncer/views.py index c635504..8825957 100644 --- a/app/bouncer/views.py +++ b/app/bouncer/views.py @@ -1,12 +1,17 @@ from flask import flash, redirect, render_template, url_for, request, abort, make_response, session from . import bouncer_bp -from ..login.auth import login_required +from ..auth import login_required, is_authenticated, get_authenticated_user +from ..controllers import FreeSpotController, UserController, ReservationController, RecordController @bouncer_bp.route('/') def home(): - """ - Handle requests to the /logout route - """ - return make_response(render_template('bouncer/home.html', title='Home')) + free_spots = FreeSpotController.get_free_spots() + if is_authenticated(): + user = get_authenticated_user() + reservation = ReservationController.get_active_reservation(user) + record = RecordController.get_active_record(user) + + return make_response(render_template('bouncer/home_authenticated.html', title='Home', free_spots=free_spots, name=user.name, email=user.email)) + return make_response(render_template('bouncer/home_anonymous.html', title='Home', free_spots=free_spots)) diff --git a/app/controller.py b/app/controller.py deleted file mode 100644 index 0c48b65..0000000 --- a/app/controller.py +++ /dev/null @@ -1,26 +0,0 @@ -import threading -from app import models -from app import db -from .exceptions import ReservationExpiredError - -class Controller(): - lock = threading.Lock() - - @classmethod - def create_record_from_reservation(cls, reservation): - with cls.lock: - if reservation.is_valid: - record = models.Record() - report.user = reservation.user - report.organisation = organisation - report.product = product - - db.session.add(report) - db.session.commit() - else: - raise ReservationExpiredError - - @classmethod - def create_new_record(cls, user, ): - with cls.lock: - pass diff --git a/app/controllers/__init__.py b/app/controllers/__init__.py index d51b900..fb7217f 100644 --- a/app/controllers/__init__.py +++ b/app/controllers/__init__.py @@ -1,2 +1,4 @@ from .record import RecordController -from .free_spot import FreeSpotController \ No newline at end of file +from .free_spot import FreeSpotController +from .reservation import ReservationController +from .user import UserController diff --git a/app/controllers/user.py b/app/controllers/user.py index 3b532e2..2abbc9d 100644 --- a/app/controllers/user.py +++ b/app/controllers/user.py @@ -1,8 +1,9 @@ import threading -from datetime import datetime, timedelta +from datetime import datetime from sqlalchemy import DateTime, cast, func +from validate_email import validate_email from flask_mail import Message -from flask import current_app +from flask import current_app, render_template from app import db, mail from app.models import User from .lock import Lock @@ -10,13 +11,44 @@ from .reservation import ReservationController from .record import RecordController from ..exceptions import ActiveReservationExistsError, \ ActiveRecordExistsError, NoActiveRecordError, ReservationExpiredError, \ - NoFreeSpotError + NoFreeSpotError, UserRegistrationInvalidDataError class UserController(): + @staticmethod + def check_user_data(name, email, telegram_id=None, password=None, password2=None): + errors = [] + + if name is None or len(name) < 3: + errors.append('name') + if not validate_email(email_address=email, \ + check_regex=True, \ + check_mx=False, \ + use_blacklist=True, \ + debug=False) or \ + User.query.filter(User.email == email).count(): + errors.append('email') + if telegram_id is not None and User.query.filter(User.telegram_id == telegram_id): + errors.append('telegram_id') + if telegram_id is None and (password is None or len(password) == 0): + errors.append('password') + if password != password2: + errors.append('password2') + + if len(errors) > 0: + raise UserRegistrationInvalidDataError(errors) + + @classmethod def create(cls, name, email, telegram_id=None, telegram_chat_id=None, password=None): + cls.check_user_data( \ + name=name, \ + email=email, \ + telegram_id=telegram_id, \ + password=password, \ + password2=password) + user = User() user.name = name user.email = email @@ -30,7 +62,7 @@ class UserController(): db.session.add(user) db.session.commit() - cls.send_confirm_email(user) + cls._send_confirm_email(user) @staticmethod @@ -38,6 +70,25 @@ class UserController(): return User.query.get(user_id) + @staticmethod + def verify_confirm_email(email, token): + user = User.query.filter(User.email == email).first() + return user is not None and user.is_token_valid(token) + + + @staticmethod + def confirm_email(email, token): + user = User.query.filter(User.email == email).first() + + if user is None or not user.is_token_valid(token): + return False + + user.is_confirmed = True + user.generate_new_token() + db.session.commit() + return True + + @staticmethod def reserve(user): if ReservationController.has_active_reservation(user): @@ -74,13 +125,16 @@ class UserController(): def send_password_reset_email(user): user.generate_new_token() sesssion.commit() - msg = Message(render_template('email.password_reset', user=user), + msg = Message(render_template('email/password_reset.txt', user=user), recipients=[user.email]) mail.send(msg) @staticmethod def _send_confirm_email(user): - msg = Message(render_template('email.confirm', user=user), - recipients=[user.email]) + msg = Message( + subject='[Bastli Bouncer] Confirm Email Address', + body=render_template('email/confirm.txt', user=user), + recipients=[user.email]) + print(msg) mail.send(msg) diff --git a/app/exceptions.py b/app/exceptions.py index 98fbad5..40cf14d 100644 --- a/app/exceptions.py +++ b/app/exceptions.py @@ -34,3 +34,14 @@ class NoActiveRecordError(Error): class UserNotConfirmedError(Error): """Raised when user has no confirmed email address""" pass + + +class UserRegistrationInvalidDataError(Error): + """Raised when registration data is invalid""" + + def __init__(self, errors): + super(UserRegistrationInvalidDataError, self).__init__() + self.errors = errors + + def get_errors(self): + return self.errors diff --git a/app/login/auth.py b/app/login/auth.py deleted file mode 100644 index 2e07dd2..0000000 --- a/app/login/auth.py +++ /dev/null @@ -1,22 +0,0 @@ -import json -from functools import wraps -from flask import request, redirect, abort, session -from app import db -from ..models import User - - -def login_required(f): - """ - Requires that the user is logged in. - - This is a wrapper for the @login_required decorator. - - Error 403: shwon if trying to access with an api key (Authorization header). - """ - @wraps(f) - def wrapped(*args, **kwargs): - if session['userID'] is None or User.query.get(session['userID']) is None: - abort(403) - - return f(*args, **kwargs) - return wrapped diff --git a/app/login/views.py b/app/login/views.py index bbfc189..a6aeec8 100644 --- a/app/login/views.py +++ b/app/login/views.py @@ -1,21 +1,54 @@ from flask import flash, redirect, render_template, url_for, request, abort, make_response +from ..controllers.user import UserController +from ..exceptions import UserRegistrationInvalidDataError +from ..auth import login as auth_login, logout as auth_logout from . import login_bp @login_bp.route('/register', methods=['GET', 'POST']) def register(): - # TODO - return make_response(render_template('login/register.html', title='Register')) + errors = None + if request.method == 'POST': + try: + UserController.check_user_data( \ + name=request.form['name'], \ + email=request.form['email'], \ + password=request.form['password'], \ + password2=request.form['password2']) + UserController.create( \ + name=request.form['name'], \ + email=request.form['email'], \ + password=request.form['password']) + + return make_response(render_template('login/register_success.html', title='Registration')) + + except UserRegistrationInvalidDataError as e: + errors = e.errors + flash('Some fields have errors: ' + ', '.join(errors), 'error') + return make_response(render_template('login/register.html', title='Registration', errors=errors)) + + +@login_bp.route('/confirm_email') +def confirm_email(): + email = request.args.get('email') + token = request.args.get('token') + action = request.args.get('action') + if action == 'confirm': + if not UserController.confirm_email(email, token): + abort(404) + return make_response(render_template('login/confirm_email_confirmed.html', title='Email Address confirmed')) + elif not UserController.verify_confirm_email(email, token): + abort(404) + + return make_response(render_template('login/confirm_email.html', title='Confirm Email Address', email=email, token=token)) @login_bp.route('/login', methods=['GET', 'POST']) def login(): - error = None if request.method == 'POST': - if request.form['username'] != 'admin' or request.form['password'] != 'admin': - error = 'Invalid Credentials. Please try again.' - else: - return redirect(url_for('home')) + if auth_login(request.form['email'], request.form['password']): + return redirect(url_for('bouncer.home')) + flash('Invalid Credentials. Please try again.', 'error') return make_response(render_template('login/login.html', title='Login')) @@ -26,8 +59,6 @@ def password_reset(token, email): @login_bp.route('/logout') def logout(): - session['userID'] = None - - if authenticated: + if auth_logout(): return make_response(render_template('login/logout.html', title='Logout')) return redirect(url_for('bouncer.home')) diff --git a/app/models/user.py b/app/models/user.py index cbbcf11..df9db67 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,6 +1,6 @@ import secrets from flask import current_app -from datetime import datetime +from datetime import datetime, timedelta from werkzeug.security import generate_password_hash, check_password_hash from app import db @@ -21,7 +21,8 @@ class User(db.Model): token = db.Column(db.String(32), nullable=False) token_expiration = db.Column(db.DateTime, nullable=False, server_default=db.func.now()) created = db.Column(db.DateTime, nullable=False, server_default=db.func.now()) - + reservations = db.relationship("Reservation", back_populates="user") + records = db.relationship("Record", back_populates="user") @property def is_telegram_user(self): diff --git a/app/static/css/style.css b/app/static/css/style.css index 547e582..d6c7acd 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -15,28 +15,25 @@ h2, h3 { color: #1f2d54; } -.navbar-default { - background-color: #1f2d54; + +a { + color: #1f2d54; } -a, -.navbar-default .navbar-brand, -.navbar-default .navbar-nav > li > a { +a:hover { color: #e8462b; + text-decoration: none; } -a:hover, -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-nav > li > a:hover { - color: #ffffff; -} -a.table_link:hover { - color: #1f2d54; -} + footer { padding-top: 30px; padding-right: 0; padding-left: 0; padding-bottom: 20px; - background-color: #1f2d54; +} +footer > .container { + border-top: 4px solid #1f2d54; + text-align: center; + padding: 2em 0 0; } p.copyright { margin: 15px 0 0; @@ -64,6 +61,18 @@ p.copyright { border-color: #e8462b; color: #e8462b; } +.btn-primary { + background-color: #1f2d54; + color: #fff; +} +.btn-primary:hover { + background-color: #ffffff; + border-color: #e8462b; + color: #e8462b; + /* border-color: #1f2d54; + background-color: #fff; + color: #1f2d54; */ +} .center { margin: auto; width: 70%; @@ -74,6 +83,14 @@ p.copyright { width: 40%; padding: 10px; } +.error { + text-align: center; +} +.free_spots { + font-size: 1.2em; + font-weight: bold; + text-decoration: underline; +} .content-section { padding: 50px 0; border-top: 1px solid #e7e7e7; diff --git a/app/templates/base.html b/app/templates/base.html index ac7074d..21ad9ee 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -24,7 +24,8 @@ <div class="row"> <div class="col-lg-12"> <ul class="list-inline"> - <li><a target="_blank" href="https://bastli.ethz.ch"><img src="{{ url_for('static', filename='img/bastli_logo.png') }}" style="width: 150px;"></a></li> + <!-- <li><a target="_blank" href="https://bastli.ethz.ch"><img src="{{ url_for('static', filename='img/bastli_logo.png') }}" style="width: 150px;"></a></li> --> + <li>© <a target="_blank" href="https://bastli.ethz.ch">AMIV Bastli</a></li> <li><a href="mailto:it@bastli.ethz.ch">Contact</a></li> <li><a target="_blank" href="https://gitlab.ethz.ch/bastli/bastli-bouncer">Source Code & Issue Tracker</a></li> </ul> diff --git a/app/templates/bouncer/home.html b/app/templates/bouncer/home.html deleted file mode 100644 index a25a87f..0000000 --- a/app/templates/bouncer/home.html +++ /dev/null @@ -1,15 +0,0 @@ -{% import "bootstrap/utils.html" as utils %} -{% extends "base.html" %} -{% block body %} -<div class="content-section"> - <br/> - {{ utils.flashed_messages() }} - <br/> - <div class="center-narrow"> - <h1>Bastli Bouncer</h1> - <br/> - <p>You have landed on the home page!</p> - <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> - </div> -</div> -{% endblock %} diff --git a/app/templates/bouncer/home_anonymous.html b/app/templates/bouncer/home_anonymous.html new file mode 100644 index 0000000..f6bc104 --- /dev/null +++ b/app/templates/bouncer/home_anonymous.html @@ -0,0 +1,21 @@ +{% import "bootstrap/utils.html" as utils %} +{% extends "base.html" %} +{% block body %} +<div class="content-section"> + <br/> + {{ utils.flashed_messages() }} + <br/> + <div class="center-narrow"> + <h1>Bastli Bouncer</h1> + <br/> + <p>We currently have <span class="free_spots">{{ free_spots }} free spots</span> in our workshop.</p> + <br/> + <p>Register and reserve your spot here before you come by! Thank you.</p> + <br/> + <p> + <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> + <a class="btn btn-default" role="button" href="{{ url_for('login.register') }}">Register</a> + </p> + </div> +</div> +{% endblock %} diff --git a/app/templates/bouncer/home_authenticated.html b/app/templates/bouncer/home_authenticated.html new file mode 100644 index 0000000..a4dd68c --- /dev/null +++ b/app/templates/bouncer/home_authenticated.html @@ -0,0 +1,19 @@ +{% import "bootstrap/utils.html" as utils %} +{% extends "base.html" %} +{% block body %} +<div class="content-section"> + <br/> + {{ utils.flashed_messages() }} + <br/> + <div class="center-narrow"> + <h1>Bastli Bouncer</h1> + <br/> + <p>We currently have <span class="free_spots">{{ free_spots }} free spots</span> in our workshop.</p> + <p> + Welcome, {{ name }} ({{ email }}). + <a class="btn btn-default" role="button" href="{{ url_for('login.logout') }}">Logout</a> + </p> + <br/> + </div> +</div> +{% endblock %} diff --git a/app/templates/email/confirm.txt b/app/templates/email/confirm.txt index 5d2666e..ae6ca5c 100644 --- a/app/templates/email/confirm.txt +++ b/app/templates/email/confirm.txt @@ -2,6 +2,6 @@ Hi {{ user.name }} Please visit the link below to confirm your email address. -{{ url_for('login.email_confirm', token=user.token, email=user.email, _external=True) }} +{{ url_for('login.confirm_email', token=user.token, email=user.email, _external=True) }} The link will expire after some time. diff --git a/app/templates/error/401.html b/app/templates/error/401.html index 998e51f..20e95dc 100644 --- a/app/templates/error/401.html +++ b/app/templates/error/401.html @@ -5,7 +5,7 @@ <br/> {{ utils.flashed_messages() }} <br/> - <div class="center-narrow"> + <div class="center-narrow error"> <h1>Oops, that's an error!</h1> <br/> <p>Error 401: Authorization header missing! An API key is required to access this resource.</p> diff --git a/app/templates/error/403.html b/app/templates/error/403.html index 5b822c8..89f6901 100644 --- a/app/templates/error/403.html +++ b/app/templates/error/403.html @@ -5,7 +5,7 @@ <br/> {{ utils.flashed_messages() }} <br/> - <div class="center-narrow"> + <div class="center-narrow error"> <h1>Oops, that's an error!</h1> <br/> <p>Error 403: You are not authorized to access this page.</p> diff --git a/app/templates/error/404.html b/app/templates/error/404.html index 56b2225..c758893 100644 --- a/app/templates/error/404.html +++ b/app/templates/error/404.html @@ -5,7 +5,7 @@ <br/> {{ utils.flashed_messages() }} <br/> - <div class="center-narrow"> + <div class="center-narrow error"> <h1>Oops, that's a error!</h1> <br/> <p>Error 404: The page you are looking for could not be found.</p> diff --git a/app/templates/error/500.html b/app/templates/error/500.html index 2ad0caf..b308c49 100644 --- a/app/templates/error/500.html +++ b/app/templates/error/500.html @@ -5,7 +5,7 @@ <br/> {{ utils.flashed_messages() }} <br/> - <div class="center-narrow"> + <div class="center-narrow error"> <h1>Oops, that's an error!</h1> <br/> <p> diff --git a/app/templates/login/confirm_email.html b/app/templates/login/confirm_email.html new file mode 100644 index 0000000..0e2604d --- /dev/null +++ b/app/templates/login/confirm_email.html @@ -0,0 +1,26 @@ +{% import "bootstrap/utils.html" as utils %} +{% extends "base.html" %} +{% block body %} +<div class="content-section"> + <br/> + {{ utils.flashed_messages() }} + <br/> + <div class="center-narrow"> + <h1>Bastli Bouncer</h1> + <br/> + <h2>Confirm Email Address</h2> + <br> + <p>Do you want to confirm your email address {{ email }}?</p> + <p> + <form action="" method="get"> + <input type="hidden" name="email" value="{{ email }}"> + <input type="hidden" name="token" value="{{ token }}"> + <input type="hidden" name="action" value="confirm"> + <input class="btn btn-primary" type="submit" value="Confirm"> + <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Abort</a> + </form> + + </p> + </div> +</div> +{% endblock %} diff --git a/app/templates/login/confirm_email_confirmed.html b/app/templates/login/confirm_email_confirmed.html new file mode 100644 index 0000000..afd2099 --- /dev/null +++ b/app/templates/login/confirm_email_confirmed.html @@ -0,0 +1,20 @@ +{% import "bootstrap/utils.html" as utils %} +{% extends "base.html" %} +{% block body %} +<div class="content-section"> + <br/> + {{ utils.flashed_messages() }} + <br/> + <div class="center-narrow"> + <h1>Bastli Bouncer</h1> + <br/> + <h2>Email address confirmed!</h2> + <br> + <p>Your email address is now confirmed. Sign in to use your new account.</p> + <p> + <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> + <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Home</a> + </p> + </div> +</div> +{% endblock %} diff --git a/app/templates/login/login.html b/app/templates/login/login.html index 6428ad5..025203c 100644 --- a/app/templates/login/login.html +++ b/app/templates/login/login.html @@ -8,8 +8,16 @@ <div class="center-narrow"> <h1>Bastli Bouncer</h1> <br/> - <p>You have successfully been logged out.</p> - <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Login</a> + <h2>Log in to your personal Account</h2> + <br> + <form action="" method="post"> + <label for="email">Email:</label><br> + <input type="email" id="email" name="email" value="{{ request.form.email }}"><br/> + <label for="name">Password:</label><br> + <input type="password" id="password" name="password" value="{{ request.form.password }}"><br/> + <input class="btn btn-primary" type="submit" value="Login"> or + <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Go back</a> + </form> </div> </div> {% endblock %} diff --git a/app/templates/login/logout.html b/app/templates/login/logout.html index 6428ad5..df5cc37 100644 --- a/app/templates/login/logout.html +++ b/app/templates/login/logout.html @@ -9,7 +9,10 @@ <h1>Bastli Bouncer</h1> <br/> <p>You have successfully been logged out.</p> - <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Login</a> + <br/> + <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> + or + <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Home</a> </div> </div> {% endblock %} diff --git a/app/templates/login/register.html b/app/templates/login/register.html index f0cecfc..ac13ea7 100644 --- a/app/templates/login/register.html +++ b/app/templates/login/register.html @@ -8,8 +8,20 @@ <div class="center-narrow"> <h1>Bastli Bouncer</h1> <br/> - <p>Here you should see a registration form in the near future.</p> - <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Login</a> + <h2>Register a new Account</h2> + <br> + <form action="" method="post"> + <label for="name">Name:</label><br> + <input type="text" id="name" name="name" value="{{ request.form.name }}"><br/> + <label for="email">Email:</label><br> + <input type="text" id="email" name="email" value="{{ request.form.email }}"><br/> + <label for="name">Password:</label><br> + <input type="password" id="password" name="password" value="{{ request.form.password }}"><br/> + <label for="name">Repeat Password:</label><br> + <input type="password" id="password2" name="password2" value="{{ request.form.password2 }}"><br/> + <input class="btn btn-primary" type="submit" value="Register"> or + <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Go back</a> + </form> </div> </div> {% endblock %} diff --git a/app/templates/login/register_success.html b/app/templates/login/register_success.html new file mode 100644 index 0000000..d4fe5bb --- /dev/null +++ b/app/templates/login/register_success.html @@ -0,0 +1,20 @@ +{% import "bootstrap/utils.html" as utils %} +{% extends "base.html" %} +{% block body %} +<div class="content-section"> + <br/> + {{ utils.flashed_messages() }} + <br/> + <div class="center-narrow"> + <h1>Bastli Bouncer</h1> + <br/> + <h2>Registration successful!</h2> + <br> + <p>Please check the inbox of your email address and follow the insttructions.</p> + <p> + <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> + <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Home</a> + </p> + </div> +</div> +{% endblock %} diff --git a/instance/config.dev.py b/instance/config.dev.py index 37f78c5..ab0915c 100644 --- a/instance/config.dev.py +++ b/instance/config.dev.py @@ -9,11 +9,11 @@ USER_TOKEN_TIMEOUT = timedelta(hours=12) SERVER_NAME = 'localhost:5000' MAIL_SERVER = 'smtp' -MAIL_PORT = 1025 +MAIL_PORT = 1030 MAIL_USE_TLS = False MAIL_USERNAME = None MAIL_PASSWORD = None -MAIL_DEFAULT_SENDER = 'Bastli Bouncer <noreply@bastli.ethz.ch>' +MAIL_DEFAULT_SENDER = ('Bastli Bouncer', 'noreply@bastli.ethz.ch') DEBUG = True diff --git a/requirements.in b/requirements.in index 15d253c..31cc083 100644 --- a/requirements.in +++ b/requirements.in @@ -5,9 +5,10 @@ flask flask-bootstrap flask-migrate flask-sqlalchemy -flask-wtf Flask-Mail +py3-validate-email + # database connector pymysql diff --git a/requirements.txt b/requirements.txt index 97af918..4393a99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,17 +7,20 @@ alembic==1.4.2 # via flask-migrate blinker==1.4 # via flask-mail click==7.1.2 # via flask +dnspython==1.16.0 # via py3-validate-email dominate==2.5.1 # via flask-bootstrap +filelock==3.0.12 # via py3-validate-email flask-bootstrap==3.3.7.1 # via -r requirements.in flask-mail==0.9.1 # via -r requirements.in flask-migrate==2.5.3 # via -r requirements.in flask-sqlalchemy==2.4.3 # via -r requirements.in, flask-migrate -flask-wtf==0.14.3 # via -r requirements.in -flask==1.1.2 # via -r requirements.in, flask-bootstrap, flask-mail, flask-migrate, flask-sqlalchemy, flask-wtf -itsdangerous==1.1.0 # via flask, flask-wtf +flask==1.1.2 # via -r requirements.in, flask-bootstrap, flask-mail, flask-migrate, flask-sqlalchemy +idna==2.10 # via py3-validate-email +itsdangerous==1.1.0 # via flask jinja2==2.11.2 # via flask mako==1.1.3 # via alembic -markupsafe==1.1.1 # via jinja2, mako, wtforms +markupsafe==1.1.1 # via jinja2, mako +py3-validate-email==0.2.9 # via -r requirements.in pymysql==0.9.3 # via -r requirements.in python-dateutil==2.8.1 # via alembic python-editor==1.0.4 # via alembic @@ -25,4 +28,3 @@ six==1.15.0 # via python-dateutil sqlalchemy==1.3.18 # via alembic, flask-sqlalchemy visitor==0.1.3 # via flask-bootstrap werkzeug==1.0.1 # via flask -wtforms==2.3.1 # via flask-wtf -- GitLab