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