use std::sync::Arc;
use as_variant::as_variant;
pub use matrix_sdk_base::crypto::types::qr_login::{
LoginQrCodeDecodeError, QrCodeData, QrCodeMode, QrCodeModeData,
};
use matrix_sdk_base::crypto::{SecretImportError, store::SecretsBundleExportError};
pub use oauth2::{
ConfigurationError, DeviceCodeErrorResponse, DeviceCodeErrorResponseType, HttpClientError,
RequestTokenError, StandardErrorResponse,
basic::{BasicErrorResponse, BasicRequestTokenError},
};
use ruma::api::{client::error::ErrorKind, error::FromHttpResponseError};
use thiserror::Error;
use tokio::sync::Mutex;
use url::Url;
use vodozemac::ecies::CheckCode;
pub use vodozemac::ecies::{Error as EciesError, MessageDecodeError};
mod grant;
mod login;
mod messages;
mod rendezvous_channel;
mod secure_channel;
pub use self::{
grant::{GrantLoginProgress, GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode},
login::{LoginProgress, LoginWithGeneratedQrCode, LoginWithQrCode},
messages::{LoginFailureReason, LoginProtocolType, QrAuthMessage},
};
use super::CrossProcessRefreshLockError;
#[cfg(doc)]
use super::OAuth;
use crate::HttpError;
#[derive(Debug, Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
pub enum QRCodeLoginError {
#[error(transparent)]
OAuth(#[from] DeviceAuthorizationOAuthError),
#[error("The login failed, reason: {reason}")]
LoginFailure {
reason: LoginFailureReason,
homeserver: Option<Url>,
},
#[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
UnexpectedMessage {
expected: &'static str,
received: QrAuthMessage,
},
#[error(transparent)]
SecureChannel(SecureChannelError),
#[error("The rendezvous session was not found and might have expired")]
NotFound,
#[error(transparent)]
CrossProcessRefreshLock(#[from] CrossProcessRefreshLockError),
#[error(transparent)]
UserIdDiscovery(HttpError),
#[error(transparent)]
SessionTokens(crate::Error),
#[error(transparent)]
DeviceKeyUpload(crate::Error),
#[error(transparent)]
SecretImport(#[from] SecretImportError),
#[error(transparent)]
ServerReset(crate::Error),
}
impl From<SecureChannelError> for QRCodeLoginError {
fn from(e: SecureChannelError) -> Self {
match e {
SecureChannelError::RendezvousChannel(HttpError::Api(ref boxed)) => {
if let FromHttpResponseError::Server(api_error) = boxed.as_ref()
&& let Some(ErrorKind::NotFound) =
api_error.as_client_api_error().and_then(|e| e.error_kind())
{
return Self::NotFound;
}
Self::SecureChannel(e)
}
e => Self::SecureChannel(e),
}
}
}
#[derive(Debug, Error)]
pub enum QRCodeGrantLoginError {
#[error("Secrets backup not set up")]
MissingSecretsBackup(Option<SecretsBundleExportError>),
#[error("The check code was incorrect")]
InvalidCheckCode,
#[error("The device could not be created")]
UnableToCreateDevice,
#[error("The rendezvous session was not found and might have expired")]
NotFound,
#[error("Auth handshake error: {0}")]
Unknown(String),
#[error("Unsupported protocol: {0}")]
UnsupportedProtocol(LoginProtocolType),
#[error("The requested device ID is already in use")]
DeviceIDAlreadyInUse,
}
impl From<SecureChannelError> for QRCodeGrantLoginError {
fn from(e: SecureChannelError) -> Self {
match e {
SecureChannelError::RendezvousChannel(HttpError::Api(ref boxed)) => {
if let FromHttpResponseError::Server(api_error) = boxed.as_ref()
&& let Some(ErrorKind::NotFound) =
api_error.as_client_api_error().and_then(|e| e.error_kind())
{
return Self::NotFound;
}
Self::Unknown(e.to_string())
}
SecureChannelError::InvalidCheckCode => Self::InvalidCheckCode,
e => Self::Unknown(e.to_string()),
}
}
}
impl From<SecretsBundleExportError> for QRCodeGrantLoginError {
fn from(e: SecretsBundleExportError) -> Self {
Self::MissingSecretsBackup(Some(e))
}
}
#[derive(Debug, Error)]
pub enum DeviceAuthorizationOAuthError {
#[error(transparent)]
OAuth(#[from] crate::authentication::oauth::OAuthError),
#[error("OAuth 2.0 server doesn't support the device authorization grant")]
NoDeviceAuthorizationEndpoint,
#[error(transparent)]
DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
#[error(transparent)]
RequestToken(
#[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
),
}
impl DeviceAuthorizationOAuthError {
pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
let error = as_variant!(self, DeviceAuthorizationOAuthError::RequestToken)?;
let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
Some(request_token_error.error())
}
}
#[derive(Debug, Error)]
pub enum SecureChannelError {
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
Ecies(#[from] EciesError),
#[error(transparent)]
MessageDecode(#[from] MessageDecodeError),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(
"The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
)]
SecureChannelMessage {
expected: &'static str,
received: String,
},
#[error("The secure channel could not have been established, the check code was invalid")]
InvalidCheckCode,
#[error("Error in the rendezvous channel: {0:?}")]
RendezvousChannel(#[from] HttpError),
#[error(
"The secure channel could not have been established, \
the two devices have the same login intent"
)]
InvalidIntent,
#[error(
"The secure channel could not have been established, \
the check code cannot be received"
)]
CannotReceiveCheckCode,
}
#[derive(Clone, Debug)]
pub struct QrProgress {
pub check_code: CheckCode,
}
#[derive(Clone, Debug)]
pub enum GeneratedQrProgress {
QrReady(QrCodeData),
QrScanned(CheckCodeSender),
}
#[derive(Clone, Debug)]
pub struct CheckCodeSender {
inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<u8>>>>,
}
impl CheckCodeSender {
pub(crate) fn new(tx: tokio::sync::oneshot::Sender<u8>) -> Self {
Self { inner: Arc::new(Mutex::new(Some(tx))) }
}
pub async fn send(&self, check_code: u8) -> Result<(), CheckCodeSenderError> {
match self.inner.lock().await.take() {
Some(tx) => tx.send(check_code).map_err(|_| CheckCodeSenderError::CannotSend),
None => Err(CheckCodeSenderError::AlreadySent),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CheckCodeSenderError {
#[error("check code already sent.")]
AlreadySent,
#[error("check code cannot be sent.")]
CannotSend,
}