diff --git a/main.py b/main.py index 68281c6..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 @@ -11,8 +12,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) @@ -22,11 +29,36 @@ 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): - 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: '{_truncate_param(limit_param)}' is not an integer.") + + try: + page = int(page_param) + except (TypeError, ValueError): + 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 = { @@ -123,7 +155,14 @@ def api_hadiths(): chapter_id = request.args.get("chapterId") if chapter_id: - query = query.filter_by(babID=float(chapter_id)) + try: + 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") if hadith_number: 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