Codebase Update
This commit is contained in:
12
apps/authentication/__init__.py
Normal file
12
apps/authentication/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
blueprint = Blueprint(
|
||||
'authentication_blueprint',
|
||||
__name__,
|
||||
url_prefix=''
|
||||
)
|
||||
31
apps/authentication/forms.py
Normal file
31
apps/authentication/forms.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField
|
||||
from wtforms.validators import Email, DataRequired
|
||||
|
||||
# login and registration
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = StringField('Username',
|
||||
id='username_login',
|
||||
validators=[DataRequired()])
|
||||
password = PasswordField('Password',
|
||||
id='pwd_login',
|
||||
validators=[DataRequired()])
|
||||
|
||||
|
||||
class CreateAccountForm(FlaskForm):
|
||||
username = StringField('Username',
|
||||
id='username_create',
|
||||
validators=[DataRequired()])
|
||||
email = StringField('Email',
|
||||
id='email_create',
|
||||
validators=[DataRequired(), Email()])
|
||||
password = PasswordField('Password',
|
||||
id='pwd_create',
|
||||
validators=[DataRequired()])
|
||||
57
apps/authentication/models.py
Normal file
57
apps/authentication/models.py
Normal file
@ -0,0 +1,57 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask_login import UserMixin
|
||||
|
||||
from sqlalchemy.orm import relationship
|
||||
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin
|
||||
|
||||
from apps import db, login_manager
|
||||
|
||||
from apps.authentication.util import hash_pass
|
||||
|
||||
class Users(db.Model, UserMixin):
|
||||
|
||||
__tablename__ = 'Users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), unique=True)
|
||||
email = db.Column(db.String(64), unique=True)
|
||||
password = db.Column(db.LargeBinary)
|
||||
|
||||
oauth_github = db.Column(db.String(100), nullable=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for property, value in kwargs.items():
|
||||
# depending on whether value is an iterable or not, we must
|
||||
# unpack it's value (when **kwargs is request.form, some values
|
||||
# will be a 1-element list)
|
||||
if hasattr(value, '__iter__') and not isinstance(value, str):
|
||||
# the ,= unpack of a singleton fails PEP8 (travis flake8 test)
|
||||
value = value[0]
|
||||
|
||||
if property == 'password':
|
||||
value = hash_pass(value) # we need bytes here (not plain str)
|
||||
|
||||
setattr(self, property, value)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.username)
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def user_loader(id):
|
||||
return Users.query.filter_by(id=id).first()
|
||||
|
||||
|
||||
@login_manager.request_loader
|
||||
def request_loader(request):
|
||||
username = request.form.get('username')
|
||||
user = Users.query.filter_by(username=username).first()
|
||||
return user if user else None
|
||||
|
||||
class OAuth(OAuthConsumerMixin, db.Model):
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("Users.id", ondelete="cascade"), nullable=False)
|
||||
user = db.relationship(Users)
|
||||
58
apps/authentication/oauth.py
Normal file
58
apps/authentication/oauth.py
Normal file
@ -0,0 +1,58 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
import os
|
||||
from flask import current_app as app
|
||||
from flask_login import current_user, login_user
|
||||
from flask_dance.consumer import oauth_authorized
|
||||
from flask_dance.contrib.github import github, make_github_blueprint
|
||||
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
|
||||
from flask_dance.contrib.twitter import twitter, make_twitter_blueprint
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
from apps.config import Config
|
||||
from .models import Users, db, OAuth
|
||||
from flask import redirect, url_for
|
||||
from flask import flash
|
||||
|
||||
github_blueprint = make_github_blueprint(
|
||||
client_id=Config.GITHUB_ID,
|
||||
client_secret=Config.GITHUB_SECRET,
|
||||
scope = 'user',
|
||||
storage=SQLAlchemyStorage(
|
||||
OAuth,
|
||||
db.session,
|
||||
user=current_user,
|
||||
user_required=False,
|
||||
),
|
||||
)
|
||||
|
||||
@oauth_authorized.connect_via(github_blueprint)
|
||||
def github_logged_in(blueprint, token):
|
||||
info = github.get("/user")
|
||||
|
||||
if info.ok:
|
||||
|
||||
account_info = info.json()
|
||||
username = account_info["login"]
|
||||
|
||||
query = Users.query.filter_by(oauth_github=username)
|
||||
try:
|
||||
|
||||
user = query.one()
|
||||
login_user(user)
|
||||
|
||||
except NoResultFound:
|
||||
|
||||
# Save to db
|
||||
user = Users()
|
||||
user.username = '(gh)' + username
|
||||
user.oauth_github = username
|
||||
|
||||
# Save current user
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
login_user(user)
|
||||
|
||||
133
apps/authentication/routes.py
Normal file
133
apps/authentication/routes.py
Normal file
@ -0,0 +1,133 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask import render_template, redirect, request, url_for
|
||||
from flask_login import (
|
||||
current_user,
|
||||
login_user,
|
||||
logout_user
|
||||
)
|
||||
|
||||
from flask_dance.contrib.github import github
|
||||
|
||||
from apps import db, login_manager
|
||||
from apps.authentication import blueprint
|
||||
from apps.authentication.forms import LoginForm, CreateAccountForm
|
||||
from apps.authentication.models import Users
|
||||
|
||||
from apps.authentication.util import verify_pass
|
||||
|
||||
|
||||
@blueprint.route('/')
|
||||
def route_default():
|
||||
return redirect(url_for('authentication_blueprint.login'))
|
||||
|
||||
# Login & Registration
|
||||
|
||||
@blueprint.route("/github")
|
||||
def login_github():
|
||||
""" Github login """
|
||||
if not github.authorized:
|
||||
return redirect(url_for("github.login"))
|
||||
|
||||
res = github.get("/user")
|
||||
return redirect(url_for('home_blueprint.index'))
|
||||
|
||||
@blueprint.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
login_form = LoginForm(request.form)
|
||||
if 'login' in request.form:
|
||||
|
||||
# read form data
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
# Locate user
|
||||
user = Users.query.filter_by(username=username).first()
|
||||
|
||||
# Check the password
|
||||
if user and verify_pass(password, user.password):
|
||||
|
||||
login_user(user)
|
||||
return redirect(url_for('authentication_blueprint.route_default'))
|
||||
|
||||
# Something (user or pass) is not ok
|
||||
return render_template('accounts/login.html',
|
||||
msg='Wrong user or password',
|
||||
form=login_form)
|
||||
|
||||
if not current_user.is_authenticated:
|
||||
return render_template('accounts/login.html',
|
||||
form=login_form)
|
||||
return redirect(url_for('home_blueprint.index'))
|
||||
|
||||
|
||||
@blueprint.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
create_account_form = CreateAccountForm(request.form)
|
||||
if 'register' in request.form:
|
||||
|
||||
username = request.form['username']
|
||||
email = request.form['email']
|
||||
|
||||
# Check usename exists
|
||||
user = Users.query.filter_by(username=username).first()
|
||||
if user:
|
||||
return render_template('accounts/register.html',
|
||||
msg='Username already registered',
|
||||
success=False,
|
||||
form=create_account_form)
|
||||
|
||||
# Check email exists
|
||||
user = Users.query.filter_by(email=email).first()
|
||||
if user:
|
||||
return render_template('accounts/register.html',
|
||||
msg='Email already registered',
|
||||
success=False,
|
||||
form=create_account_form)
|
||||
|
||||
# else we can create the user
|
||||
user = Users(**request.form)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
# Delete user from session
|
||||
logout_user()
|
||||
|
||||
return render_template('accounts/register.html',
|
||||
msg='Account created successfully.',
|
||||
success=True,
|
||||
form=create_account_form)
|
||||
|
||||
else:
|
||||
return render_template('accounts/register.html', form=create_account_form)
|
||||
|
||||
|
||||
@blueprint.route('/logout')
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('authentication_blueprint.login'))
|
||||
|
||||
|
||||
# Errors
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_handler():
|
||||
return render_template('home/page-403.html'), 403
|
||||
|
||||
|
||||
@blueprint.errorhandler(403)
|
||||
def access_forbidden(error):
|
||||
return render_template('home/page-403.html'), 403
|
||||
|
||||
|
||||
@blueprint.errorhandler(404)
|
||||
def not_found_error(error):
|
||||
return render_template('home/page-404.html'), 404
|
||||
|
||||
|
||||
@blueprint.errorhandler(500)
|
||||
def internal_error(error):
|
||||
return render_template('home/page-500.html'), 500
|
||||
34
apps/authentication/util.py
Normal file
34
apps/authentication/util.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import binascii
|
||||
|
||||
# Inspiration -> https://www.vitoshacademy.com/hashing-passwords-in-python/
|
||||
|
||||
|
||||
def hash_pass(password):
|
||||
"""Hash a password for storing."""
|
||||
|
||||
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'),
|
||||
salt, 100000)
|
||||
pwdhash = binascii.hexlify(pwdhash)
|
||||
return (salt + pwdhash) # return bytes
|
||||
|
||||
|
||||
def verify_pass(provided_password, stored_password):
|
||||
"""Verify a stored password against one provided by user"""
|
||||
|
||||
stored_password = stored_password.decode('ascii')
|
||||
salt = stored_password[:64]
|
||||
stored_password = stored_password[64:]
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512',
|
||||
provided_password.encode('utf-8'),
|
||||
salt.encode('ascii'),
|
||||
100000)
|
||||
pwdhash = binascii.hexlify(pwdhash).decode('ascii')
|
||||
return pwdhash == stored_password
|
||||
Reference in New Issue
Block a user