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