Skip to main content

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, Msc4108IntentData, QrCodeData, QrCodeIntent, QrCodeIntentData,
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 ruma::api::{client::error::ErrorKind, error::FromHttpResponseError};
37use thiserror::Error;
38use tokio::sync::Mutex;
39use url::Url;
40use vodozemac::ecies::CheckCode;
41pub use vodozemac::ecies::{Error as EciesError, MessageDecodeError};
42
43mod grant;
44mod login;
45mod messages;
46mod rendezvous_channel;
47mod secure_channel;
48
49pub use self::{
50    grant::{GrantLoginProgress, GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode},
51    login::{LoginProgress, LoginWithGeneratedQrCode, LoginWithQrCode},
52    messages::{LoginFailureReason, LoginProtocolType, QrAuthMessage},
53};
54use super::CrossProcessRefreshLockError;
55#[cfg(doc)]
56use super::OAuth;
57use crate::HttpError;
58
59/// The error type for failures while trying to log in a new device using a QR
60/// code.
61#[derive(Debug, Error)]
62#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
63pub enum QRCodeLoginError {
64    /// An error happened while we were communicating with the OAuth 2.0
65    /// authorization server.
66    #[error(transparent)]
67    OAuth(#[from] DeviceAuthorizationOAuthError),
68
69    /// The other device has signaled to us that the login has failed.
70    #[error("The login failed, reason: {reason}")]
71    LoginFailure {
72        /// The reason, as signaled by the other device, for the login failure.
73        reason: LoginFailureReason,
74        /// The homeserver that we attempted to log in to.
75        homeserver: Option<Url>,
76    },
77
78    /// An unexpected message was received from the other device.
79    #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
80    UnexpectedMessage {
81        /// The message we expected.
82        expected: &'static str,
83        /// The message we received instead.
84        received: QrAuthMessage,
85    },
86
87    /// An error happened while exchanging messages with the other device.
88    #[error(transparent)]
89    SecureChannel(SecureChannelError),
90
91    /// The rendezvous session was not found and might have expired.
92    #[error("The rendezvous session was not found and might have expired")]
93    NotFound,
94
95    /// The cross-process refresh lock failed to be initialized.
96    #[error(transparent)]
97    CrossProcessRefreshLock(#[from] CrossProcessRefreshLockError),
98
99    /// An error happened while we were trying to discover our user and device
100    /// ID, after we have acquired an access token from the OAuth 2.0
101    /// authorization server.
102    #[error(transparent)]
103    UserIdDiscovery(HttpError),
104
105    /// We failed to set the session tokens after we figured out our device and
106    /// user IDs.
107    #[error(transparent)]
108    SessionTokens(crate::Error),
109
110    /// The device keys failed to be uploaded after we successfully logged in.
111    #[error(transparent)]
112    DeviceKeyUpload(crate::Error),
113
114    /// The secrets bundle we received from the existing device failed to be
115    /// imported.
116    #[error(transparent)]
117    SecretImport(#[from] SecretImportError),
118
119    /// The other party told us to use a different homeserver but we failed to
120    /// reset the server URL.
121    #[error(transparent)]
122    ServerReset(crate::Error),
123}
124
125impl From<SecureChannelError> for QRCodeLoginError {
126    fn from(e: SecureChannelError) -> Self {
127        match e {
128            SecureChannelError::RendezvousChannel(HttpError::Api(ref boxed)) => {
129                if let FromHttpResponseError::Server(api_error) = boxed.as_ref()
130                    && let Some(ErrorKind::NotFound) =
131                        api_error.as_client_api_error().and_then(|e| e.error_kind())
132                {
133                    return Self::NotFound;
134                }
135                Self::SecureChannel(e)
136            }
137            e => Self::SecureChannel(e),
138        }
139    }
140}
141
142/// The error type for failures while trying to grant log in to a new device
143/// using a QR code.
144#[derive(Debug, Error)]
145pub enum QRCodeGrantLoginError {
146    /// Secrets backup not set up.
147    #[error("Secrets backup not set up")]
148    MissingSecretsBackup(Option<SecretsBundleExportError>),
149
150    /// The check code was incorrect.
151    #[error("The check code was incorrect")]
152    InvalidCheckCode,
153
154    /// The rendezvous session was not found and might have expired.
155    #[error("The rendezvous session was not found and might have expired")]
156    NotFound,
157
158    /// Auth handshake error.
159    #[error("Auth handshake error: {0}")]
160    Unknown(String),
161
162    /// Unsupported protocol.
163    #[error("Unsupported protocol: {0}")]
164    UnsupportedProtocol(LoginProtocolType),
165
166    /// The requested device ID is already in use.
167    #[error("The requested device ID is already in use")]
168    DeviceIDAlreadyInUse,
169
170    /// The requested device was not returned by the homeserver.
171    #[error("The requested device was not returned by the homeserver")]
172    DeviceNotFound,
173
174    /// An error happened while exchanging messages with the other device.
175    #[error(transparent)]
176    SecureChannel(SecureChannelError),
177
178    /// An unexpected message was received from the other device.
179    #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
180    UnexpectedMessage {
181        /// The message we expected.
182        expected: &'static str,
183        /// The message we received instead.
184        received: QrAuthMessage,
185    },
186
187    /// The other device has signaled to us that the login has failed.
188    #[error("The login failed, reason: {reason}")]
189    LoginFailure {
190        /// The reason, as signaled by the other device, for the login failure.
191        reason: LoginFailureReason,
192    },
193}
194
195impl From<SecureChannelError> for QRCodeGrantLoginError {
196    fn from(e: SecureChannelError) -> Self {
197        match e {
198            SecureChannelError::RendezvousChannel(HttpError::Api(ref boxed)) => {
199                if let FromHttpResponseError::Server(api_error) = boxed.as_ref()
200                    && let Some(ErrorKind::NotFound) =
201                        api_error.as_client_api_error().and_then(|e| e.error_kind())
202                {
203                    return Self::NotFound;
204                }
205                Self::SecureChannel(e)
206            }
207            SecureChannelError::InvalidCheckCode => Self::InvalidCheckCode,
208            e => Self::SecureChannel(e),
209        }
210    }
211}
212
213impl From<SecretsBundleExportError> for QRCodeGrantLoginError {
214    fn from(e: SecretsBundleExportError) -> Self {
215        Self::MissingSecretsBackup(Some(e))
216    }
217}
218
219/// Error type describing failures in the interaction between the device
220/// attempting to log in and the OAuth 2.0 authorization server.
221#[derive(Debug, Error)]
222pub enum DeviceAuthorizationOAuthError {
223    /// A generic OAuth 2.0 error happened while we were attempting to register
224    /// the device with the OAuth 2.0 authorization server.
225    #[error(transparent)]
226    OAuth(#[from] crate::authentication::oauth::OAuthError),
227
228    /// The OAuth 2.0 server doesn't support the device authorization grant.
229    #[error("OAuth 2.0 server doesn't support the device authorization grant")]
230    NoDeviceAuthorizationEndpoint,
231
232    /// An error happened while we attempted to request a device authorization
233    /// from the OAuth 2.0 authorization server.
234    #[error(transparent)]
235    DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
236
237    /// An error happened while waiting for the access token to be issued and
238    /// sent to us by the OAuth 2.0 authorization server.
239    #[error(transparent)]
240    RequestToken(
241        #[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
242    ),
243}
244
245impl DeviceAuthorizationOAuthError {
246    /// If the [`DeviceAuthorizationOAuthError`] is of the
247    /// [`DeviceCodeErrorResponseType`] error variant, return it.
248    pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
249        let error = as_variant!(self, DeviceAuthorizationOAuthError::RequestToken)?;
250        let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
251
252        Some(request_token_error.error())
253    }
254}
255
256/// Error type for failures in when receiving or sending messages over the
257/// secure channel.
258#[derive(Debug, Error)]
259pub enum SecureChannelError {
260    /// A message we received over the secure channel was not a valid UTF-8
261    /// encoded string.
262    #[error(transparent)]
263    Utf8(#[from] std::str::Utf8Error),
264
265    /// A message has failed to be decrypted.
266    #[error(transparent)]
267    Ecies(#[from] EciesError),
268
269    /// A received message has failed to be decoded.
270    #[error(transparent)]
271    MessageDecode(#[from] MessageDecodeError),
272
273    /// A message couldn't be deserialized from JSON.
274    #[error(transparent)]
275    Json(#[from] serde_json::Error),
276
277    /// The secure channel failed to be established because it received an
278    /// unexpected message.
279    #[error(
280        "The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
281    )]
282    SecureChannelMessage {
283        /// The secure channel message we expected.
284        expected: &'static str,
285        /// The secure channel message we received instead.
286        received: String,
287    },
288
289    /// The secure channel could not have been established, the check code was
290    /// invalid.
291    #[error("The secure channel could not have been established, the check code was invalid")]
292    InvalidCheckCode,
293
294    /// An error happened in the underlying rendezvous channel.
295    #[error("Error in the rendezvous channel: {0:?}")]
296    RendezvousChannel(#[from] HttpError),
297
298    /// Both devices have advertised the same intent in the login attempt, i.e.
299    /// both sides claim to be a new device.
300    #[error(
301        "The secure channel could not have been established, \
302         the two devices have the same login intent"
303    )]
304    InvalidIntent,
305
306    /// The secure channel could not have been established, the check code
307    /// cannot be received.
308    #[error(
309        "The secure channel could not have been established, \
310         the check code cannot be received"
311    )]
312    CannotReceiveCheckCode,
313
314    #[error("The QR code specifies an unsupported protocol version")]
315    /// The QR code specifies an unsupported protocol version.
316    UnsupportedQrCodeType,
317}
318
319/// Metadata to be used with [`LoginProgress::EstablishingSecureChannel`]
320/// or [`GrantLoginProgress::EstablishingSecureChannel`] when
321/// this device is the one scanning the QR code.
322///
323/// We have established the secure channel, but we need to let the other
324/// side know about the [`CheckCode`] so they can verify that the secure
325/// channel is indeed secure.
326#[derive(Clone, Debug)]
327pub struct QrProgress {
328    /// The check code we need to, out of band, send to the other device.
329    pub check_code: CheckCode,
330}
331
332/// Metadata to be used with [`LoginProgress::EstablishingSecureChannel`] and
333/// [`GrantLoginProgress::EstablishingSecureChannel`] when this device is the
334/// one generating the QR code.
335///
336/// We have established the secure channel, but we need to let the
337/// other device know about the [`QrCodeData`] so they can connect to the
338/// channel and let us know about the checkcode so we can verify that the
339/// channel is indeed secure.
340#[derive(Clone, Debug)]
341pub enum GeneratedQrProgress {
342    /// The QR code has been created and this device is waiting for the other
343    /// device to scan it.
344    QrReady(QrCodeData),
345    /// The QR code has been scanned by the other device and this device is
346    /// waiting for the user to put in the checkcode displayed on the
347    /// other device.
348    QrScanned(CheckCodeSender),
349}
350
351/// Used to pass back the checkcode entered by the user to verify that the
352/// secure channel is indeed secure.
353#[derive(Clone, Debug)]
354pub struct CheckCodeSender {
355    inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<u8>>>>,
356}
357
358impl CheckCodeSender {
359    pub(crate) fn new(tx: tokio::sync::oneshot::Sender<u8>) -> Self {
360        Self { inner: Arc::new(Mutex::new(Some(tx))) }
361    }
362
363    /// Send the checkcode.
364    ///
365    /// Calling this method more than once will result in an error.
366    ///
367    /// # Arguments
368    ///
369    /// * `check_code` - The check code in digits representation.
370    pub async fn send(&self, check_code: u8) -> Result<(), CheckCodeSenderError> {
371        match self.inner.lock().await.take() {
372            Some(tx) => tx.send(check_code).map_err(|_| CheckCodeSenderError::CannotSend),
373            None => Err(CheckCodeSenderError::AlreadySent),
374        }
375    }
376}
377
378/// Possible errors when calling [`CheckCodeSender::send`].
379#[derive(Debug, thiserror::Error)]
380pub enum CheckCodeSenderError {
381    /// The check code has already been sent.
382    #[error("check code already sent.")]
383    AlreadySent,
384    /// The check code cannot be sent.
385    #[error("check code cannot be sent.")]
386    CannotSend,
387}