dotX12 / fastapi-gateway

⚙️ FastAPI gateway for microservices.
MIT License
111 stars 27 forks source link

Unprocessable Entity with OAuth2PasswordRequestForm #3

Closed BrandeeDev closed 2 years ago

BrandeeDev commented 2 years ago

Hello I am using the gateway as reverse proxy for all my backend. I have an issue to process OAuth2PasswordRequestForm. The microservice throw this error " 172.19.0.1:59754 - "POST /api/login HTTP/1.1" 422 Unprocessable Entity".

{"detail":[{"loc":["body","username"],"msg":"field required","type":"value_error.missing"},{"loc":["body","password"],"msg":"field required","type":"value_error.missing"}]}

The method definition of my micoservice is defined by this code:


async def login(self, user_signin: OAuth2PasswordRequestForm = Depends(),
                    session: AsyncSession = Depends(get_session)):

The gateway definition is :

@staticmethod
    @route(
        request_method=api_gateway_account_settings_router.post,
        service_url=SERVICE_URL,
        gateway_path='/api/login',
        service_path='/api/login',
        query_params=[],
        form_params=['user_signin'],
        status_code=status.HTTP_200_OK,
        tags=['account_settings'],
        response_model=Any
    )
    @pm_logger
    async def login(request: Request, response: Response, user_signin: OAuth2PasswordRequestForm = Depends()):
        pass
reuseable_oauth = OAuth2PasswordBearer(
    tokenUrl="/api/login",
    scheme_name="JWT"
)

image

BrandeeDev commented 2 years ago

Hello again; I added a middleware inside my microservice container and I have notice that request coming from the gateway does not have a FormData object

 REQUEST HEADERS : Headers({'host': 'identity.XXXXX.XXX.adroot:6800', 'connection': 'keep-alive', 'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"', 'sec-ch-ua-mobile': '
?0', 'authorization': 'Basic Og==', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', 'accept': 'application/json, text/plain, */*', 'x-requested-with': 
'XMLHttpRequest', 'sec-ch-ua-platform': '"Windows"', 'origin': 'http://127.0.0.1:8000', 'sec-fetch-site': 'same-origin', 'sec-fetch-mode': 'cors', 'sec-fetch-dest': 'empty', 'referer': 'http://127.0.0.1:8000/documentation', 'accept-
language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', 'gateway_host': '127.0.0.1:8000', 'accept-encoding': 'gzip, deflate', 'content-length': '0', 'content-type': 'application/x-www-form-urlencoded'}) | FormData([]) |

Request that is coming directlty from the microservice have a valid FormData

 |  REQUEST HEADERS : Headers({'host': 'identity.XXXXXX.adroot:6800', 'connection': 'keep-alive', 'content-length': '77', 'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"
', 'sec-ch-ua-mobile': '?0', 'authorization': 'Basic Og==', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', 'content-type': 'application/x-www-form-url
encoded', 'accept': 'application/json, text/plain, */*', 'x-requested-with': 'XMLHttpRequest', 'sec-ch-ua-platform': '"Windows"', 'origin': 'https://identity.XXXXXXXX.adroot:6800', 'sec-fetch-site': 'same-origin', 'sec-fetch-mode'
: 'cors', 'sec-fetch-dest': 'empty', 'referer': 'https://identity.XXXXXX.adroot:6800/documentation', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7'}) | FormData([('grant_type', '
password'), ('username', 'test@test.com'), ('password', 'TEST')]) |

So as it looks the gateway does not seem to send the formdata as expected.. Is there something that could help?

dotX12 commented 2 years ago

@BrandeeDev, hello! Thanks for the feedback, yes the dependencies are not unpacked. I'll upload a fix within the hour.

BrandeeDev commented 2 years ago

Hello @dotX12. Thanks for you reply. I have another issue related to this part of code:

with async_timeout.timeout(delay=timeout):
        async with aiohttp.ClientSession(headers=headers) as session:
            async with session.request(
                    method=method, url=url, params=query, data=data) as response:
                response_json = await response.json()
                decoded_json = decode_json(data=response_json)
                return decoded_json, response.status, response.headers

I think makeRequest handle only json response. Whenever I return a StreamingResponse, the api throw exception with message: 'Server error'

For my issue I unpacked the object this way:

@staticmethod
    @route(
        request_method=api_gateway_account_settings_router.post,
        service_url=SERVICE_URL,
        gateway_path='/api/login',
        service_path='/api/login',
        query_params=[],
        form_params=['username', 'password'],
        status_code=status.HTTP_200_OK,
        tags=['account_settings'],
        response_model=Any
    )
    @pm_logger
    async def login(request: Request, response: Response, username=Form(...), password=Form(...)):
        pass

Would like to create a new ticket for handling FileResponse, StreamingResponse...

dotX12 commented 2 years ago

image Fix. @BrandeeDev, I'll upload it to pypi in a couple of minutes...

dotX12 commented 2 years ago

Hello @dotX12. Thanks for you reply. I have another issue related to this part of code:

with async_timeout.timeout(delay=timeout):
        async with aiohttp.ClientSession(headers=headers) as session:
            async with session.request(
                    method=method, url=url, params=query, data=data) as response:
                response_json = await response.json()
                decoded_json = decode_json(data=response_json)
                return decoded_json, response.status, response.headers

I think makeRequest handle only json response. Whenever I return a StreamingResponse, the api throw exception with message: 'Server error'

For my issue I unpacked the object this way:

@staticmethod
   @route(
       request_method=api_gateway_account_settings_router.post,
       service_url=SERVICE_URL,
       gateway_path='/api/login',
       service_path='/api/login',
       query_params=[],
       form_params=['username', 'password'],
       status_code=status.HTTP_200_OK,
       tags=['account_settings'],
       response_model=Any
   )
   @pm_logger
   async def login(request: Request, response: Response, username=Form(...), password=Form(...)):
       pass

Would like to create a new ticket for handling FileResponse, StreamingResponse...

Create another issue please.

dotX12 commented 2 years ago

@BrandeeDev, please test the update, is everything working stably?

BrandeeDev commented 2 years ago

Thanks I will check within 30mn

BrandeeDev commented 2 years ago

It is Okay. Everything works as expected. I have create a new issue for processing different response. Thanks

BrandeeDev commented 2 years ago

It is Okay. Everything works as expected. I have create a new issue for processing different response. Thanks