Skip to main content

tuwunel_api/client/backup/
mod.rs

1mod keys;
2mod keys_room;
3mod keys_session;
4mod version;
5mod version_id;
6
7use futures::{FutureExt, future::try_join};
8use http::StatusCode;
9use ruma::{
10	CanonicalJsonValue, UInt, UserId,
11	api::error::{ErrorKind, WrongRoomKeysVersionErrorData},
12	serde::Raw,
13};
14use serde::Deserialize;
15use tuwunel_core::{Error, Result};
16use tuwunel_service::Services;
17
18pub(crate) use self::{
19	keys::{add_backup_keys_route, delete_backup_keys_route, get_backup_keys_route},
20	keys_room::{
21		add_backup_keys_for_room_route, delete_backup_keys_for_room_route,
22		get_backup_keys_for_room_route,
23	},
24	keys_session::{
25		add_backup_keys_for_session_route, delete_backup_keys_for_session_route,
26		get_backup_keys_for_session_route,
27	},
28	version::{create_backup_version_route, get_latest_backup_info_route},
29	version_id::{
30		delete_backup_version_route, get_backup_info_route, update_backup_version_route,
31	},
32};
33
34/// Overrides ruma's internal `AlgorithmWithData` shape required by the GET
35/// `/room_keys/version[/{version}]` response serializer. Validating against
36/// this will not raise a serialization error (HTTP 500) when responding.
37#[derive(Deserialize)]
38#[expect(unused)]
39struct AlgorithmShape {
40	algorithm: Raw<CanonicalJsonValue>,
41	auth_data: Raw<CanonicalJsonValue>,
42}
43
44pub(super) fn validate_algorithm_shape<T>(raw: &Raw<T>) -> Result {
45	raw.deserialize_as_unchecked::<AlgorithmShape>()
46		.map_err(Into::into)
47		.map(drop)
48}
49
50pub(super) async fn get_count_etag(
51	services: &Services,
52	sender_user: &UserId,
53	version: &str,
54) -> Result<(UInt, String)> {
55	let count = services
56		.key_backups
57		.count_keys(sender_user, version)
58		.map(TryInto::try_into);
59
60	let etag = services
61		.key_backups
62		.get_etag(sender_user, version)
63		.map(Ok);
64
65	Ok(try_join(count, etag).await?)
66}
67
68pub(super) async fn check_backup_version(
69	services: &Services,
70	sender_user: &UserId,
71	version: &str,
72) -> Result {
73	services
74		.key_backups
75		.get_latest_backup_version(sender_user)
76		.await
77		.ok()
78		.filter(|current| current.as_str() != version)
79		.map_or(Ok(()), |current_version| {
80			let data = WrongRoomKeysVersionErrorData::new(current_version);
81			let kind = ErrorKind::WrongRoomKeysVersion(data);
82
83			Err(Error::Request(
84				kind,
85				"You may only manipulate the most recently created version of the backup.".into(),
86				StatusCode::BAD_REQUEST,
87			))
88		})
89}