Flask - MongoDB - Protect routes - python

I am new to Flask and I built a basic web app using Flask. This app will be used by a single user. The user must be connected in order to access any routes. What would be the easiest and most secure way to create new routes on my app and make sure that the user is logged in before they are able to access the page?
I added this route and I am able to access the page even if I am logged in.
#login_required
#app.route('/secret')
def secret():
return "hello world"
app.py
from flask import Flask, render_template, url_for, request, session, redirect
from flask_pymongo import PyMongo
import bcrypt
app = Flask(__name__)
app.config['MONGO_DBNAME'] = xxx'
app.config['MONGO_URI'] = 'xxxx'
mongo = PyMongo(app)
#app.route('/')
def index():
if 'username' in session:
return 'You are logged in as ' + session['username']
return render_template('index.html')
#app.route('/login', methods=['POST'])
def login():
users = mongo.db.users
login_user = users.find_one({'name' : request.form['username']})
if login_user:
if bcrypt.hashpw(request.form['pass'].encode('utf-8'), login_user['password']) == login_user['password']:
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'Invalid username/password combination'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'name' : request.form['username']})
if existing_user is None:
hashpass = bcrypt.hashpw(request.form['pass'].encode('utf-8'), bcrypt.gensalt())
users.insert({'name' : request.form['username'], 'password' : hashpass})
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'That username already exists!'
return render_template('register.html')
#app.route('/logout')
def logout():
session.pop('username', None)
return render_template('index.html')
if __name__ == '__main__':
app.secret_key = 'mysecret'
app.run(debug=True, port='3500')

You can use the flask-login module for this. It handles user authentication, allowing routes to be protected with the #login_required decorator.
I recommend reading the documentation, as you need to provide a class with certain properties to represent your users and you need to implement a method that returns a user based on a given identifier.
When a user is logged in, the templates can access the current_user variable and it's properties (as defined by you; name, email etc).
Here is a simple example of the python code (I have not included the static files or templates).
from flask import Flask, render_template, request, url_for, request, redirect, abort
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_pymongo import PyMongo
from flask_bcrypt import Bcrypt
from urllib.parse import urlparse, urljoin
import sys
# Import User classes
from user import User, Anonymous
# Create app
app = Flask(__name__)
# Configuration
app.config['MONGO_DBNAME'] = 'database_name'
app.config['MONGO_URI'] = 'mongo_database_uri'
app.secret_key = 'change this before production'
# Create Pymongo
mongo = PyMongo(app)
# Create Bcrypt
bc = Bcrypt(app)
# Create login manager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.anonymous_user = Anonymous
login_manager.login_view = "login"
# ROUTES
#app.route('/')
def index():
return render_template('index.html')
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
if current_user.is_authenticated:
return redirect(url_for('/index'))
return render_template('login.html')
users = mongo.db.users
user_data = users.find_one({'email': request.form['email']}, {'_id' : 0 })
if user_data:
if bc.check_password_hash(user_data['password'], request.form['pass']):
user = User(user_data['title'], user_data['first_name'], user_data['last_name'], user_data['email'], user_data['password'], user_data['id'])
login_user(user)
#Check for next argument (direct user to protected page they wanted)
next = request.args.get('next')
if not is_safe_url(next):
return abort(400)
return redirect(next or url_for('profile'))
return 'Invalid email or password'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'email' : request.form['email']}, {'_id' : 0 })
if existing_user is None:
logout_user()
hashpass = bc.generate_password_hash(request.form['pass']).decode('utf-8')
new_user = User(request.form['title'], request.form['first_name'], request.form['last_name'], request.form['email'], hashpass)
login_user(new_user)
users.insert_one(new_user.dict())
return redirect(url_for('profile'))
return 'That email already exists!'
return render_template('register.html')
#app.route('/profile', methods=['GET'])
#login_required
def profile():
return render_template('profile.html')
#app.route('/list', methods=['GET'])
#login_required
def list():
#if current_user.id:
log(current_user.is_authenticated)
all_users = mongo.db.users.find({}, {'_id' : 0 })
return render_template('list.html', users = all_users)
#app.route('/logout', methods=['GET'])
#login_required
def logout():
logout_user()
return redirect(url_for('index'))
# Login Manager requirements
#login_manager.user_loader
def load_user(userid):
# Return user object or none
users = mongo.db.users
user = users.find_one({'id': userid}, {'_id' : 0 })
if user:
log(user)
return User(user['title'], user['first_name'], user['last_name'], user['email'], user['password'], user['id'])
return None
# Safe URL
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
# Run app
if __name__ == '__main__':
app.run(debug=True)
I have a functioning basic template app using flask, flask-login and flask-pymongo at https://github.com/chriswilson1982/flask-mongo-app

Related

AttributeError: 'list' object has no attribute 'is_active'

