ajnisbet / opentopodata

Open alternative to the Google Elevation API!
https://www.opentopodata.org
MIT License
312 stars 69 forks source link

Problem with OPTIONS requests #93

Closed MaRaSu closed 6 months ago

MaRaSu commented 8 months ago

For HTTPS-connections browser is sending OPTIONS before POST. I have nginx in front of OpenTopoData to handle SSL for HTTPS, but I do not have it configured for OPTIONS, rather all requests are passed to OpenTopoData. OpenTopoData has Flask @app.route handlers that include OPTIONS, but it does not seem to handle it correctly for my case: it expects OPTIONS request to have same payload or query params as GET or POST, which I think is not the case for browser sent OPTIONS. Furthermore I believe the response is missing some typical OPTIONS header parameters. This is not my area of expertise, just read the MDN to get it working...

Here are the changes I did:

  1. Added catch all path for OPTIONS requests

    @app.route('/<path:path>', methods=['OPTIONS'])
    def handle_options(path):
    response = jsonify({'status': 'OK'})
    response.headers['Access-Control-Allow-Origin'] = _load_config()["access_control_allow_origin"]
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
    return response
  2. Removed OPTIONS method from all other `@app.route´ handlers

  3. Added extra headers to apply_cors(this might have been redundant):

@app.after_request
def apply_cors(response):
    """Set CORs header.

    Supporting CORSs enables browsers to make XHR requests. Applies the value
    of the access_control_allow_origin config option.
    """
    try:
        if _load_config()["access_control_allow_origin"]:
            response.headers["access-control-allow-origin"] = _load_config()[
                "access_control_allow_origin"
            ]
            response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
            response.headers["Access-Control-Allow-Headers"] = "Content-Type"
    except config.ConfigError:
        # If the config isn't loading, allow the request to complete without
        # CORS so user can see error message.
        pass

    return response
ajnisbet commented 6 months ago

Thanks for raising this and for the sample code, I learned something new today!

I went with this version:

@app.before_request
def handle_preflight():
    if request.method == "OPTIONS":
        response = Response(status=204)
        response.headers["access-control-allow-methods"] = "GET,POST,OPTIONS,HEAD"
        response.headers["access-control-allow-headers"] = "content-type,x-api-key"
        return response

which doesn't require changing the existing CORS logic.

I also looked at using flask-cors but your approach seems much better!

ajnisbet commented 6 months ago

I should probably do something similar with HEAD requests, though might wait until it's actually an issue for someone!