#![deny(unsafe_code)]
#![deny(clippy::unwrap_used)]
#![deny(warnings)]
#![doc = include_str!("../README.md")]
use std::error::Error as StdError;
use std::future::Future;
use std::sync::Arc;
use arc_swap::ArcSwap;
use http::Uri;
use openidconnect::{
core::{
CoreAuthDisplay, CoreAuthPrompt, CoreClaimName, CoreClaimType, CoreClientAuthMethod,
CoreErrorResponseType, CoreGenderClaim, CoreGrantType, CoreJsonWebKey,
CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJwsSigningAlgorithm,
CoreResponseMode, CoreResponseType, CoreRevocableToken, CoreRevocationErrorResponse,
CoreSubjectIdentifierType, CoreTokenIntrospectionResponse, CoreTokenType,
},
AccessToken, Audience, AuthenticationContextClass, ClientId, ClientSecret, CsrfToken,
EmptyExtraTokenFields, EndpointMaybeSet, EndpointNotSet, EndpointSet, GenderClaim,
IdTokenFields, IssuerUrl, Nonce, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope,
StandardClaims, StandardErrorResponse, StandardTokenResponse,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::error::Error;
pub mod builder;
pub mod error;
mod extractor;
mod handler;
mod middleware;
pub use extractor::{OidcAccessToken, OidcClaims, OidcRpInitiatedLogout, OidcUserInfo};
pub use handler::handle_oidc_redirect;
pub use middleware::{OidcAuthLayer, OidcAuthMiddleware, OidcLoginLayer, OidcLoginMiddleware};
pub use openidconnect;
pub trait Session<AC: AdditionalClaims> {
type Error: StdError;
fn get(
&self,
) -> impl Future<Output = Result<OidcSession<AC, CoreGenderClaim>, Self::Error>> + Send;
fn set(
&mut self,
value: OidcSession<AC, CoreGenderClaim>,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
}
pub trait AdditionalClaims:
openidconnect::AdditionalClaims + Clone + Sync + Send + Serialize + DeserializeOwned
{
}
type OidcTokenResponse<AC> = StandardTokenResponse<
IdTokenFields<
AC,
EmptyExtraTokenFields,
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
>,
CoreTokenType,
>;
pub type IdToken<AZ> = openidconnect::IdToken<
AZ,
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
>;
type Client<
AC,
HasAuthUrl = EndpointSet,
HasDeviceAuthUrl = EndpointNotSet,
HasIntrospectionUrl = EndpointNotSet,
HasRevocationUrl = EndpointNotSet,
HasTokenUrl = EndpointMaybeSet,
HasUserInfoUrl = EndpointMaybeSet,
> = openidconnect::Client<
AC,
CoreAuthDisplay,
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJsonWebKey,
CoreAuthPrompt,
StandardErrorResponse<CoreErrorResponseType>,
OidcTokenResponse<AC>,
CoreTokenIntrospectionResponse,
CoreRevocableToken,
CoreRevocationErrorResponse,
HasAuthUrl,
HasDeviceAuthUrl,
HasIntrospectionUrl,
HasRevocationUrl,
HasTokenUrl,
HasUserInfoUrl,
>;
pub type ProviderMetadata = openidconnect::ProviderMetadata<
AdditionalProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Clone)]
pub struct OidcClient<AC: AdditionalClaims> {
scopes: Vec<Scope>,
client_id: ClientId,
client_secret: Option<ClientSecret>,
redirect_url: Uri,
client: Arc<ArcSwap<Client<AC>>>,
http_client: reqwest::Client,
end_session_endpoint: Option<Uri>,
auth_context_class: Option<AuthenticationContextClass>,
untrusted_audiences: Vec<Audience>,
}
impl<AC: AdditionalClaims> OidcClient<AC> {
pub async fn rediscover(&self, issuer: IssuerUrl) -> Result<(), Error> {
let provider_metadata = ProviderMetadata::discover_async(issuer, &self.http_client).await?;
let new_client = Client::from_provider_metadata(
provider_metadata,
self.client_id.clone(),
self.client_secret.clone(),
)
.set_redirect_uri(RedirectUrl::new(self.redirect_url.to_string())?);
self.client.store(Arc::new(new_client));
Ok(())
}
}
#[derive(Deserialize, Serialize, Debug, Clone, Copy, Default)]
pub struct EmptyAdditionalClaims {}
impl AdditionalClaims for EmptyAdditionalClaims {}
impl openidconnect::AdditionalClaims for EmptyAdditionalClaims {}
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "AC: Serialize + DeserializeOwned")]
pub struct OidcSession<AC: AdditionalClaims, GC: GenderClaim>(OidcSessionInner<AC, GC>);
impl<AC: AdditionalClaims, GC: GenderClaim> Default for OidcSession<AC, GC> {
fn default() -> Self {
Self(OidcSessionInner::Unauthenticated)
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "AC: Serialize + DeserializeOwned")]
enum OidcSessionInner<AC: AdditionalClaims, GC: GenderClaim> {
Unauthenticated,
Pending(PendingOidcSession),
Authenticated(AuthenticatedOidcSession<AC, GC>),
}
#[derive(Serialize, Deserialize, Debug)]
struct PendingOidcSession {
nonce: Nonce,
csrf_token: CsrfToken,
redirect_url: Box<str>,
pkce_verifier: PkceCodeVerifier,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "AC: Serialize + DeserializeOwned")]
struct AuthenticatedOidcSession<AC: AdditionalClaims, GC: GenderClaim> {
authenticated: AuthenticatedSession<AC, GC>,
refresh_token: Option<RefreshToken>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "AC: Serialize + DeserializeOwned")]
struct AuthenticatedSession<AC: AdditionalClaims, GC: GenderClaim> {
id_token: IdToken<AC>,
access_token: AccessToken,
user_info: UserInfoClaims<AC, GC>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(bound = "AC: Serialize + DeserializeOwned")]
pub struct UserInfoClaims<AC: AdditionalClaims, GC: GenderClaim> {
pub issuer: Option<openidconnect::IssuerUrl>,
pub audiences: Option<Vec<Audience>>,
pub standard_claims: StandardClaims<GC>,
pub additional_claims: AC,
}
impl<AC, GC> From<openidconnect::UserInfoClaims<AC, GC>> for UserInfoClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn from(value: openidconnect::UserInfoClaims<AC, GC>) -> Self {
Self {
issuer: value.issuer().cloned(),
audiences: value.audiences().cloned(),
standard_claims: value.standard_claims().clone(),
additional_claims: value.additional_claims().clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdditionalProviderMetadata {
end_session_endpoint: Option<String>,
}
impl openidconnect::AdditionalProviderMetadata for AdditionalProviderMetadata {}
#[derive(Clone, Copy)]
pub struct ClearSessionFlag;