|
35 | 35 |
|
36 | 36 | from ..authentication import Scopes |
37 | 37 | from ..eventsub.subscriptions import _SUB_MAPPING |
| 38 | +from ..exceptions import HTTPException |
38 | 39 | from ..models.eventsub_ import SubscriptionRevoked, create_event_instance |
39 | 40 | from ..types_.eventsub import EventSubHeaders |
40 | 41 | from ..utils import _from_json, parse_timestamp # type: ignore |
41 | | -from .utils import MESSAGE_TYPES, BaseAdapter, verify_message |
| 42 | +from .utils import MESSAGE_TYPES, BaseAdapter, FetchTokenPayload, verify_message |
42 | 43 |
|
43 | 44 |
|
44 | 45 | if TYPE_CHECKING: |
@@ -271,30 +272,85 @@ async def eventsub_callback(self, request: web.Request) -> web.Response: |
271 | 272 |
|
272 | 273 | return web.Response(status=204) |
273 | 274 |
|
274 | | - async def fetch_token(self, request: web.Request) -> web.Response: |
| 275 | + async def fetch_token(self, request: web.Request) -> FetchTokenPayload: |
| 276 | + """This method handles sending the provided code to Twitch to receive a User Access and Refresh Token pair, and |
| 277 | + later, if successful, dispatches :func:`~twitchio.event_oauth_authorized`. |
| 278 | +
|
| 279 | + To call this coroutine you should pass the request received in the :meth:`oauth_callback`. This method is called by |
| 280 | + default, however when overriding :meth:`oauth_callback` you should always call this method. |
| 281 | +
|
| 282 | + Parameters |
| 283 | + ---------- |
| 284 | + request: aiohttp.web.Request |
| 285 | + The request received in :meth:`oauth_callback`. |
| 286 | +
|
| 287 | + Returns |
| 288 | + ------- |
| 289 | + FetchTokenPayload |
| 290 | + The payload containing various information about the authentication request to Twitch. |
| 291 | + """ |
275 | 292 | if "code" not in request.query: |
276 | | - return web.Response(status=400) |
| 293 | + return FetchTokenPayload(400, response=web.Response(status=400, text="No 'code' parameter provided.")) |
277 | 294 |
|
278 | 295 | try: |
279 | | - payload: UserTokenPayload = await self.client._http.user_access_token( |
| 296 | + resp: UserTokenPayload = await self.client._http.user_access_token( |
280 | 297 | request.query["code"], |
281 | 298 | redirect_uri=self.redirect_url, |
282 | 299 | ) |
283 | | - except Exception as e: |
| 300 | + except HTTPException as e: |
284 | 301 | logger.error("Exception raised while fetching Token in <%s>: %s", self.__class__.__qualname__, e) |
285 | | - return web.Response(status=500) |
| 302 | + status: int = e.status |
| 303 | + return FetchTokenPayload(status=status, response=web.Response(status=status), exception=e) |
286 | 304 |
|
287 | | - self.client.dispatch(event="oauth_authorized", payload=payload) |
288 | | - return web.Response(body="Success. You can leave this page.", status=200) |
| 305 | + self.client.dispatch(event="oauth_authorized", payload=resp) |
| 306 | + return FetchTokenPayload( |
| 307 | + status=20, |
| 308 | + response=web.Response(body="Success. You can leave this page.", status=200), |
| 309 | + payload=resp, |
| 310 | + ) |
289 | 311 |
|
290 | 312 | async def oauth_callback(self, request: web.Request) -> web.Response: |
| 313 | + """Default route callback for the OAuth Authentication redirect URL. |
| 314 | +
|
| 315 | + You can override this method to alter the responses sent to the user. |
| 316 | +
|
| 317 | + This callback should always return a valid response. See: `aiohttp docs <https://docs.aiohttp.org/en/stable/web.html>`_ |
| 318 | + for more information. |
| 319 | +
|
| 320 | + .. important:: |
| 321 | +
|
| 322 | + You should always call :meth:`.fetch_token` when overriding this method. |
| 323 | +
|
| 324 | + Parameters |
| 325 | + ---------- |
| 326 | + request: aiohttp.web.Request |
| 327 | + The original request received via aiohttp. |
| 328 | +
|
| 329 | + Examples |
| 330 | + -------- |
| 331 | +
|
| 332 | + .. code:: python3 |
| 333 | +
|
| 334 | + async def oauth_callback(self, request: web.Request) -> web.Response: |
| 335 | + payload: FetchTokenPayload = await self.fetch_token(request) |
| 336 | +
|
| 337 | + # Change the default success response to no content... |
| 338 | + if payload.status == 200: |
| 339 | + return web.Response(status=204) |
| 340 | +
|
| 341 | + # Return the default error responses... |
| 342 | + return payload.response |
| 343 | + """ |
291 | 344 | logger.debug("Received OAuth callback request in <%s>.", self.oauth_callback.__qualname__) |
292 | 345 |
|
293 | | - response: web.Response = await self.fetch_token(request) |
294 | | - return response |
| 346 | + payload: FetchTokenPayload = await self.fetch_token(request) |
| 347 | + if not isinstance(payload.response, web.Response): |
| 348 | + raise ValueError(f"Responses in AiohttpAdapter should be {type(web.Response)!r} not {type(payload.response)!r}") |
| 349 | + |
| 350 | + return payload.response |
295 | 351 |
|
296 | 352 | async def oauth_redirect(self, request: web.Request) -> web.Response: |
297 | | - scopes: str | None = request.query.get("scopes", None) |
| 353 | + scopes: str | None = request.query.get("scopes", request.query.get("scope", None)) |
298 | 354 | force_verify: bool = request.query.get("force_verify", "false").lower() == "true" |
299 | 355 |
|
300 | 356 | if not scopes: |
|
0 commit comments