matrix_sdk/authentication/oauth/qrcode/
mod.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types for the QR code login support defined in [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108).
16//!
17//! Please note, QR code logins are only supported when using OAuth 2.0 as the
18//! authentication mechanism, native Matrix authentication does not support it.
19//!
20//! This currently only implements the case where the new device is scanning the
21//! QR code. To log in using a QR code, please take a look at the
22//! [`OAuth::login_with_qr_code()`] method.
23
24use std::sync::Arc;
25
26use as_variant::as_variant;
27pub use matrix_sdk_base::crypto::types::qr_login::{
28    LoginQrCodeDecodeError, QrCodeData, QrCodeMode, QrCodeModeData,
29};
30use matrix_sdk_base::crypto::{SecretImportError, store::SecretsBundleExportError};
31pub use oauth2::{
32    ConfigurationError, DeviceCodeErrorResponse, DeviceCodeErrorResponseType, HttpClientError,
33    RequestTokenError, StandardErrorResponse,
34    basic::{BasicErrorResponse, BasicRequestTokenError},
35};
36use thiserror::Error;
37use tokio::sync::Mutex;
38use url::Url;
39pub use vodozemac::ecies::{Error as EciesError, MessageDecodeError};
40
41mod grant;
42mod login;
43mod messages;
44mod rendezvous_channel;
45mod secure_channel;
46
47pub use self::{
48    grant::{GrantLoginProgress, GrantLoginWithGeneratedQrCode},
49    login::{LoginProgress, LoginWithGeneratedQrCode, LoginWithQrCode, QrProgress},
50    messages::{LoginFailureReason, LoginProtocolType, QrAuthMessage},
51};
52use super::CrossProcessRefreshLockError;
53#[cfg(doc)]
54use super::OAuth;
55use crate::HttpError;
56
57/// The error type for failures while trying to log in a new device using a QR
58/// code.
59#[derive(Debug, Error)]
60#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
61pub enum QRCodeLoginError {
62    /// An error happened while we were communicating with the OAuth 2.0
63    /// authorization server.
64    #[error(transparent)]
65    OAuth(#[from] DeviceAuthorizationOAuthError),
66
67    /// The other device has signaled to us that the login has failed.
68    #[error("The login failed, reason: {reason}")]
69    LoginFailure {
70        /// The reason, as signaled by the other device, for the login failure.
71        reason: LoginFailureReason,
72        /// The homeserver that we attempted to log in to.
73        homeserver: Option<Url>,
74    },
75
76    /// An unexpected message was received from the other device.
77    #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
78    UnexpectedMessage {
79        /// The message we expected.
80        expected: &'static str,
81        /// The message we received instead.
82        received: QrAuthMessage,
83    },
84
85    /// An error happened while exchanging messages with the other device.
86    #[error(transparent)]
87    SecureChannel(#[from] SecureChannelError),
88
89    /// The cross-process refresh lock failed to be initialized.
90    #[error(transparent)]
91    CrossProcessRefreshLock(#[from] CrossProcessRefreshLockError),
92
93    /// An error happened while we were trying to discover our user and device
94    /// ID, after we have acquired an access token from the OAuth 2.0
95    /// authorization server.
96    #[error(transparent)]
97    UserIdDiscovery(HttpError),
98
99    /// We failed to set the session tokens after we figured out our device and
100    /// user IDs.
101    #[error(transparent)]
102    SessionTokens(crate::Error),
103
104    /// The device keys failed to be uploaded after we successfully logged in.
105    #[error(transparent)]
106    DeviceKeyUpload(crate::Error),
107
108    /// The secrets bundle we received from the existing device failed to be
109    /// imported.
110    #[error(transparent)]
111    SecretImport(#[from] SecretImportError),
112
113    /// The other party told us to use a different homeserver but we failed to
114    /// reset the server URL.
115    #[error(transparent)]
116    ServerReset(crate::Error),
117}
118
119/// The error type for failures while trying to grant log in to a new device
120/// using a QR code.
121#[derive(Debug, Error)]
122pub enum QRCodeGrantLoginError {
123    /// Secrets backup not set up.
124    #[error("Secrets backup not set up")]
125    MissingSecretsBackup(Option<SecretsBundleExportError>),
126
127    /// The check code was incorrect.
128    #[error("The check code was incorrect")]
129    InvalidCheckCode,
130
131    /// The device could not be created.
132    #[error("The device could not be created")]
133    UnableToCreateDevice,
134
135    /// Auth handshake error.
136    #[error("Auth handshake error: {0}")]
137    Unknown(String),
138
139    /// Unsupported protocol.
140    #[error("Unsupported protocol: {0}")]
141    UnsupportedProtocol(LoginProtocolType),
142
143    /// The requested device ID is already in use.
144    #[error("The requested device ID is already in use")]
145    DeviceIDAlreadyInUse,
146}
147
148impl From<SecureChannelError> for QRCodeGrantLoginError {
149    fn from(e: SecureChannelError) -> Self {
150        match e {
151            SecureChannelError::InvalidCheckCode => Self::InvalidCheckCode,
152            e => Self::Unknown(e.to_string()),
153        }
154    }
155}
156
157impl From<SecretsBundleExportError> for QRCodeGrantLoginError {
158    fn from(e: SecretsBundleExportError) -> Self {
159        Self::MissingSecretsBackup(Some(e))
160    }
161}
162
163/// Error type describing failures in the interaction between the device
164/// attempting to log in and the OAuth 2.0 authorization server.
165#[derive(Debug, Error)]
166pub enum DeviceAuthorizationOAuthError {
167    /// A generic OAuth 2.0 error happened while we were attempting to register
168    /// the device with the OAuth 2.0 authorization server.
169    #[error(transparent)]
170    OAuth(#[from] crate::authentication::oauth::OAuthError),
171
172    /// The OAuth 2.0 server doesn't support the device authorization grant.
173    #[error("OAuth 2.0 server doesn't support the device authorization grant")]
174    NoDeviceAuthorizationEndpoint,
175
176    /// An error happened while we attempted to request a device authorization
177    /// from the OAuth 2.0 authorization server.
178    #[error(transparent)]
179    DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
180
181    /// An error happened while waiting for the access token to be issued and
182    /// sent to us by the OAuth 2.0 authorization server.
183    #[error(transparent)]
184    RequestToken(
185        #[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
186    ),
187}
188
189impl DeviceAuthorizationOAuthError {
190    /// If the [`DeviceAuthorizationOAuthError`] is of the
191    /// [`DeviceCodeErrorResponseType`] error variant, return it.
192    pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
193        let error = as_variant!(self, DeviceAuthorizationOAuthError::RequestToken)?;
194        let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
195
196        Some(request_token_error.error())
197    }
198}
199
200/// Error type for failures in when receiving or sending messages over the
201/// secure channel.
202#[derive(Debug, Error)]
203pub enum SecureChannelError {
204    /// A message we received over the secure channel was not a valid UTF-8
205    /// encoded string.
206    #[error(transparent)]
207    Utf8(#[from] std::str::Utf8Error),
208
209    /// A message has failed to be decrypted.
210    #[error(transparent)]
211    Ecies(#[from] EciesError),
212
213    /// A received message has failed to be decoded.
214    #[error(transparent)]
215    MessageDecode(#[from] MessageDecodeError),
216
217    /// A message couldn't be deserialized from JSON.
218    #[error(transparent)]
219    Json(#[from] serde_json::Error),
220
221    /// The secure channel failed to be established because it received an
222    /// unexpected message.
223    #[error(
224        "The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
225    )]
226    SecureChannelMessage {
227        /// The secure channel message we expected.
228        expected: &'static str,
229        /// The secure channel message we received instead.
230        received: String,
231    },
232
233    /// The secure channel could not have been established, the check code was
234    /// invalid.
235    #[error("The secure channel could not have been established, the check code was invalid")]
236    InvalidCheckCode,
237
238    /// An error happened in the underlying rendezvous channel.
239    #[error("Error in the rendezvous channel: {0:?}")]
240    RendezvousChannel(#[from] HttpError),
241
242    /// Both devices have advertised the same intent in the login attempt, i.e.
243    /// both sides claim to be a new device.
244    #[error(
245        "The secure channel could not have been established, \
246         the two devices have the same login intent"
247    )]
248    InvalidIntent,
249
250    /// The secure channel could not have been established, the check code
251    /// cannot be received.
252    #[error(
253        "The secure channel could not have been established, \
254         the check code cannot be received"
255    )]
256    CannotReceiveCheckCode,
257}
258
259/// Metadata to be used with [`LoginProgress::EstablishingSecureChannel`] and
260/// [`GrantLoginProgress::EstablishingSecureChannel`] when this device is the
261/// one generating the QR code.
262///
263/// We have established the secure channel, but we need to let the
264/// other device know about the [`QrCodeData`] so they can connect to the
265/// channel and let us know about the checkcode so we can verify that the
266/// channel is indeed secure.
267#[derive(Clone, Debug)]
268pub enum GeneratedQrProgress {
269    /// The QR code has been created and this device is waiting for the other
270    /// device to scan it.
271    QrReady(QrCodeData),
272    /// The QR code has been scanned by the other device and this device is
273    /// waiting for the user to put in the checkcode displayed on the
274    /// other device.
275    QrScanned(CheckCodeSender),
276}
277
278/// Used to pass back the checkcode entered by the user to verify that the
279/// secure channel is indeed secure.
280#[derive(Clone, Debug)]
281pub struct CheckCodeSender {
282    inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<u8>>>>,
283}
284
285impl CheckCodeSender {
286    pub(crate) fn new(tx: tokio::sync::oneshot::Sender<u8>) -> Self {
287        Self { inner: Arc::new(Mutex::new(Some(tx))) }
288    }
289
290    /// Send the checkcode.
291    ///
292    /// Calling this method more than once will result in an error.
293    ///
294    /// # Arguments
295    ///
296    /// * `check_code` - The check code in digits representation.
297    pub async fn send(&self, check_code: u8) -> Result<(), CheckCodeSenderError> {
298        match self.inner.lock().await.take() {
299            Some(tx) => tx.send(check_code).map_err(|_| CheckCodeSenderError::CannotSend),
300            None => Err(CheckCodeSenderError::AlreadySent),
301        }
302    }
303}
304
305/// Possible errors when calling [`CheckCodeSender::send`].
306#[derive(Debug, thiserror::Error)]
307pub enum CheckCodeSenderError {
308    /// The check code has already been sent.
309    #[error("check code already sent.")]
310    AlreadySent,
311    /// The check code cannot be sent.
312    #[error("check code cannot be sent.")]
313    CannotSend,
314}