55use base64:: { prelude:: BASE64_URL_SAFE_NO_PAD , Engine } ;
66use chrono:: { TimeDelta , Utc } ;
77use dropshot:: {
8- http_response_temporary_redirect, Body , ClientErrorStatusCode , HttpError , HttpResponseOk ,
9- HttpResponseTemporaryRedirect , Path , Query , RequestContext , RequestInfo , TypedBody ,
8+ http_response_temporary_redirect, Body , ClientErrorStatusCode , ExclusiveExtractor , HttpError ,
9+ HttpResponseOk , HttpResponseTemporaryRedirect , Path , Query , RequestContext , RequestInfo ,
10+ SharedExtractor , TypedBody ,
1011} ;
12+ use dropshot_authorization_header:: basic:: BasicAuth ;
1113use http:: {
1214 header:: { LOCATION , SET_COOKIE } ,
1315 HeaderValue , StatusCode ,
@@ -18,7 +20,7 @@ use oauth2::{
1820 AuthorizationCode , CsrfToken , PkceCodeChallenge , PkceCodeVerifier , Scope , TokenResponse ,
1921} ;
2022use schemars:: JsonSchema ;
21- use secrecy:: SecretString ;
23+ use secrecy:: { ExposeSecret , SecretString } ;
2224use serde:: { Deserialize , Serialize } ;
2325use sha2:: { Digest , Sha256 } ;
2426use std:: { fmt:: Debug , ops:: Add } ;
@@ -121,7 +123,7 @@ where
121123{
122124 let client = ctx
123125 . oauth
124- . get_oauth_client ( & ctx. builtin_registration_user ( ) , & client_id)
126+ . get_oauth_client ( & ctx. builtin_registration_user ( ) , client_id)
125127 . await
126128 . map_err ( |err| {
127129 tracing:: error!( ?err, "Failed to lookup OAuth client" ) ;
@@ -437,8 +439,8 @@ where
437439
438440#[ derive( Debug , Deserialize , JsonSchema ) ]
439441pub struct OAuthAuthzCodeExchangeBody {
440- pub client_id : TypedUuid < OAuthClientId > ,
441- pub client_secret : OpenApiSecretString ,
442+ pub client_id : Option < TypedUuid < OAuthClientId > > ,
443+ pub client_secret : Option < OpenApiSecretString > ,
442444 pub redirect_uri : String ,
443445 pub grant_type : String ,
444446 pub code : String ,
@@ -464,6 +466,34 @@ where
464466 let ctx = rqctx. v_ctx ( ) ;
465467 let path = path. into_inner ( ) ;
466468 let body = body. into_inner ( ) ;
469+
470+ let ( client_id, client_secret) =
471+ if let ( Some ( client_id) , Some ( client_secret) ) = ( body. client_id , body. client_secret ) {
472+ Ok :: < _ , HttpError > ( ( client_id, client_secret) )
473+ } else {
474+ // Attempt to extract basic authorization credentials from the request if they were not
475+ // present in the request body
476+ let auth = <BasicAuth as SharedExtractor >:: from_request ( rqctx)
477+ . await
478+ . tap_err ( |err| {
479+ tracing:: warn!( ?err, "Failed to extract basic authentication values" ) ;
480+ } ) ;
481+ let ( client_id, client_secret) = match auth {
482+ Ok ( auth) if auth. username ( ) . is_some ( ) && auth. password ( ) . is_some ( ) => Ok ( (
483+ auth. username ( ) . unwrap ( ) . to_string ( ) ,
484+ auth. password ( ) . unwrap ( ) . to_string ( ) ,
485+ ) ) ,
486+ _ => Err ( internal_error (
487+ "Missing client id and client secret from authz code exchange" ,
488+ ) ) ,
489+ } ?;
490+
491+ Ok ( (
492+ client_id. parse ( ) . map_err ( to_internal_error) ?,
493+ OpenApiSecretString ( client_secret. into ( ) ) ,
494+ ) )
495+ } ?;
496+
467497 let provider = ctx
468498 . get_oauth_provider ( & path. provider )
469499 . await
@@ -475,8 +505,8 @@ where
475505 authorize_code_exchange (
476506 & ctx,
477507 & body. grant_type ,
478- & body . client_id ,
479- & body . client_secret . 0 ,
508+ client_id,
509+ & client_secret. 0 ,
480510 & body. redirect_uri ,
481511 )
482512 . await ?;
@@ -499,7 +529,7 @@ where
499529 // Verify that the login attempt is valid and matches the submitted client credentials
500530 verify_login_attempt (
501531 & attempt,
502- & body . client_id ,
532+ client_id,
503533 & body. redirect_uri ,
504534 body. pkce_verifier . as_deref ( ) ,
505535 ) ?;
@@ -544,7 +574,7 @@ where
544574async fn authorize_code_exchange < T > (
545575 ctx : & VContext < T > ,
546576 grant_type : & str ,
547- client_id : & TypedUuid < OAuthClientId > ,
577+ client_id : TypedUuid < OAuthClientId > ,
548578 client_secret : & SecretString ,
549579 redirect_uri : & str ,
550580) -> Result < ( ) , OAuthError >
@@ -594,11 +624,11 @@ where
594624
595625fn verify_login_attempt (
596626 attempt : & LoginAttempt ,
597- client_id : & TypedUuid < OAuthClientId > ,
627+ client_id : TypedUuid < OAuthClientId > ,
598628 redirect_uri : & str ,
599629 pkce_verifier : Option < & str > ,
600630) -> Result < ( ) , OAuthError > {
601- if attempt. client_id != * client_id {
631+ if attempt. client_id != client_id {
602632 Err ( OAuthError {
603633 error : OAuthErrorCode :: InvalidGrant ,
604634 error_description : Some ( "Invalid client id" . to_string ( ) ) ,
@@ -1238,7 +1268,7 @@ mod tests {
12381268 authorize_code_exchange(
12391269 & ctx,
12401270 "authorization_code" ,
1241- & wrong_client_id,
1271+ wrong_client_id,
12421272 & client_secret,
12431273 & redirect_uri,
12441274 )
@@ -1253,7 +1283,7 @@ mod tests {
12531283 authorize_code_exchange(
12541284 & ctx,
12551285 "authorization_code" ,
1256- & client_id,
1286+ client_id,
12571287 & client_secret,
12581288 "wrong-callback-destination" ,
12591289 )
@@ -1268,7 +1298,7 @@ mod tests {
12681298 authorize_code_exchange(
12691299 & ctx,
12701300 "authorization_code" ,
1271- & client_id,
1301+ client_id,
12721302 & client_secret,
12731303 & redirect_uri,
12741304 )
@@ -1299,7 +1329,7 @@ mod tests {
12991329 authorize_code_exchange(
13001330 & ctx,
13011331 "not_authorization_code" ,
1302- & client_id,
1332+ client_id,
13031333 & client_secret,
13041334 & redirect_uri
13051335 )
@@ -1313,7 +1343,7 @@ mod tests {
13131343 authorize_code_exchange(
13141344 & ctx,
13151345 "authorization_code" ,
1316- & client_id,
1346+ client_id,
13171347 & client_secret,
13181348 & redirect_uri
13191349 )
@@ -1351,7 +1381,7 @@ mod tests {
13511381 authorize_code_exchange(
13521382 & ctx,
13531383 "authorization_code" ,
1354- & client_id,
1384+ client_id,
13551385 & "too-short" . to_string( ) . into( ) ,
13561386 & redirect_uri
13571387 )
@@ -1365,7 +1395,7 @@ mod tests {
13651395 authorize_code_exchange(
13661396 & ctx,
13671397 "authorization_code" ,
1368- & client_id,
1398+ client_id,
13691399 & invalid_secret. into( ) ,
13701400 & redirect_uri
13711401 )
@@ -1379,7 +1409,7 @@ mod tests {
13791409 authorize_code_exchange(
13801410 & ctx,
13811411 "authorization_code" ,
1382- & client_id,
1412+ client_id,
13831413 & client_secret,
13841414 & redirect_uri
13851415 )
@@ -1425,7 +1455,7 @@ mod tests {
14251455 } ,
14261456 verify_login_attempt(
14271457 & bad_client_id,
1428- & attempt. client_id,
1458+ attempt. client_id,
14291459 & attempt. redirect_uri,
14301460 Some ( verifier. secret( ) . as_str( ) ) ,
14311461 )
@@ -1446,7 +1476,7 @@ mod tests {
14461476 } ,
14471477 verify_login_attempt(
14481478 & bad_redirect_uri,
1449- & attempt. client_id,
1479+ attempt. client_id,
14501480 & attempt. redirect_uri,
14511481 Some ( verifier. secret( ) . as_str( ) ) ,
14521482 )
@@ -1467,7 +1497,7 @@ mod tests {
14671497 } ,
14681498 verify_login_attempt(
14691499 & unconfirmed_state,
1470- & attempt. client_id,
1500+ attempt. client_id,
14711501 & attempt. redirect_uri,
14721502 Some ( verifier. secret( ) . as_str( ) ) ,
14731503 )
@@ -1488,7 +1518,7 @@ mod tests {
14881518 } ,
14891519 verify_login_attempt(
14901520 & already_used_state,
1491- & attempt. client_id,
1521+ attempt. client_id,
14921522 & attempt. redirect_uri,
14931523 Some ( verifier. secret( ) . as_str( ) ) ,
14941524 )
@@ -1509,7 +1539,7 @@ mod tests {
15091539 } ,
15101540 verify_login_attempt(
15111541 & failed_state,
1512- & attempt. client_id,
1542+ attempt. client_id,
15131543 & attempt. redirect_uri,
15141544 Some ( verifier. secret( ) . as_str( ) ) ,
15151545 )
@@ -1530,7 +1560,7 @@ mod tests {
15301560 } ,
15311561 verify_login_attempt(
15321562 & expired,
1533- & attempt. client_id,
1563+ attempt. client_id,
15341564 & attempt. redirect_uri,
15351565 Some ( verifier. secret( ) . as_str( ) ) ,
15361566 )
@@ -1548,7 +1578,7 @@ mod tests {
15481578 } ,
15491579 verify_login_attempt(
15501580 & missing_pkce,
1551- & attempt. client_id,
1581+ attempt. client_id,
15521582 & attempt. redirect_uri,
15531583 None ,
15541584 )
@@ -1569,7 +1599,7 @@ mod tests {
15691599 } ,
15701600 verify_login_attempt(
15711601 & invalid_pkce,
1572- & attempt. client_id,
1602+ attempt. client_id,
15731603 & attempt. redirect_uri,
15741604 Some ( verifier. secret( ) . as_str( ) ) ,
15751605 )
@@ -1580,7 +1610,7 @@ mod tests {
15801610 ( ) ,
15811611 verify_login_attempt(
15821612 & attempt,
1583- & attempt. client_id,
1613+ attempt. client_id,
15841614 & attempt. redirect_uri,
15851615 Some ( verifier. secret( ) . as_str( ) ) ,
15861616 )
0 commit comments