Skip to content

Commit 72ddace

Browse files
committed
Refresh OIDC client periodically
1 parent 0a5ad91 commit 72ddace

1 file changed

Lines changed: 96 additions & 27 deletions

File tree

src/webserver/oidc.rs

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::HashSet;
22
use std::future::ready;
3+
use std::ops::Deref;
4+
use std::time::{Duration, Instant};
35
use std::{future::Future, pin::Pin, str::FromStr, sync::Arc};
46

57
use crate::webserver::http_client::get_http_client_from_appdata;
@@ -15,12 +17,15 @@ use actix_web::{
1517
use anyhow::{anyhow, Context};
1618
use awc::Client;
1719
use chrono::Utc;
20+
use openidconnect::core::CoreJsonWebKey;
21+
use openidconnect::IdTokenVerifier;
1822
use openidconnect::{
1923
core::CoreAuthenticationFlow, url::Url, AsyncHttpClient, Audience, CsrfToken, EndpointMaybeSet,
2024
EndpointNotSet, EndpointSet, IssuerUrl, Nonce, OAuth2TokenResponse, RedirectUrl, Scope,
2125
TokenResponse,
2226
};
2327
use serde::{Deserialize, Serialize};
28+
use std::sync::Mutex;
2429

2530
use 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+
133143
pub 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+
138151
impl 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

165237
pub 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

Comments
 (0)