diff --git a/src/routes/invitations.rs b/src/routes/invitations.rs index 9f4c80b..fe6a1c0 100644 --- a/src/routes/invitations.rs +++ b/src/routes/invitations.rs @@ -182,7 +182,7 @@ pub async fn accept_invitation( Some(user_email.clone()), Some(json!({ "role": role, "via": "invitation" })), )); - notifier.notify_membership_changed(auth.0); + notifier.notify_membership_changed(auth.0, team_id, true); notify_team_members_changed(&pool, ¬ifier, team_id).await; Ok(StatusCode::NO_CONTENT) } @@ -298,7 +298,7 @@ pub async fn accept_my_pending_invitation( user_email, Some(json!({ "role": role, "via": "in_app_invite" })), )); - notifier.notify_membership_changed(auth.0); + notifier.notify_membership_changed(auth.0, team_id, true); notify_team_members_changed(&pool, ¬ifier, team_id).await; Ok(StatusCode::NO_CONTENT) } diff --git a/src/routes/sync.rs b/src/routes/sync.rs index 2534017..6bde32a 100644 --- a/src/routes/sync.rs +++ b/src/routes/sync.rs @@ -310,8 +310,12 @@ pub async fn sync_stream( Ok(SyncEvent::BlobPushed { user_id: uid, device_id }) if uid == user_id => { Some(Ok(Event::default().data(device_id))) } - Ok(SyncEvent::MembershipChanged { user_id: uid }) if uid == user_id => { - Some(Ok(Event::default().data("membership_changed"))) + Ok(SyncEvent::MembershipChanged { user_id: uid, team_id, added }) if uid == user_id => { + let kind = if added { "added" } else { "removed" }; + Some(Ok(Event::default().data(format!("membership_changed:{}:{}", kind, team_id)))) + } + Ok(SyncEvent::VaultKeyChanged { user_id: uid }) if uid == user_id => { + Some(Ok(Event::default().data("vault_key_changed"))) } Ok(SyncEvent::PresenceChanged { recipient, subject, online }) if recipient == user_id => { let status = if online { "online" } else { "offline" }; diff --git a/src/routes/team_grants.rs b/src/routes/team_grants.rs index bd1c8e6..2874e9d 100644 --- a/src/routes/team_grants.rs +++ b/src/routes/team_grants.rs @@ -385,7 +385,7 @@ pub async fn redeem_grant( // The joiner's own devices refetch their team list; every member — the // joiner included — gets `team_members:`, which is the event an // online key-holder's reconcileTeamVaultKeys listens for. - notifier.notify_membership_changed(auth.0); + notifier.notify_membership_changed(auth.0, locked.team_id, true); notify_team_members_changed(&pool, ¬ifier, locked.team_id).await; Ok(Json(response)) @@ -1023,7 +1023,7 @@ mod tests { { roster_events.push(user_id) } - SyncEvent::MembershipChanged { user_id } => membership_events.push(user_id), + SyncEvent::MembershipChanged { user_id, .. } => membership_events.push(user_id), _ => {} } } diff --git a/src/routes/team_sync.rs b/src/routes/team_sync.rs index c16b071..1b4d62d 100644 --- a/src/routes/team_sync.rs +++ b/src/routes/team_sync.rs @@ -409,7 +409,7 @@ pub async fn put_vault_keys( info!(team_id = %team_id, upserter = %auth.0, key_count = body.keys.len(), "Vault keys upserted"); for user_id in vault_key_notification_targets(auth.0, &body.keys) { - sync_notifier.notify_membership_changed(user_id); + sync_notifier.notify_vault_key_changed(user_id); } Ok(StatusCode::NO_CONTENT) } @@ -525,7 +525,7 @@ pub async fn rotate_vault_key( info!(team_id = %team_id, rotated_by = %auth.0, new_epoch = next_version, member_count = body.keys.len(), "Team vault key rotated"); for user_id in vault_key_notification_targets(auth.0, &body.keys) { - sync_notifier.notify_membership_changed(user_id); + sync_notifier.notify_vault_key_changed(user_id); } Ok(StatusCode::NO_CONTENT) } diff --git a/src/routes/teams.rs b/src/routes/teams.rs index 7a2a498..1741b07 100644 --- a/src/routes/teams.rs +++ b/src/routes/teams.rs @@ -583,7 +583,7 @@ pub async fn remove_member( removed_display_name, None, )); - notifier.notify_membership_changed(user_id); + notifier.notify_membership_changed(user_id, team_id, false); notify_team_members_changed(&pool, ¬ifier, team_id).await; Ok(StatusCode::NO_CONTENT) } @@ -629,7 +629,7 @@ pub async fn delete_team( info!(team_id = %team_id, deleted_by = %auth.0, "Team deleted by owner"); for member_id in member_ids { - notifier.notify_membership_changed(member_id); + notifier.notify_membership_changed(member_id, team_id, false); } Ok(StatusCode::NO_CONTENT) } diff --git a/src/sync_notifier.rs b/src/sync_notifier.rs index 81dfe95..6e17067 100644 --- a/src/sync_notifier.rs +++ b/src/sync_notifier.rs @@ -8,7 +8,9 @@ pub enum SyncEvent { /// Another device pushed a blob for this user. BlobPushed { user_id: Uuid, device_id: String }, /// The user's team membership changed (added to or removed from a team). - MembershipChanged { user_id: Uuid }, + MembershipChanged { user_id: Uuid, team_id: Uuid, added: bool }, + /// A vault key was (re)wrapped for a user who was already a member. + VaultKeyChanged { user_id: Uuid }, /// A teammate's online/offline status changed. `recipient` is who should receive it. PresenceChanged { recipient: Uuid, @@ -52,8 +54,13 @@ impl SyncNotifier { }); } - pub fn notify_membership_changed(&self, user_id: Uuid) { - let _ = self.0.tx.send(SyncEvent::MembershipChanged { user_id }); + pub fn notify_membership_changed(&self, user_id: Uuid, team_id: Uuid, added: bool) { + let _ = self.0.tx.send(SyncEvent::MembershipChanged { user_id, team_id, added }); + } + + /// Not a membership change — see `SyncEvent::VaultKeyChanged`. + pub fn notify_vault_key_changed(&self, user_id: Uuid) { + let _ = self.0.tx.send(SyncEvent::VaultKeyChanged { user_id }); } /// Tell this user's own devices to refetch `/my/pending-invitations`, after