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