set rate limit for creating alias endpoint

This commit is contained in:
Son NK 2021-03-24 16:26:42 +01:00
parent 6435d951e1
commit 0c62ac4b1f
7 changed files with 90 additions and 18 deletions

View File

@ -10,7 +10,7 @@ from app.api.serializer import (
serialize_alias_info_v2, serialize_alias_info_v2,
get_alias_info_v2, get_alias_info_v2,
) )
from app.config import MAX_NB_EMAIL_FREE_PLAN from app.config import MAX_NB_EMAIL_FREE_PLAN, ALIAS_LIMIT
from app.dashboard.views.custom_alias import verify_prefix_suffix, signer from app.dashboard.views.custom_alias import verify_prefix_suffix, signer
from app.extensions import db, limiter from app.extensions import db, limiter
from app.log import LOG from app.log import LOG
@ -28,7 +28,7 @@ from app.utils import convert_to_id
@api_bp.route("/alias/custom/new", methods=["POST"]) @api_bp.route("/alias/custom/new", methods=["POST"])
@limiter.limit("5/minute") @limiter.limit(ALIAS_LIMIT)
@require_api_auth @require_api_auth
def new_custom_alias(): def new_custom_alias():
""" """
@ -99,7 +99,7 @@ def new_custom_alias():
@api_bp.route("/v2/alias/custom/new", methods=["POST"]) @api_bp.route("/v2/alias/custom/new", methods=["POST"])
@limiter.limit("5/minute") @limiter.limit(ALIAS_LIMIT)
@require_api_auth @require_api_auth
def new_custom_alias_v2(): def new_custom_alias_v2():
""" """
@ -194,7 +194,7 @@ def new_custom_alias_v2():
@api_bp.route("/v3/alias/custom/new", methods=["POST"]) @api_bp.route("/v3/alias/custom/new", methods=["POST"])
@limiter.limit("5/minute") @limiter.limit(ALIAS_LIMIT)
@require_api_auth @require_api_auth
def new_custom_alias_v3(): def new_custom_alias_v3():
""" """

View File

@ -6,14 +6,14 @@ from app.api.serializer import (
get_alias_info_v2, get_alias_info_v2,
serialize_alias_info_v2, serialize_alias_info_v2,
) )
from app.config import MAX_NB_EMAIL_FREE_PLAN from app.config import MAX_NB_EMAIL_FREE_PLAN, ALIAS_LIMIT
from app.extensions import db, limiter from app.extensions import db, limiter
from app.log import LOG from app.log import LOG
from app.models import Alias, AliasUsedOn, AliasGeneratorEnum from app.models import Alias, AliasUsedOn, AliasGeneratorEnum
@api_bp.route("/alias/random/new", methods=["POST"]) @api_bp.route("/alias/random/new", methods=["POST"])
@limiter.limit("5/minute") @limiter.limit(ALIAS_LIMIT)
@require_api_auth @require_api_auth
def new_random_alias(): def new_random_alias():
""" """

View File

@ -369,3 +369,5 @@ try:
COINBASE_YEARLY_PRICE = float(os.environ["COINBASE_YEARLY_PRICE"]) COINBASE_YEARLY_PRICE = float(os.environ["COINBASE_YEARLY_PRICE"])
except Exception: except Exception:
COINBASE_YEARLY_PRICE = 30.00 COINBASE_YEARLY_PRICE = 30.00
ALIAS_LIMIT = "100/day;50/hour;5/minute"

View File

@ -1,4 +1,3 @@
from flask import request
from flask_limiter import Limiter from flask_limiter import Limiter
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
from flask_login import LoginManager from flask_login import LoginManager
@ -14,9 +13,9 @@ migrate = Migrate(db=db)
limiter = Limiter(key_func=get_remote_address) limiter = Limiter(key_func=get_remote_address)
@limiter.request_filter # @limiter.request_filter
def ip_whitelist(): # def ip_whitelist():
# Uncomment line to test rate limit in dev environment # # Uncomment line to test rate limit in dev environment
# return False # # return False
# No limit for local development # # No limit for local development
return request.remote_addr == "127.0.0.1" # return request.remote_addr == "127.0.0.1"

