@@ -113,6 +113,148 @@ def response(self, obj: TokenSuccessResponse | TokenErrorResponse):
113113 },
114114 )
115115
116+ async def _handle_authorization_code (
117+ self , client_info : Any , token_request : AuthorizationCodeRequest
118+ ) -> TokenSuccessResponse | TokenErrorResponse :
119+ auth_code = await self .provider .load_authorization_code (client_info , token_request .code )
120+ if auth_code is None or auth_code .client_id != token_request .client_id :
121+ # if code belongs to different client, pretend it doesn't exist
122+ return TokenErrorResponse (
123+ error = "invalid_grant" ,
124+ error_description = "authorization code does not exist" ,
125+ )
126+
127+ # make auth codes expire after a deadline
128+ # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5
129+ if auth_code .expires_at < time .time ():
130+ return TokenErrorResponse (
131+ error = "invalid_grant" ,
132+ error_description = "authorization code has expired" ,
133+ )
134+
135+ # verify redirect_uri doesn't change between /authorize and /tokens
136+ # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
137+ if auth_code .redirect_uri_provided_explicitly :
138+ authorize_request_redirect_uri = auth_code .redirect_uri
139+ else :
140+ authorize_request_redirect_uri = None
141+
142+ # Convert both sides to strings for comparison to handle AnyUrl vs string issues
143+ token_redirect_str = str (token_request .redirect_uri ) if token_request .redirect_uri is not None else None
144+ auth_redirect_str = (
145+ str (authorize_request_redirect_uri ) if authorize_request_redirect_uri is not None else None
146+ )
147+
148+ if token_redirect_str != auth_redirect_str :
149+ return TokenErrorResponse (
150+ error = "invalid_request" ,
151+ error_description = ("redirect_uri did not match the one used when creating auth code" ),
152+ )
153+
154+ # Verify PKCE code verifier
155+ sha256 = hashlib .sha256 (token_request .code_verifier .encode ()).digest ()
156+ hashed_code_verifier = base64 .urlsafe_b64encode (sha256 ).decode ().rstrip ("=" )
157+
158+ if hashed_code_verifier != auth_code .code_challenge :
159+ # see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
160+ return TokenErrorResponse (
161+ error = "invalid_grant" ,
162+ error_description = "incorrect code_verifier" ,
163+ )
164+
165+ try :
166+ # Exchange authorization code for tokens
167+ tokens = await self .provider .exchange_authorization_code (client_info , auth_code )
168+ except TokenError as e :
169+ return TokenErrorResponse (
170+ error = e .error ,
171+ error_description = e .error_description ,
172+ )
173+
174+ return TokenSuccessResponse (root = tokens )
175+
176+ async def _handle_client_credentials (
177+ self , client_info : Any , token_request : ClientCredentialsRequest
178+ ) -> TokenSuccessResponse | TokenErrorResponse :
179+ scopes = (
180+ token_request .scope .split (" " )
181+ if token_request .scope
182+ else client_info .scope .split (" " )
183+ if client_info .scope
184+ else []
185+ )
186+ try :
187+ tokens = await self .provider .exchange_client_credentials (client_info , scopes )
188+ except TokenError as e :
189+ return TokenErrorResponse (
190+ error = e .error ,
191+ error_description = e .error_description ,
192+ )
193+
194+ return TokenSuccessResponse (root = tokens )
195+
196+ async def _handle_token_exchange (
197+ self , client_info : Any , token_request : TokenExchangeRequest
198+ ) -> TokenSuccessResponse | TokenErrorResponse :
199+ scopes = token_request .scope .split (" " ) if token_request .scope else []
200+ try :
201+ tokens = await self .provider .exchange_token (
202+ client_info ,
203+ token_request .subject_token ,
204+ token_request .subject_token_type ,
205+ token_request .actor_token ,
206+ token_request .actor_token_type ,
207+ scopes ,
208+ token_request .audience ,
209+ token_request .resource ,
210+ )
211+ except TokenError as e :
212+ return TokenErrorResponse (
213+ error = e .error ,
214+ error_description = e .error_description ,
215+ )
216+
217+ return TokenSuccessResponse (root = tokens )
218+
219+ async def _handle_refresh_token (
220+ self , client_info : Any , token_request : RefreshTokenRequest
221+ ) -> TokenSuccessResponse | TokenErrorResponse :
222+ refresh_token = await self .provider .load_refresh_token (client_info , token_request .refresh_token )
223+ if refresh_token is None or refresh_token .client_id != token_request .client_id :
224+ # if token belongs to a different client, pretend it doesn't exist
225+ return TokenErrorResponse (
226+ error = "invalid_grant" ,
227+ error_description = "refresh token does not exist" ,
228+ )
229+
230+ if refresh_token .expires_at and refresh_token .expires_at < time .time ():
231+ # if the refresh token has expired, pretend it doesn't exist
232+ return TokenErrorResponse (
233+ error = "invalid_grant" ,
234+ error_description = "refresh token has expired" ,
235+ )
236+
237+ # Parse scopes if provided
238+ scopes = token_request .scope .split (" " ) if token_request .scope else refresh_token .scopes
239+
240+ for scope in scopes :
241+ if scope not in refresh_token .scopes :
242+ return TokenErrorResponse (
243+ error = "invalid_scope" ,
244+ error_description = (f"cannot request scope `{ scope } ` not provided by refresh token" ),
245+ )
246+
247+ try :
248+ # Exchange refresh token for new tokens
249+ tokens = await self .provider .exchange_refresh_token (client_info , refresh_token , scopes )
250+ except TokenError as e :
251+ return TokenErrorResponse (
252+ error = e .error ,
253+ error_description = e .error_description ,
254+ )
255+
256+ return TokenSuccessResponse (root = tokens )
257+
116258 async def handle (self , request : Request ):
117259 try :
118260 form_data = await request .form ()
@@ -146,155 +288,17 @@ async def handle(self, request: Request):
146288 )
147289 )
148290
149- tokens : OAuthToken
150-
151291 match token_request :
152292 case AuthorizationCodeRequest ():
153- auth_code = await self .provider .load_authorization_code (client_info , token_request .code )
154- if auth_code is None or auth_code .client_id != token_request .client_id :
155- # if code belongs to different client, pretend it doesn't exist
156- return self .response (
157- TokenErrorResponse (
158- error = "invalid_grant" ,
159- error_description = "authorization code does not exist" ,
160- )
161- )
162-
163- # make auth codes expire after a deadline
164- # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5
165- if auth_code .expires_at < time .time ():
166- return self .response (
167- TokenErrorResponse (
168- error = "invalid_grant" ,
169- error_description = "authorization code has expired" ,
170- )
171- )
172-
173- # verify redirect_uri doesn't change between /authorize and /tokens
174- # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
175- if auth_code .redirect_uri_provided_explicitly :
176- authorize_request_redirect_uri = auth_code .redirect_uri
177- else :
178- authorize_request_redirect_uri = None
179-
180- # Convert both sides to strings for comparison to handle AnyUrl vs string issues
181- token_redirect_str = str (token_request .redirect_uri ) if token_request .redirect_uri is not None else None
182- auth_redirect_str = (
183- str (authorize_request_redirect_uri ) if authorize_request_redirect_uri is not None else None
184- )
185-
186- if token_redirect_str != auth_redirect_str :
187- return self .response (
188- TokenErrorResponse (
189- error = "invalid_request" ,
190- error_description = ("redirect_uri did not match the one used when creating auth code" ),
191- )
192- )
193-
194- # Verify PKCE code verifier
195- sha256 = hashlib .sha256 (token_request .code_verifier .encode ()).digest ()
196- hashed_code_verifier = base64 .urlsafe_b64encode (sha256 ).decode ().rstrip ("=" )
197-
198- if hashed_code_verifier != auth_code .code_challenge :
199- # see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
200- return self .response (
201- TokenErrorResponse (
202- error = "invalid_grant" ,
203- error_description = "incorrect code_verifier" ,
204- )
205- )
206-
207- try :
208- # Exchange authorization code for tokens
209- tokens = await self .provider .exchange_authorization_code (client_info , auth_code )
210- except TokenError as e :
211- return self .response (
212- TokenErrorResponse (
213- error = e .error ,
214- error_description = e .error_description ,
215- )
216- )
293+ result = await self ._handle_authorization_code (client_info , token_request )
217294
218295 case ClientCredentialsRequest ():
219- scopes = (
220- token_request .scope .split (" " )
221- if token_request .scope
222- else client_info .scope .split (" " )
223- if client_info .scope
224- else []
225- )
226- try :
227- tokens = await self .provider .exchange_client_credentials (client_info , scopes )
228- except TokenError as e :
229- return self .response (
230- TokenErrorResponse (
231- error = e .error ,
232- error_description = e .error_description ,
233- )
234- )
296+ result = await self ._handle_client_credentials (client_info , token_request )
235297
236298 case TokenExchangeRequest ():
237- scopes = token_request .scope .split (" " ) if token_request .scope else []
238- try :
239- tokens = await self .provider .exchange_token (
240- client_info ,
241- token_request .subject_token ,
242- token_request .subject_token_type ,
243- token_request .actor_token ,
244- token_request .actor_token_type ,
245- scopes ,
246- token_request .audience ,
247- token_request .resource ,
248- )
249- except TokenError as e :
250- return self .response (
251- TokenErrorResponse (
252- error = e .error ,
253- error_description = e .error_description ,
254- )
255- )
299+ result = await self ._handle_token_exchange (client_info , token_request )
256300
257301 case RefreshTokenRequest ():
258- refresh_token = await self .provider .load_refresh_token (client_info , token_request .refresh_token )
259- if refresh_token is None or refresh_token .client_id != token_request .client_id :
260- # if token belongs to a different client, pretend it doesn't exist
261- return self .response (
262- TokenErrorResponse (
263- error = "invalid_grant" ,
264- error_description = "refresh token does not exist" ,
265- )
266- )
267-
268- if refresh_token .expires_at and refresh_token .expires_at < time .time ():
269- # if the refresh token has expired, pretend it doesn't exist
270- return self .response (
271- TokenErrorResponse (
272- error = "invalid_grant" ,
273- error_description = "refresh token has expired" ,
274- )
275- )
276-
277- # Parse scopes if provided
278- scopes = token_request .scope .split (" " ) if token_request .scope else refresh_token .scopes
279-
280- for scope in scopes :
281- if scope not in refresh_token .scopes :
282- return self .response (
283- TokenErrorResponse (
284- error = "invalid_scope" ,
285- error_description = (f"cannot request scope `{ scope } ` not provided by refresh token" ),
286- )
287- )
288-
289- try :
290- # Exchange refresh token for new tokens
291- tokens = await self .provider .exchange_refresh_token (client_info , refresh_token , scopes )
292- except TokenError as e :
293- return self .response (
294- TokenErrorResponse (
295- error = e .error ,
296- error_description = e .error_description ,
297- )
298- )
299-
300- return self .response (TokenSuccessResponse (root = tokens ))
302+ result = await self ._handle_refresh_token (client_info , token_request )
303+
304+ return self .response (result )
0 commit comments