matrix_sdk/authentication/
mod.rs

1// Copyright 2023 Kévin Commaille
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 and functions related to authentication in Matrix.
16
17use std::{fmt, sync::Arc};
18
19use matrix_sdk_base::{locks::Mutex, SessionMeta};
20use serde::{Deserialize, Serialize};
21use tokio::sync::{broadcast, Mutex as AsyncMutex, OnceCell};
22
23pub mod matrix;
24pub mod oauth;
25
26use self::{
27    matrix::MatrixAuth,
28    oauth::{OAuth, OAuthAuthData, OAuthCtx},
29};
30use crate::{Client, RefreshTokenError, SessionChange};
31
32/// The tokens for a user session.
33#[derive(Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
34#[allow(missing_debug_implementations)]
35pub struct SessionTokens {
36    /// The access token used for this session.
37    pub access_token: String,
38
39    /// The token used for refreshing the access token, if any.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub refresh_token: Option<String>,
42}
43
44#[cfg(not(tarpaulin_include))]
45impl fmt::Debug for SessionTokens {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.debug_struct("SessionTokens").finish_non_exhaustive()
48    }
49}
50
51pub(crate) type SessionCallbackError = Box<dyn std::error::Error + Send + Sync>;
52
53#[cfg(not(target_family = "wasm"))]
54pub(crate) type SaveSessionCallback =
55    dyn Fn(Client) -> Result<(), SessionCallbackError> + Send + Sync;
56#[cfg(target_family = "wasm")]
57pub(crate) type SaveSessionCallback = dyn Fn(Client) -> Result<(), SessionCallbackError>;
58
59#[cfg(not(target_family = "wasm"))]
60pub(crate) type ReloadSessionCallback =
61    dyn Fn(Client) -> Result<SessionTokens, SessionCallbackError> + Send + Sync;
62#[cfg(target_family = "wasm")]
63pub(crate) type ReloadSessionCallback =
64    dyn Fn(Client) -> Result<SessionTokens, SessionCallbackError>;
65
66/// All the data relative to authentication, and that must be shared between a
67/// client and all its children.
68pub(crate) struct AuthCtx {
69    pub(crate) oauth: OAuthCtx,
70
71    /// Whether to try to refresh the access token automatically when an
72    /// `M_UNKNOWN_TOKEN` error is encountered.
73    pub(crate) handle_refresh_tokens: bool,
74
75    /// Lock making sure we're only doing one token refresh at a time.
76    pub(crate) refresh_token_lock: Arc<AsyncMutex<Result<(), RefreshTokenError>>>,
77
78    /// Session change publisher. Allows the subscriber to handle changes to the
79    /// session such as logging out when the access token is invalid or
80    /// persisting updates to the access/refresh tokens.
81    pub(crate) session_change_sender: broadcast::Sender<SessionChange>,
82
83    /// Authentication data to keep in memory.
84    pub(crate) auth_data: OnceCell<AuthData>,
85
86    /// The current session tokens.
87    pub(crate) tokens: OnceCell<Mutex<SessionTokens>>,
88
89    /// A callback called whenever we need an absolute source of truth for the
90    /// current session tokens.
91    ///
92    /// This is required only in multiple processes setups.
93    pub(crate) reload_session_callback: OnceCell<Box<ReloadSessionCallback>>,
94
95    /// A callback to save a session back into the app's secure storage.
96    ///
97    /// This is always called, independently of the presence of a cross-process
98    /// lock.
99    ///
100    /// Internal invariant: this must be called only after `set_session_tokens`
101    /// has been called, not before.
102    pub(crate) save_session_callback: OnceCell<Box<SaveSessionCallback>>,
103}
104
105impl AuthCtx {
106    /// The current session tokens.
107    pub(crate) fn session_tokens(&self) -> Option<SessionTokens> {
108        Some(self.tokens.get()?.lock().clone())
109    }
110
111    /// The current access token.
112    pub(crate) fn access_token(&self) -> Option<String> {
113        Some(self.tokens.get()?.lock().access_token.clone())
114    }
115
116    /// Set the current session tokens.
117    pub(crate) fn set_session_tokens(&self, session_tokens: SessionTokens) {
118        if let Some(tokens) = self.tokens.get() {
119            *tokens.lock() = session_tokens;
120        } else {
121            let _ = self.tokens.set(Mutex::new(session_tokens));
122        }
123    }
124}
125
126/// An enum over all the possible authentication APIs.
127#[derive(Debug, Clone)]
128#[non_exhaustive]
129pub enum AuthApi {
130    /// The native Matrix authentication API.
131    Matrix(MatrixAuth),
132
133    /// The OAuth 2.0 API.
134    OAuth(OAuth),
135}
136
137/// A user session using one of the available authentication APIs.
138#[derive(Debug, Clone)]
139#[non_exhaustive]
140pub enum AuthSession {
141    /// A session using the native Matrix authentication API.
142    Matrix(matrix::MatrixSession),
143
144    /// A session using the OAuth 2.0 API.
145    OAuth(Box<oauth::OAuthSession>),
146}
147
148impl AuthSession {
149    /// Get the matrix user information of this session.
150    pub fn meta(&self) -> &SessionMeta {
151        match self {
152            AuthSession::Matrix(session) => &session.meta,
153            AuthSession::OAuth(session) => &session.user.meta,
154        }
155    }
156
157    /// Take the matrix user information of this session.
158    pub fn into_meta(self) -> SessionMeta {
159        match self {
160            AuthSession::Matrix(session) => session.meta,
161            AuthSession::OAuth(session) => session.user.meta,
162        }
163    }
164
165    /// Get the access token of this session.
166    pub fn access_token(&self) -> &str {
167        match self {
168            AuthSession::Matrix(session) => &session.tokens.access_token,
169            AuthSession::OAuth(session) => &session.user.tokens.access_token,
170        }
171    }
172
173    /// Get the refresh token of this session.
174    pub fn get_refresh_token(&self) -> Option<&str> {
175        match self {
176            AuthSession::Matrix(session) => session.tokens.refresh_token.as_deref(),
177            AuthSession::OAuth(session) => session.user.tokens.refresh_token.as_deref(),
178        }
179    }
180}
181
182impl From<matrix::MatrixSession> for AuthSession {
183    fn from(session: matrix::MatrixSession) -> Self {
184        Self::Matrix(session)
185    }
186}
187
188impl From<oauth::OAuthSession> for AuthSession {
189    fn from(session: oauth::OAuthSession) -> Self {
190        Self::OAuth(session.into())
191    }
192}
193
194/// Data for an authentication API.
195#[derive(Debug)]
196pub(crate) enum AuthData {
197    /// Data for the native Matrix authentication API.
198    Matrix,
199    /// Data for the OAuth 2.0 API.
200    OAuth(OAuthAuthData),
201}