Skip to content

fix(event_handler): propagate UnauthorizedException and await async class-based middlewares - #8633

Merged
leandrodamascena merged 6 commits into
aws-powertools:developfrom
SuellenQwQ:fix/appsync-unauthorized-async-middleware
Oct 29, 2026
Merged

leandrodamascena merged 6 commits into
aws-powertools:developfrom
SuellenQwQ:fix/appsync-unauthorized-async-middleware

Conversation

@SuellenQwQ

@SuellenQwQ SuellenQwQ commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: closes #8582

Summary

Changes

  • AppSyncEventsResolver now re-raises UnauthorizedException raised by a per-event (aggregate=False) publish resolver, in both the sync and the async path, instead of converting it into a per-event error payload.
  • AsyncMiddlewareFrame now awaits class-based middlewares whose handler is a coroutine function, so BaseMiddlewareHandler subclasses keep working with app.resolve_async().
  • wrap_middleware_async returns the response of a middleware that short-circuits without calling the next one, instead of raising IndexError.

User experience

A resolver that denies access with UnauthorizedException makes AppSync deny the whole operation regardless of aggregate, which matches the intent of the exception and what the subscribe path already does. Middlewares written as BaseMiddlewareHandler subclasses with async def handler now run under app.resolve_async() instead of returning an un-awaited coroutine.

This covers items 1 and 2 of the issue. Item 3 (HttpResolverLocal as a Lambda handler) is a change to the ASGI/Lambda dispatch in __call__ and is left out of this PR. If you would rather keep #8582 open for item 3, say so and I will drop the closes keyword and open a separate issue for it.

Checklist

  • Added tests covering the new behaviour
  • Ran pytest tests/functional/event_handler/required_dependencies/appsync tests/functional/event_handler/required_dependencies/test_async_middleware_frame.py

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…pSync Events resolvers

The per-event resolvers (`aggregate=False`) catch every exception and turn it into a
per-event error payload. `UnauthorizedException` is a control-flow signal rather than a
payload: re-raise it so AppSync denies the whole operation.

Both the sync and the async paths now behave like the aggregate and subscribe resolvers.

Signed-off-by: SuellenQAQ <sulun6693485@163.com>
`AsyncMiddlewareFrame` only awaited middlewares that are themselves coroutine
functions. A `BaseMiddlewareHandler` subclass with an `async def handler` is not a
coroutine function, so the frame returned the coroutine and the response leaked
through un-awaited.

Detect the class-based `handler` too and await whatever the middleware returns.

Also return a short-circuiting middleware's own response in `wrap_middleware_async`
instead of raising IndexError when the next middleware was never called.

Signed-off-by: SuellenQAQ <sulun6693485@163.com>
…rcuiting

Added tests for class-based async middleware and sync short-circuit behavior.

Signed-off-by: SuellenQAQ <sulun6693485@163.com>
…vent resolvers

Add tests for handling unauthorized exceptions in AppSync events.

Signed-off-by: SuellenQAQ <sulun6693485@163.com>

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SuellenQwQ, both fixes are right. I deployed this behind a real AppSync Events API with a Lambda data source: a publish handler raising UnauthorizedException with the default aggregate=False now makes AppSync answer 401 with its own generic message, for sync and async handlers, where develop returned 200 with the exception text in a per-event CustomError. The class-based async middleware works under resolve_async, including the short-circuit cases.

Your branch imported two helpers from api_gateway that only reached develop after you branched, so on its own it failed 121 tests and conflicted on async_utils.py. I merged develop into it keeping your version of the file, which already accounted for them, and everything is green now. Nothing for you to do.

Approving. This goes out in 3.39.0, so it will sit here for a while before I merge it. #8634 covers item 3 of the issue, so keeping closes here is fine. You'll be credited in the release notes.

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.99%. Comparing base (272a612) to head (adf0eb1).

Files with missing lines Patch % Lines
...owertools/event_handler/middlewares/async_utils.py 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #8633   +/-   ##
========================================
  Coverage    96.98%   96.99%           
========================================
  Files          321      321           
  Lines        16306    16318   +12     
  Branches      1466     1470    +4     
========================================
+ Hits         15815    15827   +12     
+ Misses         351      350    -1     
- Partials       140      141    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leandrodamascena
leandrodamascena merged commit f5c304a into aws-powertools:develop Oct 29, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event_handlers size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tests

Projects

None yet

2 participants