View File

@ -17,6 +17,7 @@ from flask import (
jsonify, jsonify,
flash, flash,
session, session,
g,
) )
from flask_admin import Admin from flask_admin import Admin
from flask_cors import cross_origin, CORS from flask_cors import cross_origin, CORS
@ -499,7 +500,11 @@ def setup_error_page(app):
@app.errorhandler(429) @app.errorhandler(429)
def rate_limited(e): def rate_limited(e):
LOG.warning("Client hit rate limit on path %s", request.path) LOG.warning(
"Client hit rate limit on path %s, user:%s",
request.path,
g.user or current_user,
)
if request.path.startswith("/api/"): if request.path.startswith("/api/"):
return jsonify(error="Rate limit exceeded"), 429 return jsonify(error="Rate limit exceeded"), 429
else: else:

View File

@ -1,4 +1,4 @@
from flask import url_for from flask import url_for, g
from app.alias_utils import delete_alias from app.alias_utils import delete_alias
from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN
@ -208,3 +208,52 @@ def test_success_v3(flask_client):
new_alias: Alias = Alias.get_by(email=r.json["alias"]) new_alias: Alias = Alias.get_by(email=r.json["alias"])
assert new_alias.note == "test note" assert new_alias.note == "test note"
assert len(new_alias.mailboxes) == 2 assert len(new_alias.mailboxes) == 2
def test_custom_domain_alias(flask_client):
user = login(flask_client)
# create a custom domain
CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True)
signed_suffix = signer.sign("@ab.cd").decode()
r = flask_client.post(
"/api/v3/alias/custom/new",
json={
"alias_prefix": "prefix",
"signed_suffix": signed_suffix,
"mailbox_ids": [user.default_mailbox_id],
},
)
assert r.status_code == 201
assert r.json["alias"] == f"prefix@ab.cd"
def test_too_many_requests(flask_client):
user = login(flask_client)
# create a custom domain
CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True)
# can't create more than 5 aliases in 1 minute
for i in range(7):
signed_suffix = signer.sign("@ab.cd").decode()
r = flask_client.post(
"/api/v3/alias/custom/new",
json={
"alias_prefix": f"prefix{i}",
"signed_suffix": signed_suffix,
"mailbox_ids": [user.default_mailbox_id],
},
)
# to make flask-limiter work with unit test
# https://github.com/alisaifee/flask-limiter/issues/147#issuecomment-642683820
g._rate_limiting_complete = False
else:
# last request
assert r.status_code == 429
assert r.json == {"error": "Rate limit exceeded"}

View File

@ -1,6 +1,6 @@
import uuid import uuid
from flask import url_for from flask import url_for, g
from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN
from app.models import Alias from app.models import Alias
@ -69,11 +69,28 @@ def test_out_of_quota(flask_client):
assert r.status_code == 400 assert r.status_code == 400
assert ( assert (
r.json["error"] r.json["error"] == "You have reached the limitation of a free account with "
== "You have reached the limitation of a free account with the maximum of 3 aliases, please upgrade your plan to create more aliases" "the maximum of 3 aliases, please upgrade your plan to create more aliases"
) )
def test_too_many_requests(flask_client):
login(flask_client)
# can't create more than 5 aliases in 1 minute
for _ in range(7):
r = flask_client.post(
url_for("api.new_random_alias", hostname="www.test.com", mode="uuid"),
)
# to make flask-limiter work with unit test
# https://github.com/alisaifee/flask-limiter/issues/147#issuecomment-642683820
g._rate_limiting_complete = False
else:
# last request
assert r.status_code == 429
assert r.json == {"error": "Rate limit exceeded"}
def is_valid_uuid(val): def is_valid_uuid(val):
try: try:
uuid.UUID(str(val)) uuid.UUID(str(val))