set rate limit for creating alias endpoint
This commit is contained in:
parent
6435d951e1
commit
0c62ac4b1f
|
@ -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():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue