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()
Related
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
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.
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
I have created a blueprint that handles authenticating. This blue print uses Flask-Login. And has the following, as well as more code not shown.
In the blueprint I have the following:
from flask.ext.login import LoginManager
from flask.ext.login import UserMixin
from flask.ext.login import current_user
from flask.ext.login import login_required
from flask.ext.login import login_user
from flask.ext.login import logout_user
auth_print = Blueprint('auth_print', __name__)
login_manager = LoginManager()
login_manager.login_view = '/login'
class User(UserMixin):
user_store = {} # Stores the users that are already logged in.
def __init__(self, user_id):
self.user_store[user_id] = self # add the user to the user_store
self.username = user_id # the user_id is in fact the username
self.id = unicode(user_id)
def sign_out(self):
logout_user()
try:
del self.user_store[self.id]
except KeyError:
pass
#classmethod
def get(cls, user_id):
return cls.user_store.get(user_id)
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
def get_current_user():
return current_user
#login_required
#auth_print.route('/')
def user():
return "Welcome, and thanks for logging in."
Then I have a small app I would like to add authentication to.
Small App
import the_above_module
app.register_blueprint(the_above_module.auth_print) # register the blueprint
#the_above_module.login_required
#app.route('/profile')
def protected():
name = the_above_module.get_current_user().username
return "Thank you for logging in."
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Now I know the blueprint's #login_required is working because if I open the browser and go to localhost:8000/ I have to sign in.
However if I go to localhost:8000/profile the login_required decorator never gets triggered. I thus get an error because there is no current user.
Why would #login_required work in the blueprint and not in the app, even when im sure to maintain the same name spaces?
You have to change the order of the decorators. Quoting the Flask documentation:
So how would you use that decorator now? Apply it as innermost
decorator to a view function. When applying further decorators, always
remember that the route() decorator is the outermost:
#app.route('/secret_page')
#login_required
def secret_page():
pass
When we want the user not to access the private page or the page which requires login for that case flask provides decorators.
#app.route("/welcome")
#login_required # If the user is not logged in then it will redirected to unauthorized_handler
def welcome_page():
return """<h1> welcome user</h1>"""
#login_manager.unauthorized_handler # In unauthorized_handler we have a callback URL
def unauthorized_callback(): # In call back url we can specify where we want to
return redirect(url_for('login')) # redirect the user in my case it is login page!
I hope your problem is solved !!!
#login_manager.unauthorized_handler
def unauthorized_callback():
return redirect(url_for('website.index'))
I am trying to build a super simple web app for my own use. I'll be the only user, so I don't feel the need to involve a database for user management. I'm trying to use flask-login, but even though my call to login_user succeeds, I'm still met with a 401-Unauthorized page after the redirect to a page with #login_required. Here is the entirety of my app:
from flask import Flask, render_template, request, flash, redirect, url_for
from flask.ext.login import LoginManager, login_user, logout_user, current_user, login_required, UserMixin
app = Flask(__name__, static_url_path="")
app.secret_key = "[redacted]"
login_manager = LoginManager()
login_manager.init_app(app)
#login_manager.login_view = "login"
class User(UserMixin):
def __init__(self, id):
self.id = id
nathan = User('nathan')
#login_manager.user_loader
def load_user(userid):
if userid == 'nathan':
return nathan
else:
return None
#app.route("/logout")
#login_required
def logout():
logout_user()
return redirect(url_for('login'))
#app.route('/')
#login_required
def index():
return render_template('index.html')
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == 'POST':
if request.form['username'] == 'nathan'\
and request.form['password'] == '[redacted]':
login_user(nathan, remember=True)
flash('logged in...', 'success')
return redirect(request.args.get("next") or url_for("index"))
else:
flash('Incorrect username or password. Try again.', 'error')
return render_template("login.html");
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
I've verified that the login actually succeeds (login_user returns true) and that load_user is returning the nathan object.
[EDIT] Also, I'm currently using the Flask development server, in case that's relevant.
Not sure where I'm going wrong. Thanks in advance!
Update:
Since a newer version(0.2.2) of Flask-Login this is no more an issue. Check out the changes in this commit.
If you are using an older version, read on.
The problem here is static_url_path="". For Flask-Login to work you can not have an empty string static_url_path.
The following lines in the Flask-Login source(older version) reveal this:
if (current_app.static_url_path is not None and
request.path.startswith(current_app.static_url_path)
):
# load up an anonymous user for static pages
_request_ctx_stack.top.user = self.anonymous_user()
return
Since your static_url_path is "" the if condition evaluates to True, because of which every page you visit acts like a static page, and hence Flask-Login always loads an anonymous user, instead of continuing to load the actual user(using the load_user callback).
Also do not forget to uncomment #login_manager.login_view = "login"
If you still want to use the root folder of the app itself as the static folder, take a look at this solution, using SharedDataMiddleWare:
app.debug = True
if app.config['DEBUG']:
from werkzeug import SharedDataMiddleware
import os
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
'/': os.path.dirname(__file__)
})
if __name__ == "__main__":
app.run(host="0.0.0.0")