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;
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(ref http_error) => {
129 if let Some(ErrorKind::NotFound) = http_error.client_api_error_kind() {
130 return Self::NotFound;
131 }
132 Self::SecureChannel(e)
133 }
134 e => Self::SecureChannel(e),
135 }
136 }
137}
138
139/// The error type for failures while trying to grant log in to a new device
140/// using a QR code.
141#[derive(Debug, Error)]
142pub enum QRCodeGrantLoginError {
143 /// Secrets backup not set up.
144 #[error("Secrets backup not set up")]
145 MissingSecretsBackup(Option<SecretsBundleExportError>),
146
147 /// The check code was incorrect.
148 #[error("The check code was incorrect")]
149 InvalidCheckCode,
150
151 /// The rendezvous session was not found and might have expired.
152 #[error("The rendezvous session was not found and might have expired")]
153 NotFound,
154
155 /// Auth handshake error.
156 #[error("Auth handshake error: {0}")]
157 Unknown(String),
158
159 /// Unsupported protocol.
160 #[error("Unsupported protocol: {0}")]
161 UnsupportedProtocol(LoginProtocolType),
162
163 /// The requested device ID is already in use.
164 #[error("The requested device ID is already in use")]
165 DeviceIDAlreadyInUse,
166
167 /// The requested device was not returned by the homeserver.
168 #[error("The requested device was not returned by the homeserver")]
169 DeviceNotFound,
170
171 /// An error happened while exchanging messages with the other device.
172 #[error(transparent)]
173 SecureChannel(SecureChannelError),
174
175 /// An unexpected message was received from the other device.
176 #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
177 UnexpectedMessage {
178 /// The message we expected.
179 expected: &'static str,
180 /// The message we received instead.
181 received: QrAuthMessage,
182 },
183
184 /// The other device has signaled to us that the login has failed.
185 #[error("The login failed, reason: {reason}")]
186 LoginFailure {
187 /// The reason, as signaled by the other device, for the login failure.
188 reason: LoginFailureReason,
189 },
190}
191
192impl From<SecureChannelError> for QRCodeGrantLoginError {
193 fn from(e: SecureChannelError) -> Self {
194 match e {
195 SecureChannelError::RendezvousChannel(ref http_error) => {
196 if let Some(ErrorKind::NotFound) = http_error.client_api_error_kind() {
197 return Self::NotFound;
198 }
199 Self::SecureChannel(e)
200 }
201 SecureChannelError::InvalidCheckCode => Self::InvalidCheckCode,
202 e => Self::SecureChannel(e),
203 }
204 }
205}
206
207impl From<SecretsBundleExportError> for QRCodeGrantLoginError {
208 fn from(e: SecretsBundleExportError) -> Self {
209 Self::MissingSecretsBackup(Some(e))
210 }
211}
212
213/// Error type describing failures in the interaction between the device
214/// attempting to log in and the OAuth 2.0 authorization server.
215#[derive(Debug, Error)]
216pub enum DeviceAuthorizationOAuthError {
217 /// A generic OAuth 2.0 error happened while we were attempting to register
218 /// the device with the OAuth 2.0 authorization server.
219 #[error(transparent)]
220 OAuth(#[from] crate::authentication::oauth::OAuthError),
221
222 /// The OAuth 2.0 server doesn't support the device authorization grant.
223 #[error("OAuth 2.0 server doesn't support the device authorization grant")]
224 NoDeviceAuthorizationEndpoint,
225
226 /// An error happened while we attempted to request a device authorization
227 /// from the OAuth 2.0 authorization server.
228 #[error(transparent)]
229 DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
230
231 /// An error happened while waiting for the access token to be issued and
232 /// sent to us by the OAuth 2.0 authorization server.
233 #[error(transparent)]
234 RequestToken(
235 #[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
236 ),
237}
238
239impl DeviceAuthorizationOAuthError {
240 /// If the [`DeviceAuthorizationOAuthError`] is of the
241 /// [`DeviceCodeErrorResponseType`] error variant, return it.
242 pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
243 let error = as_variant!(self, DeviceAuthorizationOAuthError::RequestToken)?;
244 let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
245
246 Some(request_token_error.error())
247 }
248}
249
250/// Error type for failures in when receiving or sending messages over the
251/// secure channel.
252#[derive(Debug, Error)]
253pub enum SecureChannelError {
254 /// A message we received over the secure channel was not a valid UTF-8
255 /// encoded string.
256 #[error(transparent)]
257 Utf8(#[from] std::str::Utf8Error),
258
259 /// A message has failed to be decrypted.
260 #[error(transparent)]
261 Ecies(#[from] EciesError),
262
263 /// A received message has failed to be decoded.
264 #[error(transparent)]
265 MessageDecode(#[from] MessageDecodeError),
266
267 /// A message couldn't be deserialized from JSON.
268 #[error(transparent)]
269 Json(#[from] serde_json::Error),
270
271 /// The secure channel failed to be established because it received an
272 /// unexpected message.
273 #[error(
274 "The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
275 )]
276 SecureChannelMessage {
277 /// The secure channel message we expected.
278 expected: &'static str,
279 /// The secure channel message we received instead.
280 received: String,
281 },
282
283 /// The secure channel could not have been established, the check code was
284 /// invalid.
285 #[error("The secure channel could not have been established, the check code was invalid")]
286 InvalidCheckCode,
287
288 /// An error happened in the underlying rendezvous channel.
289 #[error("Error in the rendezvous channel: {0:?}")]
290 RendezvousChannel(#[from] HttpError),
291
292 /// Both devices have advertised the same intent in the login attempt, i.e.
293 /// both sides claim to be a new device.
294 #[error(
295 "The secure channel could not have been established, \
296 the two devices have the same login intent"
297 )]
298 InvalidIntent,
299
300 /// The secure channel could not have been established, the check code
301 /// cannot be received.
302 #[error(
303 "The secure channel could not have been established, \
304 the check code cannot be received"
305 )]
306 CannotReceiveCheckCode,
307
308 #[error("The QR code specifies an unsupported protocol version")]
309 /// The QR code specifies an unsupported protocol version.
310 UnsupportedQrCodeType,
311}
312
313/// Metadata to be used with [`LoginProgress::EstablishingSecureChannel`]
314/// or [`GrantLoginProgress::EstablishingSecureChannel`] when
315/// this device is the one scanning the QR code.
316///
317/// We have established the secure channel, but we need to let the other
318/// side know about the [`CheckCode`] so they can verify that the secure
319/// channel is indeed secure.
320#[derive(Clone, Debug)]
321pub struct QrProgress {
322 /// The check code we need to, out of band, send to the other device.
323 pub check_code: CheckCode,
324}
325
326/// Metadata to be used with [`LoginProgress::EstablishingSecureChannel`] and
327/// [`GrantLoginProgress::EstablishingSecureChannel`] when this device is the
328/// one generating the QR code.
329///
330/// We have established the secure channel, but we need to let the
331/// other device know about the [`QrCodeData`] so they can connect to the
332/// channel and let us know about the checkcode so we can verify that the
333/// channel is indeed secure.
334#[derive(Clone, Debug)]
335pub enum GeneratedQrProgress {
336 /// The QR code has been created and this device is waiting for the other
337 /// device to scan it.
338 QrReady(QrCodeData),
339 /// The QR code has been scanned by the other device and this device is
340 /// waiting for the user to put in the checkcode displayed on the
341 /// other device.
342 QrScanned(CheckCodeSender),
343}
344
345/// Used to pass back the checkcode entered by the user to verify that the
346/// secure channel is indeed secure.
347#[derive(Clone, Debug)]
348pub struct CheckCodeSender {
349 inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<u8>>>>,
350}
351
352impl CheckCodeSender {
353 pub(crate) fn new(tx: tokio::sync::oneshot::Sender<u8>) -> Self {
354 Self { inner: Arc::new(Mutex::new(Some(tx))) }
355 }
356
357 /// Send the checkcode.
358 ///
359 /// Calling this method more than once will result in an error.
360 ///
361 /// # Arguments
362 ///
363 /// * `check_code` - The check code in digits representation.
364 pub async fn send(&self, check_code: u8) -> Result<(), CheckCodeSenderError> {
365 match self.inner.lock().await.take() {
366 Some(tx) => tx.send(check_code).map_err(|_| CheckCodeSenderError::CannotSend),
367 None => Err(CheckCodeSenderError::AlreadySent),
368 }
369 }
370}
371
372/// Possible errors when calling [`CheckCodeSender::send`].
373#[derive(Debug, thiserror::Error)]
374pub enum CheckCodeSenderError {
375 /// The check code has already been sent.
376 #[error("check code already sent.")]
377 AlreadySent,
378 /// The check code cannot be sent.
379 #[error("check code cannot be sent.")]
380 CannotSend,
381}