mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-16 20:49:05 -04:00
sdk: Add the OIDC helper methods from the FFI.
This commit is contained in:
@@ -13,7 +13,7 @@ use matrix_sdk::{
|
||||
registration::{ClientMetadata, Localized, VerifiedClientMetadata},
|
||||
requests::GrantType,
|
||||
},
|
||||
OidcError,
|
||||
OidcAuthorizationData, OidcError,
|
||||
},
|
||||
ClientBuildError as MatrixClientBuildError, HttpError, RumaApiError,
|
||||
};
|
||||
@@ -167,24 +167,6 @@ pub struct OidcConfiguration {
|
||||
pub dynamic_registrations_file: String,
|
||||
}
|
||||
|
||||
/// The data required to authenticate against an OIDC server.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct OidcAuthenticationData {
|
||||
/// The underlying URL for authentication.
|
||||
pub(crate) url: Url,
|
||||
/// A unique identifier for the request, used to ensure the response
|
||||
/// originated from the authentication issuer.
|
||||
pub(crate) state: String,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OidcAuthenticationData {
|
||||
/// The login URL to use for authentication.
|
||||
pub fn login_url(&self) -> String {
|
||||
self.url.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct HomeserverLoginDetails {
|
||||
url: String,
|
||||
@@ -360,7 +342,7 @@ impl AuthenticationService {
|
||||
/// returns.
|
||||
pub async fn url_for_oidc_login(
|
||||
&self,
|
||||
) -> Result<Arc<OidcAuthenticationData>, AuthenticationError> {
|
||||
) -> Result<Arc<OidcAuthorizationData>, AuthenticationError> {
|
||||
let client_guard = self.client.read().await;
|
||||
let Some(client) = client_guard.as_ref() else {
|
||||
return Err(AuthenticationError::ClientMissing);
|
||||
@@ -376,7 +358,7 @@ impl AuthenticationService {
|
||||
/// Completes the OIDC login process.
|
||||
pub async fn login_with_oidc_callback(
|
||||
&self,
|
||||
authentication_data: Arc<OidcAuthenticationData>,
|
||||
authentication_data: Arc<OidcAuthorizationData>,
|
||||
callback_url: String,
|
||||
) -> Result<Arc<Client>, AuthenticationError> {
|
||||
let client_guard = self.client.read().await;
|
||||
|
||||
@@ -13,15 +13,12 @@ use matrix_sdk::{
|
||||
requests::account_management::AccountManagementActionFull,
|
||||
types::{
|
||||
client_credentials::ClientCredentials,
|
||||
errors::ClientErrorCode::AccessDenied,
|
||||
registration::{
|
||||
ClientMetadata, ClientMetadataVerificationError, VerifiedClientMetadata,
|
||||
},
|
||||
requests::Prompt,
|
||||
},
|
||||
AuthorizationResponse, Oidc, OidcSession,
|
||||
OidcAuthorizationData, OidcError, OidcSession,
|
||||
},
|
||||
reqwest::StatusCode,
|
||||
ruma::{
|
||||
api::client::{
|
||||
media::get_content_thumbnail::v3::Method,
|
||||
@@ -39,7 +36,7 @@ use matrix_sdk::{
|
||||
serde::Raw,
|
||||
EventEncryptionAlgorithm, RoomId, TransactionId, UInt, UserId,
|
||||
},
|
||||
AuthApi, AuthSession, Client as MatrixClient, SessionChange, SessionTokens,
|
||||
AuthApi, AuthSession, Client as MatrixClient, Error, SessionChange, SessionTokens,
|
||||
};
|
||||
use matrix_sdk_ui::notification_client::{
|
||||
NotificationClient as MatrixNotificationClient,
|
||||
@@ -63,7 +60,7 @@ use url::Url;
|
||||
|
||||
use super::{room::Room, session_verification::SessionVerificationController, RUNTIME};
|
||||
use crate::{
|
||||
authentication_service::{AuthenticationError, OidcAuthenticationData, OidcConfiguration},
|
||||
authentication_service::{AuthenticationError, OidcConfiguration},
|
||||
client,
|
||||
encryption::Encryption,
|
||||
notification::NotificationClient,
|
||||
@@ -362,72 +359,58 @@ impl Client {
|
||||
pub(crate) async fn url_for_oidc_login(
|
||||
&self,
|
||||
oidc_configuration: &OidcConfiguration,
|
||||
) -> Result<Arc<OidcAuthenticationData>, AuthenticationError> {
|
||||
let oidc = self.inner.oidc();
|
||||
) -> Result<Arc<OidcAuthorizationData>, AuthenticationError> {
|
||||
let oidc_metadata: VerifiedClientMetadata = oidc_configuration.try_into()?;
|
||||
let registrations_file = Path::new(&oidc_configuration.dynamic_registrations_file);
|
||||
let static_registrations = oidc_configuration
|
||||
.static_registrations
|
||||
.iter()
|
||||
.filter_map(|(issuer, client_id)| {
|
||||
let Ok(issuer) = Url::parse(issuer) else {
|
||||
tracing::error!("Failed to parse {:?}", issuer);
|
||||
return None;
|
||||
};
|
||||
Some((issuer, ClientId(client_id.clone())))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let registrations = OidcRegistrations::new(
|
||||
registrations_file,
|
||||
oidc_metadata.clone(),
|
||||
static_registrations,
|
||||
)?;
|
||||
|
||||
let issuer = match oidc.fetch_authentication_issuer().await {
|
||||
Ok(issuer) => issuer,
|
||||
Err(error) => {
|
||||
if error
|
||||
.as_client_api_error()
|
||||
.is_some_and(|err| err.status_code == StatusCode::NOT_FOUND)
|
||||
{
|
||||
return Err(AuthenticationError::OidcNotSupported);
|
||||
} else {
|
||||
return Err(AuthenticationError::ServerUnreachable(error));
|
||||
}
|
||||
}
|
||||
};
|
||||
let data =
|
||||
self.inner.oidc().url_for_oidc_login(oidc_metadata, registrations).await.map_err(
|
||||
|e| match e {
|
||||
OidcError::MissingAuthenticationIssuer => AuthenticationError::OidcNotSupported,
|
||||
OidcError::MissingRedirectUri => AuthenticationError::OidcMetadataInvalid,
|
||||
_ => AuthenticationError::OidcError { message: e.to_string() },
|
||||
},
|
||||
)?;
|
||||
|
||||
let redirect_url = Url::parse(&oidc_configuration.redirect_uri)
|
||||
.map_err(|_e| AuthenticationError::OidcMetadataInvalid)?;
|
||||
|
||||
self.configure_oidc(&oidc, issuer, oidc_configuration).await?;
|
||||
|
||||
let mut data_builder = oidc.login(redirect_url, None)?;
|
||||
// TODO: Add a check for the Consent prompt when MAS is updated.
|
||||
data_builder = data_builder.prompt(vec![Prompt::Consent]);
|
||||
let data = data_builder.build().await?;
|
||||
|
||||
Ok(Arc::new(OidcAuthenticationData { url: data.url, state: data.state }))
|
||||
Ok(Arc::new(data))
|
||||
}
|
||||
|
||||
/// Completes the OIDC login process.
|
||||
pub(crate) async fn login_with_oidc_callback(
|
||||
&self,
|
||||
authentication_data: Arc<OidcAuthenticationData>,
|
||||
authorization_data: Arc<OidcAuthorizationData>,
|
||||
callback_url: String,
|
||||
) -> Result<(), AuthenticationError> {
|
||||
let oidc = self.inner.oidc();
|
||||
let url = Url::parse(&callback_url).or(Err(AuthenticationError::OidcCallbackUrlInvalid))?;
|
||||
|
||||
let url =
|
||||
Url::parse(&callback_url).map_err(|_| AuthenticationError::OidcCallbackUrlInvalid)?;
|
||||
|
||||
let response = AuthorizationResponse::parse_uri(&url)
|
||||
.map_err(|_| AuthenticationError::OidcCallbackUrlInvalid)?;
|
||||
|
||||
let code = match response {
|
||||
AuthorizationResponse::Success(code) => code,
|
||||
AuthorizationResponse::Error(err) => {
|
||||
if err.error.error == AccessDenied {
|
||||
// The user cancelled the login in the web view.
|
||||
return Err(AuthenticationError::OidcCancelled);
|
||||
self.inner.oidc().login_with_oidc_callback(&authorization_data, url).await.map_err(
|
||||
|e| match e {
|
||||
Error::Oidc(OidcError::InvalidCallbackUrl) => {
|
||||
AuthenticationError::OidcCallbackUrlInvalid
|
||||
}
|
||||
return Err(AuthenticationError::OidcError {
|
||||
message: err.error.error.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if code.state != authentication_data.state {
|
||||
return Err(AuthenticationError::OidcCallbackUrlInvalid);
|
||||
};
|
||||
|
||||
oidc.finish_authorization(code).await?;
|
||||
|
||||
oidc.finish_login()
|
||||
.await
|
||||
.map_err(|e| AuthenticationError::OidcError { message: e.to_string() })?;
|
||||
Error::Oidc(OidcError::InvalidState) => AuthenticationError::OidcCallbackUrlInvalid,
|
||||
Error::Oidc(OidcError::CancelledAuthorization) => {
|
||||
AuthenticationError::OidcCancelled
|
||||
}
|
||||
_ => AuthenticationError::OidcError { message: e.to_string() },
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -457,126 +440,6 @@ impl Client {
|
||||
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
|
||||
Ok(supports_password)
|
||||
}
|
||||
|
||||
/// Handle any necessary configuration in order for login via OIDC to
|
||||
/// succeed. This includes performing dynamic client registration against
|
||||
/// the homeserver's issuer or restoring a previous registration if one has
|
||||
/// been stored.
|
||||
async fn configure_oidc(
|
||||
&self,
|
||||
oidc: &Oidc,
|
||||
issuer: String,
|
||||
configuration: &OidcConfiguration,
|
||||
) -> Result<(), AuthenticationError> {
|
||||
if oidc.client_credentials().is_some() {
|
||||
tracing::info!("OIDC is already configured.");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let oidc_metadata: VerifiedClientMetadata = configuration.try_into()?;
|
||||
let registrations_file = Path::new(&configuration.dynamic_registrations_file);
|
||||
let static_registrations = configuration
|
||||
.static_registrations
|
||||
.iter()
|
||||
.filter_map(|(issuer, client_id)| {
|
||||
let Ok(issuer) = Url::parse(issuer) else {
|
||||
tracing::error!("Failed to parse {:?}", issuer);
|
||||
return None;
|
||||
};
|
||||
Some((issuer, ClientId(client_id.clone())))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
if self.load_client_registration(
|
||||
oidc,
|
||||
issuer.clone(),
|
||||
oidc_metadata.clone(),
|
||||
registrations_file,
|
||||
static_registrations.clone(),
|
||||
) {
|
||||
tracing::info!("OIDC configuration loaded from disk.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("Registering this client for OIDC.");
|
||||
let registration_response =
|
||||
oidc.register_client(&issuer, oidc_metadata.clone(), None).await?;
|
||||
|
||||
// The format of the credentials changes according to the client metadata that
|
||||
// was sent. Public clients only get a client ID.
|
||||
let credentials =
|
||||
ClientCredentials::None { client_id: registration_response.client_id.clone() };
|
||||
oidc.restore_registered_client(issuer, oidc_metadata, credentials);
|
||||
|
||||
tracing::info!("Persisting OIDC registration data.");
|
||||
self.store_client_registration(oidc, registrations_file, static_registrations)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores the current OIDC dynamic client registration so it can be re-used
|
||||
/// if we ever log in via the same issuer again.
|
||||
fn store_client_registration(
|
||||
&self,
|
||||
oidc: &Oidc,
|
||||
registrations_file: &Path,
|
||||
static_registrations: HashMap<Url, ClientId>,
|
||||
) -> Result<(), AuthenticationError> {
|
||||
let issuer = Url::parse(oidc.issuer().ok_or(AuthenticationError::OidcNotSupported)?)
|
||||
.map_err(|_| AuthenticationError::OidcError {
|
||||
message: String::from("Failed to parse issuer URL."),
|
||||
})?;
|
||||
let client_id = oidc
|
||||
.client_credentials()
|
||||
.ok_or(AuthenticationError::OidcError {
|
||||
message: String::from("Missing client registration."),
|
||||
})?
|
||||
.client_id()
|
||||
.to_owned();
|
||||
|
||||
let metadata = oidc.client_metadata().ok_or(AuthenticationError::OidcError {
|
||||
message: String::from("Missing client metadata."),
|
||||
})?;
|
||||
|
||||
let registrations =
|
||||
OidcRegistrations::new(registrations_file, metadata.clone(), static_registrations)?;
|
||||
registrations.set_and_write_client_id(ClientId(client_id), issuer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to load an existing OIDC dynamic client registration for the
|
||||
/// currently configured issuer.
|
||||
fn load_client_registration(
|
||||
&self,
|
||||
oidc: &Oidc,
|
||||
issuer: String,
|
||||
oidc_metadata: VerifiedClientMetadata,
|
||||
registrations_file: &Path,
|
||||
static_registrations: HashMap<Url, ClientId>,
|
||||
) -> bool {
|
||||
let Ok(issuer_url) = Url::parse(&issuer) else {
|
||||
tracing::error!("Failed to parse {issuer:?}");
|
||||
return false;
|
||||
};
|
||||
let Some(registrations) =
|
||||
OidcRegistrations::new(registrations_file, oidc_metadata.clone(), static_registrations)
|
||||
.ok()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let Some(client_id) = registrations.client_id(&issuer_url) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
oidc.restore_registered_client(
|
||||
issuer,
|
||||
oidc_metadata,
|
||||
ClientCredentials::None { client_id: client_id.0 },
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
|
||||
@@ -236,9 +236,20 @@ impl OidcAuthCodeUrlBuilder {
|
||||
|
||||
/// The data needed to perform authorization using OpenID Connect.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
|
||||
pub struct OidcAuthorizationData {
|
||||
/// The URL that should be presented.
|
||||
pub url: Url,
|
||||
/// A unique identifier for the request.
|
||||
/// A unique identifier for the request, used to ensure the response
|
||||
/// originated from the authentication issuer.
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "uniffi")]
|
||||
#[uniffi::export]
|
||||
impl OidcAuthorizationData {
|
||||
/// The login URL to use for authorization.
|
||||
pub fn login_url(&self) -> String {
|
||||
self.url.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ use std::{collections::HashMap, fmt, sync::Arc};
|
||||
use as_variant::as_variant;
|
||||
use eyeball::SharedObservable;
|
||||
use futures_core::Stream;
|
||||
use http::StatusCode;
|
||||
pub use mas_oidc_client::{error, requests, types};
|
||||
use mas_oidc_client::{
|
||||
requests::{
|
||||
@@ -178,10 +179,11 @@ use mas_oidc_client::{
|
||||
},
|
||||
types::{
|
||||
client_credentials::ClientCredentials,
|
||||
errors::ClientError,
|
||||
errors::{ClientError, ClientErrorCode::AccessDenied},
|
||||
iana::oauth::OAuthTokenTypeHint,
|
||||
oidc::{AccountManagementAction, VerifiedProviderMetadata},
|
||||
registration::{ClientRegistrationResponse, VerifiedClientMetadata},
|
||||
requests::Prompt,
|
||||
scope::{MatrixApiScopeToken, Scope, ScopeToken},
|
||||
IdToken,
|
||||
},
|
||||
@@ -219,6 +221,7 @@ use self::{
|
||||
use crate::{
|
||||
authentication::{qrcode::LoginWithQrCode, AuthData},
|
||||
client::SessionChange,
|
||||
oidc::registrations::{ClientId, OidcRegistrations},
|
||||
Client, HttpError, RefreshTokenError, Result,
|
||||
};
|
||||
|
||||
@@ -434,6 +437,157 @@ impl Oidc {
|
||||
LoginWithQrCode::new(&self.client, client_metadata, data)
|
||||
}
|
||||
|
||||
/// A higher level wrapper around the configuration and login methods that
|
||||
/// will take some client metadata, register the client if needed and begin
|
||||
/// the login process, returning the authorization data required to show a
|
||||
/// webview for a user to login to their account. Call
|
||||
/// [`Oidc::login_with_oidc_callback`] to finish the process when the
|
||||
/// webview is complete.
|
||||
pub async fn url_for_oidc_login(
|
||||
&self,
|
||||
client_metadata: VerifiedClientMetadata,
|
||||
registrations: OidcRegistrations,
|
||||
) -> Result<OidcAuthorizationData, OidcError> {
|
||||
let issuer = match self.fetch_authentication_issuer().await {
|
||||
Ok(issuer) => issuer,
|
||||
Err(error) => {
|
||||
if error
|
||||
.as_client_api_error()
|
||||
.is_some_and(|err| err.status_code == StatusCode::NOT_FOUND)
|
||||
{
|
||||
return Err(OidcError::MissingAuthenticationIssuer);
|
||||
} else {
|
||||
return Err(OidcError::UnknownError(Box::new(error)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let redirect_uris =
|
||||
client_metadata.redirect_uris.clone().ok_or(OidcError::MissingRedirectUri)?;
|
||||
|
||||
let redirect_url = redirect_uris.first().ok_or(OidcError::MissingRedirectUri)?;
|
||||
|
||||
self.configure(issuer, client_metadata, registrations).await?;
|
||||
|
||||
let mut data_builder = self.login(redirect_url.clone(), None)?;
|
||||
data_builder = data_builder.prompt(vec![Prompt::Consent]);
|
||||
let data = data_builder.build().await?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// A higher level wrapper around the methods to complete a login after the
|
||||
/// user has logged in through a webview. This method should be used in
|
||||
/// tandem with [`Oidc::url_for_oidc_login`].
|
||||
pub async fn login_with_oidc_callback(
|
||||
&self,
|
||||
authorization_data: &OidcAuthorizationData,
|
||||
callback_url: Url,
|
||||
) -> Result<()> {
|
||||
let response = AuthorizationResponse::parse_uri(&callback_url)
|
||||
.or(Err(OidcError::InvalidCallbackUrl))?;
|
||||
|
||||
let code = match response {
|
||||
AuthorizationResponse::Success(code) => code,
|
||||
AuthorizationResponse::Error(err) => {
|
||||
if err.error.error == AccessDenied {
|
||||
// The user cancelled the login in the web view.
|
||||
return Err(OidcError::CancelledAuthorization.into());
|
||||
}
|
||||
return Err(OidcError::Authorization(err).into());
|
||||
}
|
||||
};
|
||||
|
||||
// Given the InvalidState error already exists, I wonder if this was ever
|
||||
// necessary to manually check?
|
||||
if code.state != authorization_data.state {
|
||||
return Err(OidcError::InvalidState.into());
|
||||
};
|
||||
|
||||
self.finish_authorization(code).await?;
|
||||
self.finish_login().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Higher level wrapper that restores the OIDC client with automatic
|
||||
/// static/dynamic client registration.
|
||||
async fn configure(
|
||||
&self,
|
||||
issuer: String,
|
||||
client_metadata: VerifiedClientMetadata,
|
||||
registrations: OidcRegistrations,
|
||||
) -> std::result::Result<(), OidcError> {
|
||||
if self.client_credentials().is_some() {
|
||||
tracing::info!("OIDC is already configured.");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if self.load_client_registration(issuer.clone(), client_metadata.clone(), ®istrations) {
|
||||
tracing::info!("OIDC configuration loaded from disk.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("Registering this client for OIDC.");
|
||||
let registration_response =
|
||||
self.register_client(&issuer, client_metadata.clone(), None).await?;
|
||||
|
||||
// The format of the credentials changes according to the client metadata that
|
||||
// was sent. Public clients only get a client ID.
|
||||
let credentials =
|
||||
ClientCredentials::None { client_id: registration_response.client_id.clone() };
|
||||
self.restore_registered_client(issuer, client_metadata, credentials);
|
||||
|
||||
tracing::info!("Persisting OIDC registration data.");
|
||||
self.store_client_registration(®istrations)
|
||||
.map_err(|e| OidcError::UnknownError(Box::new(e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores the current OIDC dynamic client registration so it can be re-used
|
||||
/// if we ever log in via the same issuer again.
|
||||
fn store_client_registration(
|
||||
&self,
|
||||
registrations: &OidcRegistrations,
|
||||
) -> std::result::Result<(), OidcError> {
|
||||
let issuer = Url::parse(self.issuer().ok_or(OidcError::MissingAuthenticationIssuer)?)
|
||||
.map_err(|e| OidcError::Url(e))?;
|
||||
let client_id =
|
||||
self.client_credentials().ok_or(OidcError::NotRegistered)?.client_id().to_owned();
|
||||
|
||||
registrations
|
||||
.set_and_write_client_id(ClientId(client_id), issuer)
|
||||
.map_err(|e| OidcError::UnknownError(Box::new(e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to load an existing OIDC dynamic client registration for a
|
||||
/// given issuer.
|
||||
fn load_client_registration(
|
||||
&self,
|
||||
issuer: String,
|
||||
oidc_metadata: VerifiedClientMetadata,
|
||||
registrations: &OidcRegistrations,
|
||||
) -> bool {
|
||||
let Ok(issuer_url) = Url::parse(&issuer) else {
|
||||
error!("Failed to parse {issuer:?}");
|
||||
return false;
|
||||
};
|
||||
let Some(client_id) = registrations.client_id(&issuer_url) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.restore_registered_client(
|
||||
issuer,
|
||||
oidc_metadata,
|
||||
ClientCredentials::None { client_id: client_id.0 },
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// The OpenID Connect Provider used for authorization.
|
||||
///
|
||||
/// Returns `None` if the client registration was not restored with
|
||||
@@ -1499,6 +1653,14 @@ pub enum OidcError {
|
||||
#[error("no dynamic registration support")]
|
||||
NoRegistrationSupport,
|
||||
|
||||
/// The client has not registered while the operation requires it.
|
||||
#[error("client not registered")]
|
||||
NotRegistered,
|
||||
|
||||
/// The supplied redirect URIs are missing or empty.
|
||||
#[error("missing or empty redirect URIs")]
|
||||
MissingRedirectUri,
|
||||
|
||||
/// The device ID was not returned by the homeserver after login.
|
||||
#[error("missing device ID in response")]
|
||||
MissingDeviceId,
|
||||
@@ -1512,6 +1674,18 @@ pub enum OidcError {
|
||||
#[error("the supplied state is unexpected")]
|
||||
InvalidState,
|
||||
|
||||
/// The user cancelled authorization in the web view.
|
||||
#[error("authorization cancelled")]
|
||||
CancelledAuthorization,
|
||||
|
||||
/// The login was completed with an invalid callback.
|
||||
#[error("the supplied callback URL is invalid")]
|
||||
InvalidCallbackUrl,
|
||||
|
||||
/// An error occurred during authorization.
|
||||
#[error("authorization failed")]
|
||||
Authorization(AuthorizationError),
|
||||
|
||||
/// The device ID is invalid.
|
||||
#[error("invalid device ID")]
|
||||
InvalidDeviceId,
|
||||
|
||||
@@ -42,7 +42,7 @@ pub enum OidcRegistrationsError {
|
||||
InvalidFilePath,
|
||||
/// An error occurred whilst saving the registration data.
|
||||
#[error("Failed to save the registration data {0}.")]
|
||||
SaveFailure(#[source] Box<dyn std::error::Error>),
|
||||
SaveFailure(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
|
||||
/// A client ID that has been registered with an OpenID Connect provider.
|
||||
|
||||
Reference in New Issue
Block a user