I am building a website using Flask and when I try to log in and signup I get this error AttributeError: 'list' object has no attribute 'is_active'
an error like this appears when I have used login_user in the flask_login library
here I use firestore database
I have tried various solutions on StackOverflow but none of them worked, I'm very confused now
this is my code
__ini__.py
from flask import Flask
from flask_login import UserMixin
from flask_login import LoginManager
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'adjadlkahd'
db = connect_database()
from .views import views
from .auth import auth
from .models import User
#check user login
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
#create user model
#login_manager.user_loader
def load_user(id):
return User.get_user_id(id)
#register blueprints
app.register_blueprint(views, url_prefix='/')
app.register_blueprint(auth, url_prefix='/')
return app
cred = credentials.Certificate("websites\serviceAccountKey.json")
firebase_admin.initialize_app(cred)
def connect_database():
db = firestore.client()
return db
auth.py
from flask import Blueprint, render_template, request, redirect, url_for
from .models import User
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, login_required, logout_user, current_user
auth = Blueprint('auth', __name__)
#auth.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
user = User.log_in(email)
if user:
if check_password_hash(user[0].to_dict()['password'], password):
login_user(user, remember=True)
redirect(url_for('views.home'))
else:
return "<h1>paass salah</h1>"
else:
return "<h1>user tidak ada</h1>"
return render_template('login.html')
#auth.route('/logout')
#login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
#auth.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
if User.check_user(email):
return "<h1>email already exist</h1>"
new_user = User(username=username, email=email,
password=generate_password_hash(password)).create_user()
login_user(new_user, remember=True)
return redirect(url_for('views.home'))
return render_template('signup.html')
models.py
from flask_login import UserMixin
from . import connect_database
import uuid
db = connect_database()
class User(UserMixin):
def __init__(self, username, email, password):
self.username = username
self.email = email
self.password = password
def create_user(self):
db.collection('user').document(self.username).set({
'id' : str(uuid.uuid4()),
'username': self.username,
'email': self.email,
'password': self.password
})
def log_in(email):
user = db.collection('user').where('email', '==', email).get()
return user
def check_user(email):
user = db.collection('user').where('email', '==', email).get()
return bool(user)
def get_user_id(id):
return db.collection('user').document(id).get()
views.py
from flask import Blueprint
from flask_login import login_required, current_user
views = Blueprint('views', __name__)
#views.route('/')
#login_required
def home():
return "<h1>logged in</h1>"
For flask login to work you need to have a user class that implements some properties and methods. When you use login_user method the argument should be an instance of that user class.
make user that your method def log_in(email) returns a user object not a list.
def log_in(email):
user = db.collection('user').where('email', '==', email).get()
return user # <-- most probably is a list.
Read the items form the user collection
Create an instance of the user class
fill the properties of the user class from the collection
return the instance

Flask - learning how to structure my Flask application and failed to display any of my routes or views

I am not sure if its a circular import issue but havent got an error indicating so. How do i make my app display the routes!!!!!
Here is my application structure
My init.py file is where i have created my FLask application instance. I have tried to import views into it but it refuses to work
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SECRET_KEY'] = 'e14c80be9484a60c179678f7a0c5d6f1'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
Here is my views.py file, i imported the app instance in it to get my routes working but in vain
from flask import render_template, url_for, flash, redirect
from crud_app import app
from crud_app.forms import RegistrationForm, LoginForm
#app.route('/')
#app.route('/index')
def index():
return render_template('index.html')
#app.route('/home')
def home():
return render_template('home.html', title='HomePage')
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
if form.email.data == 'andy#gmail.com' and form.password.data == 'password':
flash('Your LogIn was a Success')
return redirect(url_for('home'))
else:
flash('Login Unsuccessful, Please check your Email and Password ')
return render_template('login.html', title='loginPage', form=form)
#app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
flash(f'Welcome { form.username.data} ,your account has been created!')
return redirect(url_for('home'))
return render_template('register.html', title='RegisterPage', form=form)

Flask session redirects to login page

I am currently in the process of building a flask based LAN chatting app (using sqlite3 to store usernames and socketio for messaging) and am having trouble implementing sessions correctly.
I have followed both this guide:
https://www.techwithtim.net/tutorials/flask/sessions/
and read the documentation here https://flask-session.readthedocs.io/en/latest/ but am somehow still not getting my code to work:
In the login page, when the username is posted, I want users to be redirected to the chat-page.html, but this does not occur. Instead they are redirected to the login page, and I cannot figure out why:
from flask import Flask, render_template, request, flash, session, redirect, url_for
#creating the routes
#app.route('/login', methods=["POST", "GET"])
def login_form():
if request.method == "POST":
username = request.form.get("user_name")
session["user"] = username
return redirect(url_for('chat_page'))
else:
if "user" in session:
return redirect(url_for('chat_page'))
return render_template('login.html')
#app.route('/chat-page')
def chat_page():
if "user" in session:
username = session["user"]
return render_template('chat-page.html', Uname=username)
return redirect(url_for('login_form'))
#app.route("/logout")
def logout():
session.pop("user", None)
flash("You have been logged out!")
return redirect(url_for('login_form'))
from flask_session import Session
app = Flask(__name__)
Session(app)
When I tried to debug your code, I ran into issues with the secret key. I don't know how or where you set it or call your app, but here is my complete code that worked. They key might be to set app.config['SESSION_TYPE'] = 'filesystem'. I used this answer to solve it.
from flask import Flask, render_template, request, flash, session, redirect, url_for
from flask_session import Session
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SECRET_KEY'] = 'secret key'
Session(app)
# creating the routes
#app.route('/login', methods=["POST", "GET"])
def login_form():
if request.method == "POST":
username = request.form.get("user_name")
session["user"] = username
return redirect(url_for('chat_page'))
else:
if "user" in session:
return redirect(url_for('chat_page'))
return render_template('login.html')
#app.route('/chat-page')
def chat_page():
if "user" in session:
return '<div>chat page!</div>'
return redirect(url_for('login_form'))
#app.route("/logout")
def logout():
session.pop("user", None)
flash("You have been logged out!")
return redirect(url_for('login_form'))
app.run(debug=True)
What I'm saying is, your redirect logic is completely fine. The issue must be with the session.

