We kindly request your cooperation in refraining from engaging in an unusually high volume of requests.
20 | """ 21 | return make_response(response, 429) 22 | 23 | 24 | limiter = Limiter( 25 | key_func=get_remote_address, 26 | default_limits=['30/minute'], 27 | on_breach=default_error_responder, 28 | auto_check=False 29 | # default_limits_exempt_when=default_exempt_when, 30 | ) 31 | -------------------------------------------------------------------------------- /backend/tests/flask/test_limiter.py: -------------------------------------------------------------------------------- 1 | # Other modules 2 | import pytest 3 | 4 | # Local modules 5 | from app import create_app 6 | from app.extensions import limiter 7 | from flask_limiter.util import get_remote_address 8 | 9 | 10 | @pytest.fixture 11 | def app(): 12 | app = create_app() 13 | 14 | limiter.enabled = True 15 | limiter.reset() 16 | 17 | @app.route('/tests/ratelimit') 18 | @limiter.limit(key_func=get_remote_address, limit_value="5/minute") 19 | def index(): 20 | return "Test route" 21 | 22 | return app 23 | 24 | 25 | def test_rate_limiter(app): 26 | with app.test_client() as client: 27 | # Send 5 requests within the rate limit 28 | for _ in range(5): 29 | response = client.get('/tests/ratelimit') 30 | assert response.status_code == 200 31 | assert response.data.decode('utf-8') == "Test route" 32 | 33 | # Send another request, which should exceed the rate limit 34 | response = client.get('/tests/ratelimit') 35 | assert response.status_code == 429 36 | assert response.headers['Retry-After'] is not None -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Riadh Azzoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/app/utils/logger.py: -------------------------------------------------------------------------------- 1 | # Other modules 2 | import os 3 | from pathlib import Path 4 | from logging.config import dictConfig 5 | 6 | BASE_DIR = Path(__file__).resolve().parent.parent.parent 7 | LOG_FILENAME = "app.log" 8 | LOG_FOLDER_DIR = BASE_DIR / "logs" 9 | LOG_FILE_PATH = os.path.join(LOG_FOLDER_DIR, LOG_FILENAME) 10 | 11 | 12 | def setup_flask_logger(): 13 | if not os.path.isdir(LOG_FOLDER_DIR): 14 | os.makedirs(LOG_FOLDER_DIR) 15 | 16 | dictConfig({ 17 | 'version': 1, 18 | 'formatters': {'default': { 19 | 'format': '[%(asctime)s] %(levelname)s : %(message)s', 20 | }}, 21 | 'handlers': {'file': { 22 | 'class': 'logging.handlers.TimedRotatingFileHandler', 23 | 'formatter': 'default', 24 | 'filename': LOG_FILE_PATH, 25 | 'backupCount': 2, 26 | }, 'wsgi': { 27 | 'class': 'logging.StreamHandler', 28 | 'stream': 'ext://flask.logging.wsgi_errors_stream', 29 | 'formatter': 'default' 30 | } 31 | }, 32 | 'root': { 33 | 'level': 'DEBUG', 34 | 'handlers': ['file', 'wsgi'] 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /frontend/src/components/AlertError.tsx: -------------------------------------------------------------------------------- 1 | import { BiErrorCircle } from "react-icons/bi"; 2 | import { AiOutlineClose } from "react-icons/ai"; 3 | 4 | interface AlertErrorProps { 5 | errorMsg: string; 6 | setErrorMsg: React.Dispatch