Basic auth system implimented
Basic user authantication has implimented with Flask-Login and pass word hasing with werkzeg.
This commit is contained in:
parent
e24f059bb0
commit
8b95f4c054
|
@ -1,8 +1,11 @@
|
|||
from flask import Flask, render_template
|
||||
|
||||
from flask_adminlte import AdminLTE
|
||||
from flask_mail import Mail
|
||||
from flask_moment import Moment
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager
|
||||
|
||||
from config import config
|
||||
|
||||
AdminLTE = AdminLTE()
|
||||
|
@ -10,20 +13,37 @@ mail = Mail()
|
|||
moment = Moment()
|
||||
db = SQLAlchemy()
|
||||
|
||||
# initialize flask_login
|
||||
login_manager = LoginManager()
|
||||
login_manager.session_protection = 'strong'
|
||||
login_manager.login_view = 'auth.login'
|
||||
|
||||
# user loader call back function
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
def create_app(config_name):
|
||||
dash = Flask(__name__)
|
||||
dash.config.from_object(config[config_name])
|
||||
config[config_name].init_app(dash)
|
||||
|
||||
|
||||
|
||||
AdminLTE.init_app(dash)
|
||||
mail.init_app(dash)
|
||||
moment.init_app(dash)
|
||||
db.init_app(dash)
|
||||
login_manager.init_app(dash)
|
||||
|
||||
|
||||
# attach routes and custom error pages here
|
||||
|
||||
# main application
|
||||
from main import main as main_blueprint
|
||||
dash.register_blueprint(main_blueprint)
|
||||
|
||||
# auth application
|
||||
from .auth import auth as auth_blueprint
|
||||
dash.register_blueprint(auth_blueprint, url_prefix='/auth')
|
||||
|
||||
return dash
|
|
@ -0,0 +1,5 @@
|
|||
from flask import Blueprint
|
||||
|
||||
auth = Blueprint('auth', __name__)
|
||||
|
||||
from . import views
|
|
@ -0,0 +1,12 @@
|
|||
from flask_wtf import Form
|
||||
|
||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||
from wtforms.validators import Required, Length, Email
|
||||
|
||||
class LoginForm(Form):
|
||||
email = StringField('Email', validators=[Required(), Length(1, 128),
|
||||
Email()])
|
||||
password = PasswordField('Password', validators=[Required()])
|
||||
remember_me = BooleanField('Keep me logged in')
|
||||
submit = SubmitField('Log In')
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from flask import render_template, redirect, request, url_for, flash
|
||||
from flask_login import login_user, logout_user, login_required
|
||||
from . import auth
|
||||
from ..models import User
|
||||
from .forms import LoginForm
|
||||
|
||||
|
||||
@auth.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
form = LoginForm()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user is not None and user.verify_password(form.password.data):
|
||||
login_user(user, form.remember_me.data)
|
||||
return redirect(request.args.get('next') or url_for('main.index'))
|
||||
flash('Invalid username or password.')
|
||||
return render_template('auth/login.html', form=form)
|
||||
|
||||
@auth.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash('You have been logged out.')
|
||||
return redirect(url_for('main.index'))
|
|
@ -1,4 +1,9 @@
|
|||
import dateutil.parser
|
||||
|
||||
from flask import render_template, session, redirect, url_for, current_app
|
||||
|
||||
from flask_login import login_required
|
||||
|
||||
from .. import db
|
||||
from ..models import User
|
||||
from ..email import send_email
|
||||
|
@ -7,31 +12,9 @@ from .forms import NameForm
|
|||
|
||||
|
||||
@main.route('/', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def index():
|
||||
form = NameForm()
|
||||
current_user = User()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(username=form.name.data).first()
|
||||
if user is None:
|
||||
user = User(username=form.name.data)
|
||||
db.session.add(user)
|
||||
session['known'] = False
|
||||
if current_app.config['DASH_STACK_ADMIN']:
|
||||
send_email(current_app.config['DASH_STACK_ADMIN'], 'New User',
|
||||
'mail/new_user', user=user)
|
||||
else:
|
||||
session['known'] = True
|
||||
session['name'] = form.name.data
|
||||
return redirect(url_for('.index'))
|
||||
return render_template('index.html',
|
||||
form=form, name=session.get('name'),
|
||||
known=session.get('known', False),
|
||||
current_user=current_user)
|
||||
|
||||
@main.route('/login')
|
||||
def login():
|
||||
current_user = User()
|
||||
return render_template('login.html', current_user=current_user)
|
||||
return render_template('index.html')
|
||||
|
||||
@main.route('/lockscreen')
|
||||
def lockscreen():
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import dateutil.parser
|
||||
import datetime
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
from flask_login import UserMixin
|
||||
|
||||
from . import db
|
||||
from . import login_manager
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
class Role(db.Model):
|
||||
__tablename__ = 'roles'
|
||||
|
@ -14,15 +22,28 @@ class Role(db.Model):
|
|||
return '<Role %r>' % self.name
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
class User(UserMixin, db.Model):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
email = db.Column(db.String(128), unique=True, index=True)
|
||||
username = db.Column(db.String(64), unique=True, index=True)
|
||||
full_name = db.Column(db.String(256), index=True)
|
||||
avatar = db.Column(db.String(256), index=True)
|
||||
created_at = dateutil.parser.parse("November 12, 2012")
|
||||
password_hash = db.Column(db.String(128))
|
||||
full_name = db.Column(db.String(255), index=True)
|
||||
avatar = db.Column(db.String(255), index=True)
|
||||
created_at = db.Column(db.DateTime)
|
||||
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
raise AttributeError('password is not a readable attribute')
|
||||
|
||||
@password.setter
|
||||
def password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def verify_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % self.username
|
||||
return '<User %r>' % self.username
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<strong>Admin</strong>LTE
|
||||
<strong>-</strong> stack
|
||||
</div>
|
||||
<div class="login-box-body">
|
||||
<p>Log into your account!</p>
|
||||
|
@ -23,17 +23,21 @@
|
|||
{% endif %}
|
||||
{% endwith %}
|
||||
{# Render the login form. #}
|
||||
<form method="post">
|
||||
<form method="post" name="login">
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="form-group has-feedback">
|
||||
<input type="email" placeholder="Username or Email" required="true" class="form-control">
|
||||
<input type="email" name="email" placeholder="Email" required="true" class="form-control">
|
||||
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="password" placeholder="Password" required="true" class="form-control">
|
||||
<input type="password" name="password" placeholder="Password" required="true" class="form-control">
|
||||
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-4 col-xs-offset-8">
|
||||
<div class="col-xs-6 col-xs-offset-0">
|
||||
<p><input type="checkbox" name="remember_me" /> Remember me?</p>
|
||||
</div>
|
||||
<div class="col-xs-6 col-xs-offset-0">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-flat">Log In</button>
|
||||
</div>
|
||||
</div>
|
|
@ -233,7 +233,9 @@
|
|||
<a href="#" class="btn btn-default btn-flat">Profile</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a href="{{ url_for('.login') }}" class="btn btn-default btn-flat">Sign out</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('auth.logout') }}" class="btn btn-default btn-flat">Sign out</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -272,7 +274,7 @@
|
|||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<li>
|
||||
<a href="{{ url_for('.login') }}">
|
||||
<a href="{{ url_for('auth.login') }}">
|
||||
<i class="fa fa-circle-o"></i>
|
||||
Login
|
||||
</a>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 24ea14f8cb4e
|
||||
Revises: 8491b2bf3af9
|
||||
Create Date: 2016-07-09 23:21:24.446391
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '24ea14f8cb4e'
|
||||
down_revision = '8491b2bf3af9'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('users', sa.Column('email', sa.String(length=128), nullable=True))
|
||||
op.add_column('users', sa.Column('password_hash', sa.String(length=128), nullable=True))
|
||||
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||
op.drop_column('users', 'password_hash')
|
||||
op.drop_column('users', 'email')
|
||||
### end Alembic commands ###
|
|
@ -0,0 +1,26 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 28f38d9e7724
|
||||
Revises: 24ea14f8cb4e
|
||||
Create Date: 2016-07-10 18:31:53.096426
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '28f38d9e7724'
|
||||
down_revision = '24ea14f8cb4e'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('users', sa.Column('created_at', sa.DateTime(), nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('users', 'created_at')
|
||||
### end Alembic commands ###
|
|
@ -0,0 +1,22 @@
|
|||
import unittest
|
||||
from dash.models import User
|
||||
|
||||
class UserModelTestCase(unittest.TestCase):
|
||||
def test_password_setter(self):
|
||||
u = User(password = 'cat')
|
||||
self.assertTrue(u.password_hash is not None)
|
||||
|
||||
def test_no_password_getter(self):
|
||||
u = User(password = 'cat')
|
||||
with self.assertRaises(AttributeError):
|
||||
u.password
|
||||
|
||||
def test_password_verification(self):
|
||||
u = User(password = 'cat')
|
||||
self.assertTrue(u.verify_password('cat'))
|
||||
self.assertFalse(u.verify_password('dog'))
|
||||
|
||||
def test_password_salts_are_random(self):
|
||||
u = User(password='cat')
|
||||
u2 = User(password='cat')
|
||||
self.assertTrue(u.password_hash != u2.password_hash)
|
Loading…
Reference in New Issue