Skip to content

Commit

Permalink
Link secondary devices as primary device (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schmiddiii authored Jun 8, 2024
1 parent 0c46be2 commit 5bf74db
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 6 deletions.
4 changes: 3 additions & 1 deletion presage-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,9 @@ async fn run<S: Store>(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}"),
}
Expand Down
4 changes: 2 additions & 2 deletions presage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ edition = "2021"
license = "AGPL-3.0-only"

[dependencies]
libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "26c036e" }
libsignal-service-hyper = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "26c036e" }
libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "1e04a655c62271ffdfe70b053a6150e0cc2cb155" }
libsignal-service-hyper = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "1e04a655c62271ffdfe70b053a6150e0cc2cb155" }

base64 = "0.21"
futures = "0.3"
Expand Down
2 changes: 2 additions & 0 deletions presage/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ pub enum Error<S: std::error::Error> {
UnverifiedRegistrationSession,
#[error("profile cipher error")]
ProfileCipherError(#[from] libsignal_service::profile_cipher::ProfileCipherError),
#[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> {
Expand Down
2 changes: 1 addition & 1 deletion presage/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 71 additions & 2 deletions presage/src/manager/registered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use libsignal_service::proto::{
use libsignal_service::protocol::{IdentityKeyStore, SenderCertificate};
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};
Expand All @@ -42,6 +42,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;
Expand All @@ -51,6 +52,12 @@ use crate::{AvatarBytes, Error, Manager};
type ServiceCipher<S> = cipher::ServiceCipher<S, StdRng>;
type MessageSender<S> = libsignal_service::prelude::MessageSender<HyperPushService, S, StdRng>;

#[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 {
Expand Down Expand Up @@ -1216,6 +1223,68 @@ impl<S: Store> Manager<S, Registered> {
}
}

/// 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<S::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.aci_protocol_store(),
&store.pni_protocol_store(),
credentials,
)
.await?;
Ok(())
}

/// As a primary device, unlink a secondary device.
pub async fn unlink_secondary(&self, device_id: i64) -> Result<(), Error<S::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 (uncluding the current device).
pub async fn devices(&self) -> Result<Vec<DeviceInfo>, Error<S::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::<S::Error>::NotPrimaryDevice);
}

let aci_protocol_store = self.store.aci_protocol_store();
let mut account_manager = AccountManager::new(
self.identified_push_service(),
Some(self.state.data.profile_key),
);

Ok(account_manager.linked_devices(&aci_protocol_store).await?)
}

/// Deprecated methods

/// Get a single contact by its UUID
Expand Down

0 comments on commit 5bf74db

Please sign in to comment.