44
55use base64:: { prelude:: BASE64_URL_SAFE_NO_PAD , Engine } ;
66use chrono:: { TimeDelta , Utc } ;
7+ use cookie:: { Cookie , SameSite } ;
78use dropshot:: {
8- http_response_temporary_redirect, Body , ClientErrorStatusCode , HttpError , HttpResponseOk ,
9- HttpResponseTemporaryRedirect , Path , Query , RequestContext , RequestInfo , SharedExtractor ,
10- TypedBody ,
9+ Body , ClientErrorStatusCode , HttpError , HttpResponseOk , Path , Query , RequestContext ,
10+ RequestInfo , SharedExtractor , TypedBody ,
1111} ;
1212use dropshot_authorization_header:: basic:: BasicAuth ;
1313use http:: {
@@ -250,8 +250,13 @@ fn oauth_redirect_response(
250250
251251 // Create an attempt cookie header for storing the login attempt. This also acts as our csrf
252252 // check
253- let login_cookie = HeaderValue :: from_str ( & format ! ( "{}={}" , LOGIN_ATTEMPT_COOKIE , attempt. id) )
254- . map_err ( to_internal_error) ?;
253+ let mut cookie = Cookie :: new ( LOGIN_ATTEMPT_COOKIE , attempt. id . to_string ( ) ) ;
254+ cookie. set_http_only ( true ) ;
255+ cookie. set_same_site ( SameSite :: Lax ) ;
256+ cookie. set_secure ( public_url. starts_with ( "https" ) ) ;
257+ cookie. set_max_age ( cookie:: time:: Duration :: seconds ( 600 ) ) ;
258+
259+ let login_cookie = HeaderValue :: from_str ( & cookie. to_string ( ) ) . map_err ( to_internal_error) ?;
255260
256261 // Generate the url to the remote provider that the user will be redirected to
257262 let mut authz_url = client
@@ -342,7 +347,7 @@ pub async fn authz_code_callback_op<T>(
342347 rqctx : & RequestContext < impl ApiContext < AppPermissions = T > > ,
343348 path : Path < OAuthProviderNameParam > ,
344349 query : Query < OAuthAuthzCodeReturnQuery > ,
345- ) -> Result < HttpResponseTemporaryRedirect , HttpError >
350+ ) -> Result < Response < Body > , HttpError >
346351where
347352 T : VAppPermission + PermissionStorage ,
348353{
@@ -359,9 +364,25 @@ where
359364 // Verify and extract the attempt id before performing any work
360365 let attempt_id = verify_csrf ( & rqctx. request , & query) ?;
361366
362- http_response_temporary_redirect (
363- authz_code_callback_op_inner ( ctx, & attempt_id, query. code , query. error ) . await ?,
364- )
367+ // Clear the login attempt cookie
368+ let mut cookie = Cookie :: new ( LOGIN_ATTEMPT_COOKIE , "" ) ;
369+ cookie. set_http_only ( true ) ;
370+ cookie. set_same_site ( SameSite :: Lax ) ;
371+ cookie. set_secure ( ctx. public_url ( ) . starts_with ( "https" ) ) ;
372+ cookie. set_max_age ( cookie:: time:: Duration :: seconds ( 0 ) ) ;
373+ let login_cookie = HeaderValue :: from_str ( & cookie. to_string ( ) ) . map_err ( to_internal_error) ?;
374+
375+ Ok ( Response :: builder ( )
376+ . status ( StatusCode :: TEMPORARY_REDIRECT )
377+ . header ( SET_COOKIE , login_cookie)
378+ . header (
379+ LOCATION ,
380+ HeaderValue :: from_str (
381+ & authz_code_callback_op_inner ( ctx, & attempt_id, query. code , query. error ) . await ?,
382+ )
383+ . map_err ( to_internal_error) ?,
384+ )
385+ . body ( Body :: empty ( ) ) ?)
365386}
366387
367388pub async fn authz_code_callback_op_inner < T > (
@@ -930,7 +951,11 @@ mod tests {
930951 . unwrap( )
931952 ) ;
932953 assert_eq ! (
933- attempt. id. to_string( ) . as_str( ) ,
954+ format!(
955+ "{}; HttpOnly; SameSite=Lax; Secure; Max-Age=600" ,
956+ attempt. id
957+ )
958+ . as_str( ) ,
934959 String :: from_utf8(
935960 response
936961 . headers( )
0 commit comments