[docs]classAuthorizationCodeGrant(OAuth2Grant):""" OAuth 2.0 Authorization Code grant. Use a browser login to request an authorization code, which is then used to request an access token. https://datatracker.ietf.org/doc/html/rfc6749#section-4.1 """timeout:int=300def__init__(self,token_url:Union[str,URL],authorization_url:Union[str,URL],client_id:str,token:Optional[dict]=None,pkce:bool=False,_web_server_port:Optional[int]=None,**kwargs,):""" :param token_url: OAuth 2.0 Token URL :param authorization_url: OAuth 2.0 Authorization URL :param client_id: client identifier :param token: OAuth 2.0 Token :param pkce: use PKCE :param _web_server_port: web server port for handling redirect callback, leave empty for random available port """super().__init__(token_url,token,**kwargs)self.authorization_url=URL(authorization_url)self.client_id=client_idself.pkce=PKCE()ifpkceelseNoneself._web_server_port=_web_server_portasyncdef_fetch_token(self)->Token:time_start=time.time()asyncwith_web_server(port=self._web_server_portor0)as(socket_info,state):redirect_uri=URL.build(scheme="http",host="localhost",port=socket_info[1],path="/callback")ifself.pkce:authorization_request=AuthorizationRequestPKCE(client_id=self.client_id,redirect_uri=str(redirect_uri),code_challenge=self.pkce.code_challenge,code_challenge_method=self.pkce.code_challenge_method,**self.kwargs,)else:authorization_request=AuthorizationRequest(client_id=self.client_id,redirect_uri=str(redirect_uri),**self.kwargs,)full_authz_url=self.authorization_url%authorization_request.model_dump(exclude_none=True)webbrowser.open(str(full_authz_url))whilenotstateandnottime.time()>time_start+self.timeout:awaitasyncio.sleep(1)ifnotstate:raiseAuthError("Authorization timed out.")authorization=AuthorizationResponse.model_validate(state)token_request=AuthorizationCodeAccessTokenRequest(code=authorization.code,redirect_uri=authorization_request.redirect_uri,client_id=self.client_id,)ifself.pkce:token_request.code_verifier=str(self.pkce.code_verifier,encoding="utf-8")returnawaitself.execute_token_request(token_request)
@contextlib.asynccontextmanagerasyncdef_web_server(port:int):""" Launch a web server to handle the redirect after the authorization request. Stores the authorization code in the state. """state=dict()asyncdef_request_handler(request:aiohttp.web.Request)->aiohttp.web.Response:ifrequest.path=="/callback":state.update(request.query)returnaiohttp.web.Response(text="Authorization successful! You can close this window now.",status=200,)else:returnaiohttp.web.Response(status=404)server=aiohttp.web.Server(_request_handler)runner=aiohttp.web.ServerRunner(server)awaitrunner.setup()site=aiohttp.web.TCPSite(runner,port=port)awaitsite.start()socket_info=site._server.sockets[0].getsockname()yieldsocket_info,stateawaitsite.stop()