From 26ab099b17961cd9af603f56aeca39fec1128b73 Mon Sep 17 00:00:00 2001 From: wakqasahmed Date: Sat, 15 Aug 2026 09:39:12 +0200 Subject: [PATCH 1/2] fix: differentiate authentication/request error messages (#274) --- main.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index 68281c6..aae998c 100644 --- a/main.py +++ b/main.py @@ -11,8 +11,14 @@ @app.before_request def verify_secret(): - if not app.debug and request.headers.get("x-aws-secret") != app.config["AWS_SECRET"]: - abort(401) + if app.debug: + return + + secret = request.headers.get("x-aws-secret") + if secret is None: + abort(401, "Missing 'x-aws-secret' header.") + if secret != app.config["AWS_SECRET"]: + abort(401, "Invalid 'x-aws-secret' header value.") @app.errorhandler(HTTPException) @@ -25,8 +31,18 @@ def jsonify_http_error(error): def paginate_results(f): @functools.wraps(f) def decorated_function(*args, **kwargs): - limit = int(request.args.get("limit", 50)) - page = int(request.args.get("page", 1)) + limit_param = request.args.get("limit", 50) + page_param = request.args.get("page", 1) + + try: + limit = int(limit_param) + except (TypeError, ValueError): + abort(400, f"Invalid 'limit' query parameter: '{limit_param}' is not an integer.") + + try: + page = int(page_param) + except (TypeError, ValueError): + abort(400, f"Invalid 'page' query parameter: '{page_param}' is not an integer.") queryset = f(*args, **kwargs).paginate(page=page, per_page=limit, max_per_page=100) result = { @@ -123,7 +139,11 @@ def api_hadiths(): chapter_id = request.args.get("chapterId") if chapter_id: - query = query.filter_by(babID=float(chapter_id)) + try: + chapter_id = float(chapter_id) + except ValueError: + abort(400, f"Invalid 'chapterId' query parameter: '{chapter_id}' is not a number.") + query = query.filter_by(babID=chapter_id) hadith_number = request.args.get("hadithNumber") if hadith_number: From 914af9c49288a9061c5750f7703e3f2c0d50b7a2 Mon Sep 17 00:00:00 2001 From: wakqasahmed Date: Sat, 15 Aug 2026 09:51:47 +0200 Subject: [PATCH 2/2] fix: reject non-finite chapterId and out-of-range limit/page (#274) - reject nan/inf/-inf/Infinity/1e400 chapterId values with a clear 400 instead of letting them reach PyMySQL and raise an unformatted 500 - reject limit/page < 1 with a clear 400 instead of falling through to Flask-SQLAlchemy's bare 404 from paginate(error_out=True) - truncate raw query-param values echoed into error messages to 50 chars - document the new 400 responses for the paginated endpoints and /hadiths in spec.v1.yml --- main.py | 29 ++++++++++++++++++++++++----- spec.v1.yml | 10 ++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index aae998c..0d2554d 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import functools +import math from flask import Flask, jsonify, request, abort from sqlalchemy import and_, func, or_ from werkzeug.exceptions import HTTPException @@ -28,6 +29,16 @@ def jsonify_http_error(error): return jsonify(response), error.code +MAX_PARAM_ECHO_LEN = 50 + + +def _truncate_param(value): + value = str(value) + if len(value) > MAX_PARAM_ECHO_LEN: + return value[:MAX_PARAM_ECHO_LEN] + "..." + return value + + def paginate_results(f): @functools.wraps(f) def decorated_function(*args, **kwargs): @@ -37,12 +48,17 @@ def decorated_function(*args, **kwargs): try: limit = int(limit_param) except (TypeError, ValueError): - abort(400, f"Invalid 'limit' query parameter: '{limit_param}' is not an integer.") + abort(400, f"Invalid 'limit' query parameter: '{_truncate_param(limit_param)}' is not an integer.") try: page = int(page_param) except (TypeError, ValueError): - abort(400, f"Invalid 'page' query parameter: '{page_param}' is not an integer.") + abort(400, f"Invalid 'page' query parameter: '{_truncate_param(page_param)}' is not an integer.") + + if limit < 1: + abort(400, "Invalid 'limit' query parameter: must be >= 1.") + if page < 1: + abort(400, "Invalid 'page' query parameter: must be >= 1.") queryset = f(*args, **kwargs).paginate(page=page, per_page=limit, max_per_page=100) result = { @@ -140,9 +156,12 @@ def api_hadiths(): chapter_id = request.args.get("chapterId") if chapter_id: try: - chapter_id = float(chapter_id) - except ValueError: - abort(400, f"Invalid 'chapterId' query parameter: '{chapter_id}' is not a number.") + parsed_chapter_id = float(chapter_id) + except (TypeError, ValueError): + abort(400, f"Invalid 'chapterId' query parameter: '{_truncate_param(chapter_id)}' is not a number.") + if not math.isfinite(parsed_chapter_id): + abort(400, f"Invalid 'chapterId' query parameter: '{_truncate_param(chapter_id)}' is not a finite number.") + chapter_id = parsed_chapter_id query = query.filter_by(babID=chapter_id) hadith_number = request.args.get("hadithNumber") diff --git a/spec.v1.yml b/spec.v1.yml index 4f49a8f..7d9dd08 100644 --- a/spec.v1.yml +++ b/spec.v1.yml @@ -47,6 +47,8 @@ paths: items: $ref: "#/components/schemas/Collection" - $ref: "#/components/schemas/PaginatedResponse" + "400": + description: Bad request (invalid 'limit' or 'page' query parameter) parameters: - $ref: "#/components/parameters/limit" - $ref: "#/components/parameters/page" @@ -86,6 +88,8 @@ paths: items: $ref: "#/components/schemas/Book" - $ref: "#/components/schemas/PaginatedResponse" + "400": + description: Bad request (invalid 'limit' or 'page' query parameter) parameters: - in: path name: collectionName @@ -137,6 +141,8 @@ paths: items: $ref: "#/components/schemas/Chapter" - $ref: "#/components/schemas/PaginatedResponse" + "400": + description: Bad request (invalid 'limit' or 'page' query parameter) parameters: - in: path name: collectionName @@ -201,6 +207,8 @@ paths: items: $ref: "#/components/schemas/Hadith" - $ref: "#/components/schemas/PaginatedResponse" + "400": + description: Bad request (invalid 'limit' or 'page' query parameter) parameters: - in: path name: collectionName @@ -258,6 +266,8 @@ paths: items: $ref: "#/components/schemas/Hadith" - $ref: "#/components/schemas/PaginatedResponse" + "400": + description: Bad request (invalid 'limit', 'page', or 'chapterId' query parameter) parameters: - in: query name: collection