How can I rate-limit my Flask application per user?

https://flask-limiter.readthedocs.io/en/stable/
I am looking at Flask-Limiter's documentations and I'm unable to find how to rate-limit per user, everything is globally. Example, Instead of setting it to max 200 requests by all users, how can I make it 200 requests per day by a single user? (IP, or any other identification I don't know what's used)
I found this in the recipes:
Rate limiting a route by current user (using Flask-Login):
#route("/test")
#login_required
#limiter.limit("1 per day", key_func = lambda : current_user.username)
def test_route():
return "42"
UPDATED: added simple example
Here is a simple Flask app implementing the recipe to give you better idea:
from flask import Flask, redirect
from flask_login import (
LoginManager,
UserMixin,
current_user,
login_required,
login_user,
logout_user
)
from flask_limiter import Limiter
app = Flask(__name__)
# flask-login
app.secret_key = 'super secret string'
login_manager = LoginManager()
login_manager.init_app(app)
# flask-limiter
limiter = Limiter(app)
# user class
class User(UserMixin):
def __init__(self, id):
self.id = id
self.username = id
# memory storage
users = [User('user')]
#login_manager.user_loader
def load_user(user_id):
return users[0]
#app.route('/')
def index():
return 'Hello, World!'
#app.route('/login')
def login():
if not current_user.is_authenticated:
login_user(users[0])
return redirect('/secured')
#app.route('/logout')
#login_required
def logout():
logout_user()
return redirect('/')
#app.route('/secured')
#login_required
#limiter.limit("2 per day", key_func = lambda : current_user.username)
def secured():
return f"Hello, {current_user.id}"
if __name__ == '__main__':
app.run()

Python3 bcrypt, pymongo, flask ValueError: Invalid salt

I am trying to create a website using flask, bcrypt and pymongo that allows you to register an account and login. Currently register is working but login is not. When I click login I get this error.
My code:
from flask import Flask, render_template, url_for, request, session, redirect
from flask_pymongo import PyMongo
import bcrypt
app = Flask(__name__)
app.config['MONGO_DBNAME'] = 'websitetest'
app.config['MONGO_URI'] = 'mongodb://localhost:27017'
mongo = PyMongo(app)
#app.route('/')
def index():
if 'username' in session:
return('You are logged in as ' + session['username'])
return render_template('index.html')
#app.route('/login', methods=['POST'])
def login():
users = mongo.db.users
login_user = users.find_one({'name': request.form['username']})
if login_user:
if bcrypt.hashpw(bytes(request.form['pass'], 'utf-8'), bytes(request.form['pass'], 'utf-8')) == bytes(request.form['pass'], 'utf-8'):
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'Invalid username/password combination.'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'name': request.form['username']})
if existing_user is None:
hashpass = bcrypt.hashpw(request.form['pass'].encode('utf-8'), bcrypt.gensalt())
users.insert({'name': request.form['username'], 'password': hashpass})
session['username'] = request.form['username']
return redirect(url_for('index'))
return('That username already exists!')
return render_template('register.html')
if __name__ == '__main__':
app.secret_key = 'mysecret'
app.run(debug=True)
Any help would be greatly appreciated. Thanks!
This line is not following the API description of bcrypt:
if bcrypt.hashpw(bytes(request.form['pass'], 'utf-8'), bytes(request.form['pass'], 'utf-8')) == bytes(request.form['pass'], 'utf-8'):
The docs say to compare like so:
if bcrypt.hashpw(password, hashed) == hashed:
hashed in your environment is represented by this line in your code:
hashpass = bcrypt.hashpw(request.form['pass'].encode('utf-8'), bcrypt.gensalt())
So you need to retrieve hashpass in some way so your code compares thusly:
if bcrypt.hashpw(bytes(request.form['pass'], 'utf-8'), hashpass) == hashpass:
Note that if you are using a more recent version (3x) of bcrypt, you should use:
bcrypt.checkpw(password, hashed):

Categories