diff --git a/app/__init__.py b/app/__init__.py index 65c596b..75d0d8c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,6 +10,7 @@ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE'] app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SECURITY_REGISTERABLE'] = True +app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ['username'] app.config['SECURITY_SEND_REGISTER_EMAIL'] = False # Please use your own salt for productive use! app.config['SECURITY_PASSWORD_SALT'] = "eph4OoGh Oochiel4" @@ -18,11 +19,11 @@ from flask_admin import Admin admin = Admin(app, name='s0inv', template_mode='bootstrap3', url='/'+NAMESPACE) -from app import models,views, modelviews +from app import models,views, modelviews, forms # Setup Flask-Security from flask_security import Security, SQLAlchemyUserDatastore, current_user user_datastore = SQLAlchemyUserDatastore(db, models.User, models.Role) -security = Security(app, user_datastore) +security = Security(app, user_datastore, register_form=forms.ExtendedRegisterForm, login_form=forms.LoginForm) modelviews.__init__() def build_sample_db(): diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..c19c921 --- /dev/null +++ b/app/forms.py @@ -0,0 +1,59 @@ +from flask_security.forms import RegisterForm, LoginForm, Required, Form, NextFormMixin +from wtforms import StringField, PasswordField, BooleanField, SubmitField +from flask import request, current_app +from app import models, db +from flask_security.utils import verify_and_update_password +from flask_security.confirmable import requires_confirmation + +class ExtendedRegisterForm(RegisterForm): + username = StringField('Username', [Required()]) + +class LoginForm(Form, NextFormMixin): + + username = StringField('Username', + validators=[Required(message='EMAIL_NOT_PROVIDED')]) + password = PasswordField('Password', + validators=[Required()]) + remember = BooleanField("Remember me") + submit = SubmitField("Log In") + + def __init__(self, *args, **kwargs): + super(LoginForm, self).__init__(*args, **kwargs) + if not self.next.data: + self.next.data = request.args.get('next', '') + self.remember.default = True + if current_app.extensions['security'].recoverable and \ + not self.password.description: + html = Markup(u'{message}'.format( + url=url_for_security("forgot_password"), + message=get_message("FORGOT_PASSWORD")[0], + )) + self.password.description = html + + def validate(self): + if not super(LoginForm, self).validate(): + return False + + # self.user = _datastore.get_user(self.username.data) + self.user = models.User.query.filter(models.User.username == self.username.data).first() + + if self.user is None: + self.username.errors.append(get_message('USER_DOES_NOT_EXIST')[0]) + # Reduce timing variation between existing and non-existung users + hash_password(self.password.data) + return False + if not self.user.password: + self.password.errors.append(get_message('PASSWORD_NOT_SET')[0]) + # Reduce timing variation between existing and non-existung users + hash_password(self.password.data) + return False + if not verify_and_update_password(self.password.data, self.user): + self.password.errors.append(get_message('INVALID_PASSWORD')[0]) + return False + if requires_confirmation(self.user): + self.username.errors.append(get_message('CONFIRMATION_REQUIRED')[0]) + return False + if not self.user.is_active: + self.username.errors.append(get_message('DISABLED_ACCOUNT')[0]) + return False + return True diff --git a/app/models.py b/app/models.py index c06d752..13ceb6f 100644 --- a/app/models.py +++ b/app/models.py @@ -19,6 +19,7 @@ class Role(db.Model, RoleMixin): class User(db.Model, UserMixin): __tablename__ = 'User' id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(255), unique=True) email = db.Column(db.String(255), unique=True) password = db.Column(db.String(255)) active = db.Column(db.Boolean()) diff --git a/app/modelviews.py b/app/modelviews.py index 7f8c091..936e5c3 100644 --- a/app/modelviews.py +++ b/app/modelviews.py @@ -56,6 +56,20 @@ def is_accessible(self): else: return False +class UserView(ModelView): + can_create = False + can_edit = False + can_delete = False + column_exclude_list = ['password','confirmed_at','active', ] + # column_searchable_list = ['name', 'email'] + def is_accessible(self): + if current_user.is_authenticated: + return True + else: + return False + + def __init__(): admin.add_view(ItemView(models.Item, db.session)) admin.add_view(ProjectView(models.Project, db.session)) + admin.add_view(UserView(models.User, db.session)) diff --git a/app/templates/security/base.html b/app/templates/security/base.html new file mode 100644 index 0000000..da792e7 --- /dev/null +++ b/app/templates/security/base.html @@ -0,0 +1,30 @@ +{% block doc -%} + + + {%- block html %} +
+ {%- block head %} +