From 7fb2d40e96e561efb155e3c7fe25f52b3eaec5c8 Mon Sep 17 00:00:00 2001
From: Schmiddiii <github@schmiddi.anonaddy.com>
Date: Thu, 5 Oct 2023 20:08:28 +0200
Subject: [PATCH] Link secondary devices as primary device

---
 presage-cli/src/main.rs       |  4 ++-
 presage-store-sled/src/lib.rs | 11 +++++--
 presage/Cargo.toml            |  4 +--
 presage/src/errors.rs         |  4 +++
 presage/src/lib.rs            |  4 ++-
 presage/src/manager.rs        | 55 ++++++++++++++++++++++++++++++++++-
 6 files changed, 74 insertions(+), 8 deletions(-)

diff --git a/presage-cli/src/main.rs b/presage-cli/src/main.rs
index 7398b6d12..b17641c34 100644
--- a/presage-cli/src/main.rs
+++ b/presage-cli/src/main.rs
@@ -503,7 +503,9 @@ async fn run<C: Store + 'static>(subcommand: Cmd, config_store: C) -> anyhow::Re
                 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-store-sled/src/lib.rs b/presage-store-sled/src/lib.rs
index 757f5f51e..ed689d5a7 100644
--- a/presage-store-sled/src/lib.rs
+++ b/presage-store-sled/src/lib.rs
@@ -122,9 +122,12 @@ impl SledStore {
 
         #[cfg(feature = "encryption")]
         let cipher = passphrase
+            .as_ref()
             .map(|p| Self::get_or_create_store_cipher(&database, p.as_ref()))
             .transpose()?;
 
+        dbg!(cipher.is_some());
+
         #[cfg(not(feature = "encryption"))]
         if passphrase.is_some() {
             panic!("A passphrase was supplied but the encryption feature flag is not enabled")
@@ -299,7 +302,7 @@ fn migrate(
 
     let run_migrations = move || {
         let mut store = SledStore::new(db_path, passphrase)?;
-        let schema_version = store.schema_version();
+        let schema_version = dbg!(store.schema_version());
         for step in schema_version.steps() {
             match &step {
                 SchemaVersion::V1 => {
@@ -374,11 +377,11 @@ impl Store for SledStore {
     /// State
 
     fn load_state(&self) -> Result<Option<Registered>, SledStoreError> {
-        self.get(SLED_TREE_STATE, SLED_KEY_REGISTRATION)
+        dbg!(self.get(SLED_TREE_STATE, SLED_KEY_REGISTRATION))
     }
 
     fn save_state(&mut self, state: &Registered) -> Result<(), SledStoreError> {
-        self.insert(SLED_TREE_STATE, SLED_KEY_REGISTRATION, state)?;
+        self.insert(SLED_TREE_STATE, SLED_KEY_REGISTRATION, dbg!(state))?;
         Ok(())
     }
 
@@ -656,8 +659,10 @@ impl SledContactsIter {
     #[cfg(feature = "encryption")]
     fn decrypt_value<T: DeserializeOwned>(&self, value: &[u8]) -> Result<T, SledStoreError> {
         if let Some(cipher) = self.cipher.as_ref() {
+            log::trace!("Decrypt with cipher");
             Ok(cipher.decrypt_value(value)?)
         } else {
+            log::trace!("Decrypt no cipher");
             Ok(serde_json::from_slice(value)?)
         }
     }
diff --git a/presage/Cargo.toml b/presage/Cargo.toml
index f7dbc1f9b..16a46f186 100644
--- a/presage/Cargo.toml
+++ b/presage/Cargo.toml
@@ -6,8 +6,8 @@ authors = ["Gabriel FĂ©ron <g@leirbag.net>"]
 edition = "2021"
 
 [dependencies]
-libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "454d234" }
-libsignal-service-hyper = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "454d234" }
+libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "aa84243" }
+libsignal-service-hyper = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "aa84243" }
 
 base64 = "0.12"
 futures = "0.3"
diff --git a/presage/src/errors.rs b/presage/src/errors.rs
index 2507b0c2a..1d40da399 100644
--- a/presage/src/errors.rs
+++ b/presage/src/errors.rs
@@ -66,6 +66,10 @@ pub enum Error<S: std::error::Error> {
     RequestingCodeForbidden(libsignal_service::push_service::RegistrationSessionMetadataResponse),
     #[error("Unverified registration session (i.e. wrong verification code)")]
     UnverifiedRegistrationSession,
+    #[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<S: StoreError> From<S> for Error<S> {
diff --git a/presage/src/lib.rs b/presage/src/lib.rs
index 60a9b29c4..ac38cd9ad 100644
--- a/presage/src/lib.rs
+++ b/presage/src/lib.rs
@@ -5,7 +5,9 @@ mod serde;
 mod store;
 
 pub use errors::Error;
-pub use manager::{Confirmation, Linking, Manager, Registered, Registration, RegistrationOptions};
+pub use manager::{
+    Confirmation, Linking, Manager, Registered, Registration, RegistrationOptions, RegistrationType,
+};
 pub use store::{ContentTimestamp, Store, StoreError, Thread};
 
 #[deprecated(note = "Please help use improve the prelude module instead")]
diff --git a/presage/src/manager.rs b/presage/src/manager.rs
index f06bdc529..5826357cd 100644
--- a/presage/src/manager.rs
+++ b/presage/src/manager.rs
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
 use url::Url;
 
 use libsignal_service::proto::EditMessage;
-use libsignal_service::push_service::{RegistrationMethod, VerificationTransport};
+use libsignal_service::push_service::{DeviceInfo, RegistrationMethod, VerificationTransport};
 use libsignal_service::{
     attachment_cipher::decrypt_in_place,
     cipher,
@@ -74,6 +74,12 @@ impl<Store, State: fmt::Debug> fmt::Debug for Manager<Store, State> {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum RegistrationType {
+    Primary,
+    Secondary,
+}
+
 #[derive(Clone, Serialize, Deserialize)]
 pub struct RegistrationOptions<'a> {
     pub signal_servers: SignalServers,
@@ -1287,6 +1293,53 @@ impl<C: Store> Manager<C, Registered> {
         }
     }
 
+    /// Returns how this client was registered, either as a primary or secondary device.
+    pub fn registration_type(&self) -> RegistrationType {
+        if self.state.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<C::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.push_service()?, Some(self.state.profile_key));
+        let store = &self.config_store;
+
+        account_manager
+            .link_device(secondary, store, credentials)
+            .await?;
+        Ok(())
+    }
+
+    /// As a primary device, unlink a secondary device.
+    pub async fn unlink_secondary(&self, device_id: i64) -> Result<(), Error<C::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.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<Vec<DeviceInfo>, Error<C::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.push_service()?.devices().await?)
+    }
+
     #[deprecated = "use Manager::contact_by_id"]
     pub fn get_contacts(
         &self,