matrix_sdk/authentication/qrcode/
oidc_client.rsuse std::pin::Pin;
use futures_core::Future;
use mas_oidc_client::types::scope::{MatrixApiScopeToken, ScopeToken};
use openidconnect::{
core::{
CoreAuthDisplay, CoreAuthPrompt, CoreClaimName, CoreClaimType, CoreClient,
CoreClientAuthMethod, CoreDeviceAuthorizationResponse, CoreErrorResponseType,
CoreGenderClaim, CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType, CoreRevocableToken,
CoreRevocationErrorResponse, CoreSubjectIdentifierType, CoreTokenIntrospectionResponse,
CoreTokenResponse,
},
AdditionalProviderMetadata, AuthType, ClientId, ClientSecret, DeviceAuthorizationUrl,
EmptyAdditionalClaims, EndpointMaybeSet, EndpointNotSet, EndpointSet, HttpClientError,
HttpRequest, IssuerUrl, OAuth2TokenResponse, ProviderMetadata, Scope, StandardErrorResponse,
};
use vodozemac::Curve25519PublicKey;
use super::DeviceAuhorizationOidcError;
use crate::{http_client::HttpClient, oidc::OidcSessionTokens};
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
struct DeviceEndpointProviderMetadata {
device_authorization_endpoint: DeviceAuthorizationUrl,
}
impl AdditionalProviderMetadata for DeviceEndpointProviderMetadata {}
type DeviceProviderMetadata = ProviderMetadata<
DeviceEndpointProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
pub type OidcClientInner<
HasAuthUrl = EndpointSet,
HasDeviceAuthUrl = EndpointSet,
HasIntrospectionUrl = EndpointNotSet,
HasRevocationUrl = EndpointNotSet,
HasTokenUrl = EndpointMaybeSet,
HasUserInfoUrl = EndpointMaybeSet,
> = openidconnect::Client<
EmptyAdditionalClaims,
CoreAuthDisplay,
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJsonWebKey,
CoreAuthPrompt,
StandardErrorResponse<CoreErrorResponseType>,
CoreTokenResponse,
CoreTokenIntrospectionResponse,
CoreRevocableToken,
CoreRevocationErrorResponse,
HasAuthUrl,
HasDeviceAuthUrl,
HasIntrospectionUrl,
HasRevocationUrl,
HasTokenUrl,
HasUserInfoUrl,
>;
pub(super) struct OidcClient {
inner: OidcClientInner,
http_client: HttpClient,
}
impl OidcClient {
pub(super) async fn new(
client_id: String,
issuer_url: String,
http_client: HttpClient,
client_secret: Option<&str>,
) -> Result<Self, DeviceAuhorizationOidcError> {
let client_id = ClientId::new(client_id);
let issuer_url = IssuerUrl::new(issuer_url)?;
let client_secret = client_secret.map(|s| ClientSecret::new(s.to_owned()));
let provider_metadata =
DeviceProviderMetadata::discover_async(issuer_url, &http_client).await?;
let device_authorization_endpoint =
provider_metadata.additional_metadata().device_authorization_endpoint.clone();
let oidc_client =
CoreClient::from_provider_metadata(provider_metadata, client_id.clone(), client_secret)
.set_device_authorization_url(device_authorization_endpoint)
.set_auth_type(AuthType::RequestBody);
Ok(OidcClient { inner: oidc_client, http_client })
}
pub(super) async fn request_device_authorization(
&self,
device_id: Curve25519PublicKey,
) -> Result<CoreDeviceAuthorizationResponse, DeviceAuhorizationOidcError> {
let scopes = [
ScopeToken::Openid,
ScopeToken::MatrixApi(MatrixApiScopeToken::Full),
ScopeToken::try_with_matrix_device(device_id.to_base64()).expect(
"We should be able to create a scope token from a \
Curve25519 public key encoded as base64",
),
]
.into_iter()
.map(|scope| Scope::new(scope.to_string()));
let details: CoreDeviceAuthorizationResponse = self
.inner
.exchange_device_code()
.add_scopes(scopes)
.request_async(&self.http_client)
.await?;
Ok(details)
}
pub(super) async fn wait_for_tokens(
&self,
details: &CoreDeviceAuthorizationResponse,
) -> Result<OidcSessionTokens, DeviceAuhorizationOidcError> {
let response = self
.inner
.exchange_device_access_token(details)?
.request_async(&self.http_client, tokio::time::sleep, None)
.await?;
let tokens = OidcSessionTokens {
access_token: response.access_token().secret().to_owned(),
refresh_token: response.refresh_token().map(|t| t.secret().to_owned()),
latest_id_token: None,
};
Ok(tokens)
}
}
impl<'c> openidconnect::AsyncHttpClient<'c> for HttpClient {
type Error = HttpClientError<reqwest::Error>;
type Future = Pin<
Box<
dyn Future<Output = Result<openidconnect::HttpResponse, Self::Error>>
+ Send
+ Sync
+ 'c,
>,
>;
fn call(&'c self, request: HttpRequest) -> Self::Future {
Box::pin(async move {
let response = self.inner.call(request).await?;
Ok(response)
})
}
}