matrix_sdk/authentication/oidc/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//! [`Oidc::login_with_qr_code()`] method.
23
24use as_variant::as_variant;
25use matrix_sdk_base::crypto::SecretImportError;
26pub use oauth2::{
27    basic::{BasicErrorResponse, BasicRequestTokenError},
28    ConfigurationError, DeviceCodeErrorResponse, DeviceCodeErrorResponseType, HttpClientError,
29    RequestTokenError, StandardErrorResponse,
30};
31use thiserror::Error;
32use url::Url;
33pub use vodozemac::ecies::{Error as EciesError, MessageDecodeError};
34
35#[cfg(doc)]
36use crate::authentication::oidc::Oidc;
37use crate::{authentication::oidc::CrossProcessRefreshLockError, HttpError};
38
39mod login;
40mod messages;
41mod rendezvous_channel;
42mod secure_channel;
43
44pub use matrix_sdk_base::crypto::types::qr_login::{
45    LoginQrCodeDecodeError, QrCodeData, QrCodeMode, QrCodeModeData,
46};
47
48pub use self::{
49    login::{LoginProgress, LoginWithQrCode},
50    messages::{LoginFailureReason, LoginProtocolType, QrAuthMessage},
51};
52
53/// The error type for failures while trying to log in a new device using a QR
54/// code.
55#[derive(Debug, Error)]
56#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
57pub enum QRCodeLoginError {
58    /// An error happened while we were communicating with the OAuth 2.0
59    /// authorization server.
60    #[error(transparent)]
61    Oauth(#[from] DeviceAuthorizationOauthError),
62
63    /// The other device has signaled to us that the login has failed.
64    #[error("The login failed, reason: {reason}")]
65    LoginFailure {
66        /// The reason, as signaled by the other device, for the login failure.
67        reason: LoginFailureReason,
68        /// The homeserver that we attempted to log in to.
69        homeserver: Option<Url>,
70    },
71
72    /// An unexpected message was received from the other device.
73    #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
74    UnexpectedMessage {
75        /// The message we expected.
76        expected: &'static str,
77        /// The message we received instead.
78        received: QrAuthMessage,
79    },
80
81    /// An error happened while exchanging messages with the other device.
82    #[error(transparent)]
83    SecureChannel(#[from] SecureChannelError),
84
85    /// The cross-process refresh lock failed to be initialized.
86    #[error(transparent)]
87    CrossProcessRefreshLock(#[from] CrossProcessRefreshLockError),
88
89    /// An error happened while we were trying to discover our user and device
90    /// ID, after we have acquired an access token from the OAuth 2.0
91    /// authorization server.
92    #[error(transparent)]
93    UserIdDiscovery(HttpError),
94
95    /// We failed to set the session tokens after we figured out our device and
96    /// user IDs.
97    #[error(transparent)]
98    SessionTokens(crate::Error),
99
100    /// The device keys failed to be uploaded after we successfully logged in.
101    #[error(transparent)]
102    DeviceKeyUpload(crate::Error),
103
104    /// The secrets bundle we received from the existing device failed to be
105    /// imported.
106    #[error(transparent)]
107    SecretImport(#[from] SecretImportError),
108}
109
110/// Error type describing failures in the interaction between the device
111/// attempting to log in and the OAuth 2.0 authorization server.
112#[derive(Debug, Error)]
113pub enum DeviceAuthorizationOauthError {
114    /// A generic OAuth 2.0 error happened while we were attempting to register
115    /// the device with the OAuth 2.0 authorization server.
116    #[error(transparent)]
117    Oauth(#[from] crate::authentication::oidc::OidcError),
118
119    /// The OAuth 2.0 server doesn't support the device authorization grant.
120    #[error("OAuth 2.0 server doesn't support the device authorization grant")]
121    NoDeviceAuthorizationEndpoint,
122
123    /// An error happened while we attempted to request a device authorization
124    /// from the Oauth 2.0 authorization server.
125    #[error(transparent)]
126    DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
127
128    /// An error happened while waiting for the access token to be issued and
129    /// sent to us by the Oauth 2.0 authorization server.
130    #[error(transparent)]
131    RequestToken(
132        #[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
133    ),
134}
135
136impl DeviceAuthorizationOauthError {
137    /// If the [`DeviceAuthorizationOauthError`] is of the
138    /// [`DeviceCodeErrorResponseType`] error variant, return it.
139    pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
140        let error = as_variant!(self, DeviceAuthorizationOauthError::RequestToken)?;
141        let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
142
143        Some(request_token_error.error())
144    }
145}
146
147/// Error type for failures in when receiving or sending messages over the
148/// secure channel.
149#[derive(Debug, Error)]
150pub enum SecureChannelError {
151    /// A message we received over the secure channel was not a valid UTF-8
152    /// encoded string.
153    #[error(transparent)]
154    Utf8(#[from] std::str::Utf8Error),
155
156    /// A message has failed to be decrypted.
157    #[error(transparent)]
158    Ecies(#[from] EciesError),
159
160    /// A received message has failed to be decoded.
161    #[error(transparent)]
162    MessageDecode(#[from] MessageDecodeError),
163
164    /// A message couldn't be deserialized from JSON.
165    #[error(transparent)]
166    Json(#[from] serde_json::Error),
167
168    /// The secure channel failed to be established because it received an
169    /// unexpected message.
170    #[error(
171        "The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
172    )]
173    SecureChannelMessage {
174        /// The secure channel message we expected.
175        expected: &'static str,
176        /// The secure channel message we received instead.
177        received: String,
178    },
179
180    /// The secure channel could not have been established, the check code was
181    /// invalid.
182    #[error("The secure channel could not have been established, the check code was invalid")]
183    InvalidCheckCode,
184
185    /// An error happened in the underlying rendezvous channel.
186    #[error("Error in the rendezvous channel: {0:?}")]
187    RendezvousChannel(#[from] HttpError),
188
189    /// Both devices have advertised the same intent in the login attempt, i.e.
190    /// both sides claim to be a new device.
191    #[error("The secure channel could not have been established, the two devices have the same login intent")]
192    InvalidIntent,
193}