@@ -4,15 +4,17 @@ use crate::{app_config::AppConfig, AppState};
44use actix_web:: {
55 dev:: { forward_ready, Service , ServiceRequest , ServiceResponse , Transform } ,
66 middleware:: Condition ,
7- web, Error , HttpResponse ,
7+ web:: { self , Query } ,
8+ Error , HttpResponse ,
89} ;
910use anyhow:: { anyhow, Context } ;
1011use awc:: Client ;
1112use openidconnect:: {
1213 core:: { CoreAuthDisplay , CoreAuthenticationFlow } ,
1314 AsyncHttpClient , CsrfToken , EmptyAdditionalClaims , EndpointMaybeSet , EndpointNotSet ,
14- EndpointSet , IssuerUrl , Nonce , RedirectUrl , Scope ,
15+ EndpointSet , IssuerUrl , Nonce , OAuth2TokenResponse , RedirectUrl , Scope , TokenResponse ,
1516} ;
17+ use serde:: Deserialize ;
1618
1719use super :: http_client:: make_http_client;
1820
@@ -97,12 +99,11 @@ impl OidcMiddleware {
9799}
98100
99101async fn discover_provider_metadata (
100- app_config : & AppConfig ,
102+ http_client : & AwcHttpClient ,
101103 issuer_url : IssuerUrl ,
102104) -> anyhow:: Result < openidconnect:: core:: CoreProviderMetadata > {
103- let http_client = AwcHttpClient :: new ( app_config) ?;
104105 let provider_metadata =
105- openidconnect:: core:: CoreProviderMetadata :: discover_async ( issuer_url, & http_client) . await ?;
106+ openidconnect:: core:: CoreProviderMetadata :: discover_async ( issuer_url, http_client) . await ?;
106107 Ok ( provider_metadata)
107108}
108109
@@ -141,7 +142,8 @@ pub struct OidcService<S> {
141142 service : S ,
142143 app_state : web:: Data < AppState > ,
143144 config : Arc < OidcConfig > ,
144- client : Arc < OidcClient > ,
145+ oidc_client : Arc < OidcClient > ,
146+ http_client : Arc < AwcHttpClient > ,
145147}
146148
147149impl < S > OidcService < S > {
@@ -151,19 +153,21 @@ impl<S> OidcService<S> {
151153 config : Arc < OidcConfig > ,
152154 ) -> anyhow:: Result < Self > {
153155 let issuer_url = config. issuer_url . clone ( ) ;
154- let provider_metadata = discover_provider_metadata ( & app_state. config , issuer_url) . await ?;
156+ let http_client = AwcHttpClient :: new ( & app_state. config ) ?;
157+ let provider_metadata = discover_provider_metadata ( & http_client, issuer_url) . await ?;
155158 let client: OidcClient = make_oidc_client ( & config, provider_metadata) ?;
156159 Ok ( Self {
157160 service,
158161 app_state : web:: Data :: clone ( app_state) ,
159162 config,
160- client : Arc :: new ( client) ,
163+ oidc_client : Arc :: new ( client) ,
164+ http_client : Arc :: new ( http_client) ,
161165 } )
162166 }
163167
164168 fn build_auth_url ( & self , request : & ServiceRequest ) -> String {
165169 let ( auth_url, csrf_token, nonce) = self
166- . client
170+ . oidc_client
167171 . authorize_url (
168172 CoreAuthenticationFlow :: AuthorizationCode ,
169173 CsrfToken :: new_random,
@@ -192,54 +196,20 @@ impl<S> OidcService<S> {
192196 & self ,
193197 request : ServiceRequest ,
194198 ) -> LocalBoxFuture < Result < ServiceResponse < BoxBody > , Error > > {
195- let client = Arc :: clone ( & self . client ) ;
199+ let oidc_client = Arc :: clone ( & self . oidc_client ) ;
200+ let http_client = Arc :: clone ( & self . http_client ) ;
196201
197202 Box :: pin ( async move {
198203 let query_string = request. query_string ( ) ;
199- let result = Self :: process_oidc_callback ( & client, & query_string) . await ;
200- match result {
204+ match process_oidc_callback ( & oidc_client, & http_client, query_string) . await {
201205 Ok ( response) => Ok ( request. into_response ( response) ) ,
202206 Err ( e) => {
203- log:: error!( "Failed to process OIDC callback: {}" , e) ;
204- Ok ( request
205- . into_response ( HttpResponse :: BadRequest ( ) . body ( "Authentication failed" ) ) )
207+ log:: error!( "Failed to process OIDC callback with params {query_string}: {e}" ) ;
208+ Ok ( request. into_response ( HttpResponse :: BadRequest ( ) . body ( e. to_string ( ) ) ) )
206209 }
207210 }
208211 } )
209212 }
210-
211- async fn process_oidc_callback (
212- client : & Arc < OidcClient > ,
213- query_params : & str ,
214- ) -> anyhow:: Result < HttpResponse > {
215- let token_response = Self :: exchange_code_for_token ( client, query_params) . await ?;
216- let mut response = build_redirect_response ( format ! ( "/" ) ) ;
217- Self :: set_auth_cookie ( & mut response, & token_response) ;
218- Ok ( response)
219- }
220-
221- async fn exchange_code_for_token (
222- client : & Arc < OidcClient > ,
223- query_string : & str ,
224- ) -> anyhow:: Result < openidconnect:: core:: CoreTokenResponse > {
225- todo ! ( "Extract 'code' and 'state' from query_string" ) ;
226- todo ! ( "Verify the state matches the expected CSRF token" ) ;
227- todo ! ( "Use client.exchange_code() to get the token response" ) ;
228- }
229-
230- fn set_auth_cookie (
231- response : & mut HttpResponse ,
232- token_response : & openidconnect:: core:: CoreTokenResponse ,
233- ) {
234- // Extract token information (access token, id token, etc.)
235- todo ! ( "Extract access_token and id_token from token_response" ) ;
236-
237- // Create a secure cookie with the token information
238- todo ! ( "Create a cookie with token information" ) ;
239-
240- // Add the cookie to the response
241- todo ! ( "response.cookie() to add the cookie to the response" ) ;
242- }
243213}
244214
245215type LocalBoxFuture < T > = Pin < Box < dyn Future < Output = T > + ' static > > ;
@@ -275,6 +245,54 @@ where
275245 }
276246}
277247
248+ async fn process_oidc_callback (
249+ oidc_client : & Arc < OidcClient > ,
250+ http_client : & Arc < AwcHttpClient > ,
251+ query_string : & str ,
252+ ) -> anyhow:: Result < HttpResponse > {
253+ let params = Query :: < OidcCallbackParams > :: from_query ( query_string) ?. into_inner ( ) ;
254+ let token_response = exchange_code_for_token ( oidc_client, http_client, params) . await ?;
255+ let mut response = build_redirect_response ( format ! ( "/" ) ) ;
256+ set_auth_cookie ( & mut response, & token_response) ?;
257+ Ok ( response)
258+ }
259+
260+ async fn exchange_code_for_token (
261+ oidc_client : & OidcClient ,
262+ http_client : & AwcHttpClient ,
263+ oidc_callback_params : OidcCallbackParams ,
264+ ) -> anyhow:: Result < openidconnect:: core:: CoreTokenResponse > {
265+ // TODO: Verify the state matches the expected CSRF token
266+ let token_response = oidc_client
267+ . exchange_code ( openidconnect:: AuthorizationCode :: new (
268+ oidc_callback_params. code ,
269+ ) ) ?
270+ . request_async ( http_client)
271+ . await ?;
272+ Ok ( token_response)
273+ }
274+
275+ fn set_auth_cookie (
276+ response : & mut HttpResponse ,
277+ token_response : & openidconnect:: core:: CoreTokenResponse ,
278+ ) -> anyhow:: Result < ( ) > {
279+ let access_token = token_response. access_token ( ) ;
280+ log:: debug!( "Received access token: {}" , access_token. secret( ) ) ;
281+ let id_token = token_response
282+ . id_token ( )
283+ . context ( "No ID token found in the token response. You may have specified an oauth2 provider that does not support OIDC." ) ?;
284+
285+ let cookie = actix_web:: cookie:: Cookie :: build ( SQLPAGE_AUTH_COOKIE_NAME , id_token. to_string ( ) )
286+ . secure ( true )
287+ . http_only ( true )
288+ . same_site ( actix_web:: cookie:: SameSite :: Lax )
289+ . path ( "/" )
290+ . finish ( ) ;
291+
292+ response. add_cookie ( & cookie) . unwrap ( ) ;
293+ Ok ( ( ) )
294+ }
295+
278296fn build_redirect_response ( target_url : String ) -> HttpResponse {
279297 HttpResponse :: TemporaryRedirect ( )
280298 . append_header ( ( "Location" , target_url) )
@@ -404,3 +422,9 @@ fn make_oidc_client(
404422
405423 Ok ( client)
406424}
425+
426+ #[ derive( Debug , Deserialize ) ]
427+ struct OidcCallbackParams {
428+ code : String ,
429+ state : String ,
430+ }
0 commit comments