Summary
handleGCInvite rejects any incoming RMGroupInvite for a group chat that
already exists in the local DB - it persists nothing and fires no notification,
only logging an error. This makes it impossible to recover a stale local GC
copy: after a user restores an old backup (or otherwise holds an out-of-date
local copy), an admin re-adds them by re-inviting, but every re-invite is
silently dropped, so the user can never accept and rejoin.
Details
client/client_groupchat.go, in handleGCInvite:
_, err = c.db.GetGC(tx, invite.ID)
if !errors.Is(err, clientdb.ErrNotFound) {
if err == nil {
err = fmt.Errorf("can't accept gc invite: gc %q already exists",
invite.ID.String())
}
return err
}
When GetGC finds the GC locally, the early return skips both
AddGCInvite (persist the pending invite) and notifyInvitedToGC (the
OnInvitedToGCNtfn). The user never sees the invite.
Impact
A user whose local GC copy is stale - common after a backup/restore where the
live roster has since dropped them - cannot be re-added: the admin's re-invite
is dropped, so there is no way to accept and refresh the local copy.
Why storing the invite when the GC exists is safe
Accepting the invite triggers the normal join -> RMGroupList flow, and
handleGCList already routes an existing GC to maybeUpdateGC, which refreshes
the roster while validating the sender's admin permission against the local
metadata and rejecting generation backtracks. So storing the invite simply
unblocks the existing, safe recovery path. A forged invite from a non-admin can
at most surface a notification (the invite path has never authenticated the
inviter - same as for new GCs) and cannot alter the GC.
Suggested fix
Only fail handleGCInvite on a real DB error; store the invite and notify even
when the GC already exists. I have a patch that passes the existing GC e2e suite
and can open a PR.
Version
Present on master and v0.2.4.
Summary
handleGCInviterejects any incomingRMGroupInvitefor a group chat thatalready exists in the local DB - it persists nothing and fires no notification,
only logging an error. This makes it impossible to recover a stale local GC
copy: after a user restores an old backup (or otherwise holds an out-of-date
local copy), an admin re-adds them by re-inviting, but every re-invite is
silently dropped, so the user can never accept and rejoin.
Details
client/client_groupchat.go, inhandleGCInvite:When
GetGCfinds the GC locally, the early return skips bothAddGCInvite(persist the pending invite) andnotifyInvitedToGC(theOnInvitedToGCNtfn). The user never sees the invite.Impact
A user whose local GC copy is stale - common after a backup/restore where the
live roster has since dropped them - cannot be re-added: the admin's re-invite
is dropped, so there is no way to accept and refresh the local copy.
Why storing the invite when the GC exists is safe
Accepting the invite triggers the normal join ->
RMGroupListflow, andhandleGCListalready routes an existing GC tomaybeUpdateGC, which refreshesthe roster while validating the sender's admin permission against the local
metadata and rejecting generation backtracks. So storing the invite simply
unblocks the existing, safe recovery path. A forged invite from a non-admin can
at most surface a notification (the invite path has never authenticated the
inviter - same as for new GCs) and cannot alter the GC.
Suggested fix
Only fail
handleGCInviteon a real DB error; store the invite and notify evenwhen the GC already exists. I have a patch that passes the existing GC e2e suite
and can open a PR.
Version
Present on
masterandv0.2.4.