11use std:: collections:: HashSet ;
22use std:: future:: ready;
3+ use std:: ops:: Deref ;
4+ use std:: time:: { Duration , Instant } ;
35use std:: { future:: Future , pin:: Pin , str:: FromStr , sync:: Arc } ;
46
57use crate :: webserver:: http_client:: get_http_client_from_appdata;
@@ -15,12 +17,15 @@ use actix_web::{
1517use anyhow:: { anyhow, Context } ;
1618use awc:: Client ;
1719use chrono:: Utc ;
20+ use openidconnect:: core:: CoreJsonWebKey ;
21+ use openidconnect:: IdTokenVerifier ;
1822use openidconnect:: {
1923 core:: CoreAuthenticationFlow , url:: Url , AsyncHttpClient , Audience , CsrfToken , EndpointMaybeSet ,
2024 EndpointNotSet , EndpointSet , IssuerUrl , Nonce , OAuth2TokenResponse , RedirectUrl , Scope ,
2125 TokenResponse ,
2226} ;
2327use serde:: { Deserialize , Serialize } ;
28+ use std:: sync:: Mutex ;
2429
2530use super :: http_client:: make_http_client;
2631
@@ -99,7 +104,7 @@ impl OidcConfig {
99104 fn create_id_token_verifier < ' a > (
100105 & ' a self ,
101106 oidc_client : & ' a OidcClient ,
102- ) -> openidconnect :: IdTokenVerifier < ' a , openidconnect :: core :: CoreJsonWebKey > {
107+ ) -> IdTokenVerifier < ' a , CoreJsonWebKey > {
103108 oidc_client
104109 . id_token_verifier ( )
105110 . set_other_audience_verifier_fn ( self . additional_audience_verifier . as_fn ( ) )
@@ -130,15 +135,78 @@ fn get_app_host(config: &AppConfig) -> String {
130135 host
131136}
132137
138+ pub struct ClientWithTime {
139+ client : OidcClient ,
140+ last_update : Instant ,
141+ }
142+
133143pub struct OidcState {
134144 pub config : OidcConfig ,
135- client : OidcClient ,
145+ app_config : AppConfig ,
146+ client : Mutex < ClientWithTime > ,
136147}
137148
149+ const OIDC_CLIENT_REFRESH_INTERVAL : Duration = Duration :: from_secs ( 600 ) ;
150+
138151impl OidcState {
139- pub async fn get_client ( & self ) -> & OidcClient {
140- todo ! ( ) ;
141- & self . client
152+ pub async fn new ( oidc_cfg : OidcConfig , app_config : AppConfig ) -> anyhow:: Result < Self > {
153+ let http_client = make_http_client ( & app_config) ?;
154+ let client = build_oidc_client ( & oidc_cfg, & http_client) . await ?;
155+
156+ Ok ( Self {
157+ config : oidc_cfg,
158+ app_config,
159+ client : Mutex :: new ( ClientWithTime {
160+ client,
161+ last_update : Instant :: now ( ) ,
162+ } ) ,
163+ } )
164+ }
165+
166+ async fn refresh ( & self ) {
167+ let Ok ( http_client) = make_http_client ( & self . app_config ) else {
168+ log:: error!( "Failed to create HTTP client" ) ;
169+ return ;
170+ } ;
171+ match build_oidc_client ( & self . config , & http_client) . await {
172+ Ok ( client) => {
173+ * self . client . lock ( ) . expect ( "oidc client" ) = ClientWithTime {
174+ client,
175+ last_update : Instant :: now ( ) ,
176+ } ;
177+ }
178+ Err ( e) => {
179+ log:: error!( "Failed to refresh OIDC client: {e}" ) ;
180+ }
181+ }
182+ }
183+
184+ /// Gets a reference to the oidc client, potentially generating a new one if needed
185+ pub async fn get_client ( & self ) -> impl Deref < Target = ClientWithTime > + ' _ {
186+ {
187+ let client_lock = self . client . lock ( ) . expect ( "oidc client" ) ;
188+ if client_lock. last_update . elapsed ( ) < OIDC_CLIENT_REFRESH_INTERVAL {
189+ return client_lock;
190+ }
191+ }
192+ self . refresh ( ) . await ;
193+ self . client . lock ( ) . expect ( "oidc client" )
194+ }
195+
196+ fn get_token_claims (
197+ & self ,
198+ id_token : & OidcToken ,
199+ state : & OidcLoginState ,
200+ ) -> anyhow:: Result < OidcClaims > {
201+ // Do not refresh the client on every check
202+ let client = & self . client . lock ( ) . expect ( "oidc client" ) . client ;
203+ let verifier = self . config . create_id_token_verifier ( client) ;
204+ let nonce_verifier = |nonce : Option < & Nonce > | check_nonce ( nonce, & state. nonce ) ;
205+ let claims: OidcClaims = id_token
206+ . claims ( & verifier, nonce_verifier)
207+ . with_context ( || format ! ( "Could not verify the ID token: {id_token:?}" ) ) ?
208+ . clone ( ) ;
209+ Ok ( claims)
142210 }
143211}
144212
@@ -151,15 +219,19 @@ pub async fn initialize_oidc_state(
151219 Err ( Some ( e) ) => return Err ( anyhow:: anyhow!( e) ) ,
152220 } ;
153221
154- let http_client = make_http_client ( app_config ) ? ;
155- let provider_metadata =
156- discover_provider_metadata ( & http_client , oidc_cfg . issuer_url . clone ( ) ) . await ? ;
157- let client = make_oidc_client ( & oidc_cfg , provider_metadata ) ? ;
222+ Ok ( Some ( Arc :: new (
223+ OidcState :: new ( oidc_cfg , app_config . clone ( ) ) . await ? ,
224+ ) ) )
225+ }
158226
159- Ok ( Some ( Arc :: new ( OidcState {
160- config : oidc_cfg,
161- client,
162- } ) ) )
227+ async fn build_oidc_client (
228+ oidc_cfg : & OidcConfig ,
229+ http_client : & Client ,
230+ ) -> anyhow:: Result < OidcClient > {
231+ let provider_metadata =
232+ discover_provider_metadata ( http_client, oidc_cfg. issuer_url . clone ( ) ) . await ?;
233+ let client = make_oidc_client ( oidc_cfg, provider_metadata) ?;
234+ Ok ( client)
163235}
164236
165237pub struct OidcMiddleware {
@@ -249,7 +321,7 @@ where
249321 let oidc_state = Arc :: clone ( & self . oidc_state ) ;
250322 Box :: pin ( async move {
251323 let response = build_auth_provider_redirect_response (
252- oidc_state. get_client ( ) . await ,
324+ & oidc_state. get_client ( ) . await . client ,
253325 & oidc_state. config ,
254326 & request,
255327 ) ;
@@ -266,12 +338,17 @@ where
266338 Box :: pin ( async move {
267339 let query_string = request. query_string ( ) ;
268340 let client = oidc_state. get_client ( ) . await ;
269- match process_oidc_callback ( client, & oidc_state. config , query_string, & request) . await {
341+ match process_oidc_callback ( & client. client , & oidc_state. config , query_string, & request)
342+ . await
343+ {
270344 Ok ( response) => Ok ( request. into_response ( response) ) ,
271345 Err ( e) => {
272346 log:: error!( "Failed to process OIDC callback with params {query_string}: {e}" ) ;
273- let resp =
274- build_auth_provider_redirect_response ( client, & oidc_state. config , & request) ;
347+ let resp = build_auth_provider_redirect_response (
348+ & client. client ,
349+ & oidc_state. config ,
350+ & request,
351+ ) ;
275352 Ok ( request. into_response ( resp) )
276353 }
277354 }
@@ -448,19 +525,11 @@ fn get_authenticated_user_info(
448525 return Ok ( None ) ;
449526 } ;
450527 let cookie_value = cookie. value ( ) . to_string ( ) ;
451-
452- let state = get_state_from_cookie ( request) ?;
453- let config = oidc_state. config ;
454- let oidc_client = oidc_state. get_client ( ) . await ;
455- let verifier = config. create_id_token_verifier ( oidc_client) ;
456528 let id_token = OidcToken :: from_str ( & cookie_value)
457529 . with_context ( || format ! ( "Invalid SQLPage auth cookie: {cookie_value:?}" ) ) ?;
458530
459- let nonce_verifier = |nonce : Option < & Nonce > | check_nonce ( nonce, & state. nonce ) ;
460- let claims: OidcClaims = id_token
461- . claims ( & verifier, nonce_verifier)
462- . with_context ( || format ! ( "Could not verify the ID token: {cookie_value:?}" ) ) ?
463- . clone ( ) ;
531+ let state = get_state_from_cookie ( request) ?;
532+ let claims = oidc_state. get_token_claims ( & id_token, & state) ?;
464533 log:: debug!( "The current user is: {claims:?}" ) ;
465534 Ok ( Some ( claims) )
466535}
0 commit comments