From 580eabc2fa41807b45fe562ce67e0c0cded0d537 Mon Sep 17 00:00:00 2001 From: Schmiddiii Date: Thu, 5 Oct 2023 20:08:28 +0200 Subject: [PATCH] Link secondary devices as primary device --- presage-cli/src/main.rs | 4 +- presage/src/errors.rs | 4 ++ presage/src/manager/mod.rs | 2 +- presage/src/manager/registered.rs | 62 ++++++++++++++++++++++++++++++- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/presage-cli/src/main.rs b/presage-cli/src/main.rs index 3ff2b2af7..4d91b33bd 100644 --- a/presage-cli/src/main.rs +++ b/presage-cli/src/main.rs @@ -510,7 +510,9 @@ async fn run(subcommand: Cmd, config_store: S) -> anyhow::Result<()> { async move { match provisioning_link_rx.await { Ok(url) => { - qr2term::print_qr(url.to_string()).expect("failed to render qrcode") + println!("Please scan in the QR code:"); + qr2term::print_qr(url.to_string()).expect("failed to render qrcode"); + println!("Alternatively, use the URL: {}", url); } Err(e) => log::error!("Error linking device: {e}"), } diff --git a/presage/src/errors.rs b/presage/src/errors.rs index f4c0629bf..9bde6b3fc 100644 --- a/presage/src/errors.rs +++ b/presage/src/errors.rs @@ -72,6 +72,10 @@ pub enum Error { UnverifiedRegistrationSession, #[error("profile cipher error")] ProfileCipherError(#[from] libsignal_service::profile_cipher::ProfileCipherError), + #[error("Failed to link secondary device")] + ServiceLinkError(#[from] libsignal_service::LinkError), + #[error("An operation was requested that requires the registration to be primary, but it was only secondary")] + NotPrimaryDevice, } impl From for Error { diff --git a/presage/src/manager/mod.rs b/presage/src/manager/mod.rs index a17558672..4d26e632b 100644 --- a/presage/src/manager/mod.rs +++ b/presage/src/manager/mod.rs @@ -11,7 +11,7 @@ use rand::rngs::StdRng; pub use self::confirmation::Confirmation; pub use self::linking::Linking; -pub use self::registered::{ReceivingMode, Registered, RegistrationData}; +pub use self::registered::{ReceivingMode, Registered, RegistrationData, RegistrationType}; pub use self::registration::{Registration, RegistrationOptions}; /// Signal manager diff --git a/presage/src/manager/registered.rs b/presage/src/manager/registered.rs index e2301a9c4..1853b07a9 100644 --- a/presage/src/manager/registered.rs +++ b/presage/src/manager/registered.rs @@ -24,8 +24,8 @@ use libsignal_service::protocol::SenderCertificate; use libsignal_service::protocol::{PrivateKey, PublicKey}; use libsignal_service::provisioning::{generate_registration_id, ProvisioningError}; use libsignal_service::push_service::{ - AccountAttributes, DeviceCapabilities, PushService, ServiceError, ServiceIdType, ServiceIds, - WhoAmIResponse, DEFAULT_DEVICE_ID, + AccountAttributes, DeviceCapabilities, DeviceInfo, PushService, ServiceError, ServiceIdType, + ServiceIds, WhoAmIResponse, DEFAULT_DEVICE_ID, }; use libsignal_service::receiver::MessageReceiver; use libsignal_service::sender::{AttachmentSpec, AttachmentUploadError}; @@ -46,6 +46,7 @@ use rand::SeedableRng; use serde::{Deserialize, Serialize}; use sha2::Digest; use tokio::sync::Mutex; +use url::Url; use crate::cache::CacheCell; use crate::serde::serde_profile_key; @@ -55,6 +56,12 @@ use crate::{AvatarBytes, Error, Manager}; type ServiceCipher = cipher::ServiceCipher; type MessageSender = libsignal_service::prelude::MessageSender; +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RegistrationType { + Primary, + Secondary, +} + /// Manager state when the client is registered and can send and receive messages from Signal #[derive(Clone)] pub struct Registered { @@ -1210,6 +1217,57 @@ impl Manager { } } + /// Returns how this client was registered, either as a primary or secondary device. + pub fn registration_type(&self) -> RegistrationType { + if self.state.data.device_name.is_some() { + RegistrationType::Secondary + } else { + RegistrationType::Primary + } + } + + /// As a primary device, link a secondary device. + pub async fn link_secondary(&self, secondary: Url) -> Result<(), Error> { + // XXX: What happens if secondary device? Possible to use static typing to make this method call impossible in that case? + if self.registration_type() != RegistrationType::Primary { + return Err(Error::NotPrimaryDevice); + } + + let credentials = self.credentials().ok_or(Error::NotYetRegisteredError)?; + let mut account_manager = AccountManager::new( + self.identified_push_service(), + Some(self.state.data.profile_key), + ); + let store = self.store(); + + account_manager + .link_device(secondary, store, store, credentials) + .await?; + Ok(()) + } + + /// As a primary device, unlink a secondary device. + pub async fn unlink_secondary(&self, device_id: i64) -> Result<(), Error> { + // XXX: What happens if secondary device? Possible to use static typing to make this method call impossible in that case? + if self.registration_type() != RegistrationType::Primary { + return Err(Error::NotPrimaryDevice); + } + self.identified_push_service() + .unlink_device(device_id) + .await?; + Ok(()) + } + + /// As a primary device, list all the devices. + // XXX: Also shows the current device? + pub async fn linked_devices(&self) -> Result, Error> { + // XXX: What happens if secondary device? Possible to use static typing to make this method call impossible in that case? + if self.registration_type() != RegistrationType::Primary { + return Err(Error::::NotPrimaryDevice); + } + Ok(self.identified_push_service().devices().await?) + } + /// Deprecated methods /// Get a single contact by its UUID