0

I have an issue with my app, since I was adding the feature "users roles" I am getting a strange page content on login page looks like default login page instead of my custom login page and I cannot explain why is happening because I did not change the urls and when I am removing the "users roles code" is working perfect. Could you explain how is possible?

This is the code what is creating the issue:

import os
import base64
from io import BytesIO
from flask import Flask, render_template, request, redirect, url_for, flash, session, \
    abort
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, \
    current_user
from flask_bootstrap import Bootstrap
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, SelectField
from wtforms.validators import Required, Length, EqualTo
import onetimepass
import pyqrcode
from datetime import datetime, date, time, timedelta
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from flask_security import Security, SQLAlchemyUserDatastore, \
    UserMixin, RoleMixin, login_required

# create application instance
app = Flask(__name__)
app.config.from_object('config')

# initialize extensions
bootstrap = Bootstrap(app)
db = SQLAlchemy(app)
lm = LoginManager(app)
Base = declarative_base()

# Define models
roles_users = db.Table('roles_users',
        db.Column('users_id', db.Integer(), db.ForeignKey('users.id')),
        db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))

class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

class User(UserMixin, db.Model):
    """User model."""
    __tablename__ = 'users'
    id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))
    otp_secret = db.Column(db.String(16))
    access = db.Column(db.String(16))
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        if self.otp_secret is None:
            # generate a random secret
            self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')

    @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 get_totp_uri(self):
        return 'otpauth://totp/2FA-Demo:{0}?secret={1}&issuer=2FA-Demo' \
            .format(self.username, self.otp_secret)

    def verify_totp(self, token):
        return onetimepass.valid_totp(token, self.otp_secret)

################################################################################
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
###############################################################################

@lm.user_loader
def load_user(user_id):
    """User loader callback for Flask-Login."""
    return User.query.get(int(user_id))

class RegisterForm(FlaskForm):
    """Registration form."""
    access = SelectField(validators=[Required()],
            choices=[("dm", "demo"),
            ("dm2", "demo2")])
    username = StringField('Username', validators=[Required(), Length(1, 9)])
    password = PasswordField('Password', validators=[Required(), Length(4, 4)])
    password_again = PasswordField('Password again',
                                   validators=[Required(), EqualTo('password')])
    submit = SubmitField('Register')


class LoginForm(FlaskForm):
    """Login form."""
    username = StringField('Username', validators=[Required(), Length(1, 9)])
    password = PasswordField('Password', validators=[Required(), Length(4, 4)])
    token = PasswordField('Token', validators=[Required(), Length(6, 6)])
    submit = SubmitField('Login')

@app.route('/')
def index():
    return render_template('index.html')


@app.route('/register', methods=['GET', 'POST'])
def register():
    """User registration route."""
    if current_user.is_authenticated:
        # if user is logged in we get out of here
        return redirect(url_for('index'))
    form = RegisterForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is not None:
            flash('Username already exists.')
            return redirect(url_for('register'))
        # add new user to the database
        user = User(username=form.username.data, password=form.password.data, access=form.access.data)
        db.session.add(user)
        db.session.commit()

        # redirect to the two-factor auth page, passing username in session
        session['username'] = user.username
        return redirect(url_for('two_factor_setup'))
    return render_template('auth/register.html', form=form)


@app.route('/twofactor')
def two_factor_setup():
    if 'username' not in session:
        return redirect(url_for('index'))
    user = User.query.filter_by(username=session['username']).first()
    if user is None:
        return redirect(url_for('index'))
    # since this page contains the sensitive qrcode, make sure the browser
    # does not cache it
    return render_template('auth/2fa.html'), 200, {
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': '0'}


@app.route('/qrcode')
def qrcode():
    if 'username' not in session:
        abort(404)
    user = User.query.filter_by(username=session['username']).first()
    if user is None:
        abort(404)

    # for added security, remove username from session
    del session['username']

    # render qrcode for FreeTOTP
    url = pyqrcode.create(user.get_totp_uri())
    stream = BytesIO()
    url.svg(stream, scale=3)
    return stream.getvalue(), 200, {
        'Content-Type': 'image/svg+xml',
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': '0'}


@app.route('/login', methods=['GET', 'POST'])
def login():
    """User login route."""
    if current_user.is_authenticated:
        # if user is logged in we get out of here
        return redirect(url_for('index'))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is None or not user.verify_password(form.password.data) or \
                not user.verify_totp(form.token.data):
            flash('Invalid username, password or token.')
            return redirect(url_for('login'))

        # log user in
        login_user(user)
        flash('You are now logged in!')
        return redirect(url_for('cm_e_11a'))
    return render_template('auth/login.html', form=form)


@app.route('/logout')
def logout():
    """User logout route."""
    logout_user()
    return redirect(url_for('index'))

And this is the Login page what is returning

pic

instead of mine what should contain username, password & pin

And the login page has this source code:

<h1>Login</h1>
<form action="/login" method="POST" name="login_user_form">
  <input id="next" name="next" type="hidden" value="">
<input id="csrf_token" name="csrf_token" type="hidden" value="IjEyNWY4ZjliNmIxZTJjNmM3YmM4YzFlM2U0NWZlYjJlMDEzYThiNWEi.DZnvqw.key_C7NmtQogY7CSpb14sfVDUOQ">

  <p>
    <label for="email">Email Address</label> <input id="email" name="email" type="text" value="">

  </p>


  <p>
    <label for="password">Password</label> <input id="password" name="password" type="password" value="">

  </p>


  <p>
    <label for="remember">Remember Me</label> <input id="remember" name="remember" type="checkbox" value="y">

  </p>


  <p><input id="next" name="next" type="hidden" value=""></p>


  <p><input id="submit" name="submit" type="submit" value="Login"></p>

</form>
1

I found the solution!!!

Views Flask-Security is packaged with a default template for each view it presents to a user. Templates are located within a sub-folder named security. The following is a list of view templates:

security/forgot_password.html
security/login_user.html
security/register_user.html
security/reset_password.html
security/change_password.html
security/send_confirmation.html
security/send_login.html

Overriding these templates is simple:

  • Create a folder named security within your application’s templates folder
  • Create a template with the same name for the template you wish to override

Not the answer you're looking for? Browse other questions tagged or ask your own question.