mrevutskyi / flask-restless-ng

A Flask extension for creating simple ReSTful JSON APIs from SQLAlchemy models.
https://flask-restless-ng.readthedocs.io
Other
64 stars 11 forks source link

No support for X-Forwarded-Proto or other proxy #38

Closed gnydick closed 1 year ago

gnydick commented 1 year ago

The urls generated for the pagination links have no awareness of what the actual URL used is.

This makes it not usable behind any SSL termination point before the server itself.

I tried to find the place to add the reading of the req state in order to provide you a pull request, but it seems like that information is dropped well advanced of that point in the execution.

I'd be happy with either a total fix or an enhancement to carry the req state along and I'll write the code.

mrevutskyi commented 1 year ago

Hi, for me to better understand the issue, could you please share an example of pagination URL you get now and how it supposed to look instead?

gnydick commented 1 year ago
  "links": {
    "first": "http://<redacted>/api/rest/v1/<redacted>?page[size]=10&page[number]=1",
    "last": "http://<redacted>/api/rest/v1/<redacted>?page[size]=10&page[number]=1",
    "next": null,
    "prev": null,
    "self": "/api/rest/v1/<redacted>"
  },
  "meta": {
    "total": 2
  }

My service is running at https://<redacted>/api/rest/v1. There is a load balancer terminating the SSL connection so the server only sees a request to http://<server_ip>:5001/api/rest/v1

It generates the below links because flask-restless-ng has no idea about the de facto standard of using X-Forwarded- headers or any other mechanism for hinting about the context of the request.

From what I can tell, IIRC, the flask-restless-ng code base drops all request context by the time any urls are generated in the code.

I had to work around this with a 2 step process.

in my gunicorn.conf.py I put the following to always inject the header if we're deployed to production

def pre_request(worker, req):
    req.headers.append(tuple(['X-FORWARDED-PROTO', 'https']))
    for header in req.headers:
        worker.log.info(header)

then in my app initialization I put the following so the application from there on out things the request had an https scheme.

def change_url():
    base_url = request.base_url
    if 'X-FORWARDED-PROTO' in request.headers and request.path not in ('/up', '/up/'):
        request.base_url = base_url.replace('http://', 'https://')

app.before_request(change_url)
mrevutskyi commented 1 year ago

I see makes sense. Would it work for you if I change links to the same format as self link (and resource links), e.g. simply

"links": {
    "first": "/api/rest/v1/<redacted>?page[size]=10&page[number]=1",
    "last": "/api/rest/v1/<redacted>?page[size]=10&page[number]=1",
    "next": null,
    "prev": null,
    "self": "/api/rest/v1/<redacted>"
  },
  "meta": {
    "total": 2
  }

?

mrevutskyi commented 1 year ago

@gnydick please let me know if the proposed solution works for you or if you need a full URL for some reason

mrevutskyi commented 1 year ago

Version 2.5.0 now uses X-Forwarded-Host and X-Forwarded-Proto when preset. Please me know if this is still an issue