Skip to content
This repository has been archived by the owner on Nov 13, 2024. It is now read-only.

Commit

Permalink
Merge branch 'local-origin/logout-bridge'
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFerr committed Feb 14, 2024
2 parents ffc1efb + ff16ef4 commit e060c40
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 30 deletions.
30 changes: 26 additions & 4 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func (br *SignalBridge) RegisterCommands() {
proc.AddHandlers(
cmdPing,
cmdLogin,
cmdLogout,
cmdSetDeviceName,
cmdPM,
cmdResolvePhone,
Expand Down Expand Up @@ -137,7 +138,7 @@ func fnDeleteSession(ce *WrappedCommandEvent) {
ce.Reply("You're not logged in")
return
}
ce.User.Client.ClearKeysAndDisconnect(ce.Ctx)
ce.User.clearKeysAndDisconnect(ce.Ctx)
ce.Reply("Disconnected from Signal")
}

Expand All @@ -151,10 +152,8 @@ var cmdPing = &commands.FullHandler{
}

func fnPing(ce *WrappedCommandEvent) {
if ce.User.SignalID == uuid.Nil {
if ce.User.SignalID == uuid.Nil || !ce.User.IsLoggedIn() {
ce.Reply("You're not logged in")
} else if !ce.User.IsLoggedIn() {
ce.Reply("You were logged in at some point, but are not anymore")
} else if !ce.User.Client.IsConnected() {
ce.Reply("You're logged into Signal, but not connected to the server")
} else {
Expand Down Expand Up @@ -438,6 +437,29 @@ func fnLogin(ce *WrappedCommandEvent) {
ce.Reply("Successfully logged in as %s (UUID: %s)", ce.User.SignalUsername, ce.User.SignalID)
}

var cmdLogout = &commands.FullHandler{
Func: wrapCommand(fnLogout),
Name: "logout",
Help: commands.HelpMeta{
Section: commands.HelpSectionAuth,
Description: "Remove all local data about your Signal link",
},
}

func fnLogout(ce *WrappedCommandEvent) {
if !ce.User.IsLoggedIn() {
ce.Reply("You're not logged in.")
return
}
err := ce.User.Logout(ce.Ctx)
if err != nil {
ce.User.log.Warn().Err(err).Msg("Error while logging out")
ce.Reply("Error while logging out: %v", err)
return
}
ce.Reply("Logged out successfully.")
}

func (user *User) sendQR(ce *WrappedCommandEvent, code string, prevQR, prevMsg id.EventID) (qr, msg id.EventID) {
content, ok := user.uploadQR(ce, code)
if !ok {
Expand Down
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,13 @@ func (br *SignalBridge) Stop() {
br.Metrics.Stop()
for _, user := range br.usersByMXID {
br.ZLog.Debug().Stringer("user_id", user.MXID).Msg("Disconnecting user")
user.Disconnect()
err := user.Disconnect()
if err != nil {
br.ZLog.Debug().Err(err).
Str("action", "stop").
Stringer("user_id", user.MXID).
Msg("Error on disconnect")
}
}
close(br.activePuppetMetricLoopDone)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/signalmeow/store/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var _ DeviceStore = (*StoreContainer)(nil)

type DeviceStore interface {
PutDevice(ctx context.Context, dd *DeviceData) error
DeleteDevice(ctx context.Context, dd *DeviceData) error
DeviceByACI(ctx context.Context, aci uuid.UUID) (*Device, error)
}

Expand Down
10 changes: 10 additions & 0 deletions pkg/signalmeow/store/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,13 @@ func (d *Device) ClearPassword(ctx context.Context) error {
d.Password = ""
return d.DeviceStore.PutDevice(ctx, &d.DeviceData)
}

func (d *Device) DeleteDevice(ctx context.Context) error {
if err := d.DeviceStore.DeleteDevice(ctx, &d.DeviceData); err != nil {
return err
}
d.ACI = uuid.Nil
d.DeviceID = 0
d.Password = ""
return nil
}
49 changes: 44 additions & 5 deletions provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (prov *ProvisioningAPI) Init() {
r.HandleFunc("/v2/link/new", prov.LinkNew).Methods(http.MethodPost)
r.HandleFunc("/v2/link/wait/scan", prov.LinkWaitForScan).Methods(http.MethodPost)
r.HandleFunc("/v2/link/wait/account", prov.LinkWaitForAccount).Methods(http.MethodPost)
r.HandleFunc("/v2/delete_session", prov.DeleteSession).Methods(http.MethodPost)
r.HandleFunc("/v2/logout", prov.Logout).Methods(http.MethodPost)
r.HandleFunc("/v2/resolve_identifier/{phonenum}", prov.ResolveIdentifier).Methods(http.MethodGet)
r.HandleFunc("/v2/pm/{phonenum}", prov.StartPM).Methods(http.MethodPost)
Expand Down Expand Up @@ -666,20 +667,58 @@ func (prov *ProvisioningAPI) LinkWaitForAccount(w http.ResponseWriter, r *http.R
}
}

func (prov *ProvisioningAPI) DeleteSession(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(provisioningUserKey).(*User)
log := prov.log.With().
Str("action", "delete_session").
Stringer("user_id", user.MXID).
Logger()
ctx := log.WithContext(r.Context())
log.Debug().Msg("delete_session called")

if !user.IsLoggedIn() {
jsonResponse(w, http.StatusOK, Error{
Error: "You're not logged in",
ErrCode: "not logged in",
})
return
}

user.clearKeysAndDisconnect(ctx)
jsonResponse(w, http.StatusOK, Response{
Success: true,
Status: "disconnected",
})
}

func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(provisioningUserKey).(*User)
log := prov.log.With().
Str("action", "logout").
Stringer("user_id", user.MXID).
Logger()
ctx := log.WithContext(r.Context())
log.Debug().Msg("Logout called (but not logging out)")
log.Debug().Msg("Logout called")

prov.clearSession(ctx, user)
if !user.IsLoggedIn() {
jsonResponse(w, http.StatusOK, Error{
Error: "You're not logged in",
ErrCode: "not logged in",
})
return
}

err := user.Logout(ctx)
if err != nil {
user.log.Warn().Err(err).Msg("Error while logging out")
jsonResponse(w, http.StatusInternalServerError, Error{
Success: false,
Error: err.Error(),
ErrCode: "M_INTERNAL",
})
return
}

// For now do nothing - we need this API to return 200 to be compatible with
// the old Signal bridge, which needed a call to Logout before allowing LinkNew
// to be called, but we don't actually want to logout, we want to allow a reconnect.
jsonResponse(w, http.StatusOK, Response{
Success: true,
Status: "logged_out",
Expand Down
89 changes: 69 additions & 20 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ func (user *User) startupTryConnect(retryCount int) {
if errors.Is(err, ErrNotLoggedIn) {
user.log.Warn().Msg("Not logged in, clearing Signal device keys")
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: "You have been logged out of Signal, please reconnect"})
user.clearKeysAndDisconnect()
user.clearKeysAndDisconnect(ctx)
} else if retryCount < 6 {
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "unknown-websocket-error", Message: err.Error()})
retryInSeconds := 2 << retryCount
Expand Down Expand Up @@ -483,7 +483,13 @@ func (user *User) startupTryConnect(retryCount int) {
} else {
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: err.Error()})
}
user.clearKeysAndDisconnect()
err = user.Logout(ctx)
if err != nil {
user.log.Err(err).Msg("Error logging out user in response to remote logout")
user.clearKeysAndDisconnect(ctx)
user.Client = nil
user.handleLoggedOut(ctx)
}
if managementRoom := user.GetManagementRoomID(); managementRoom != "" {
_, _ = user.bridge.Bot.SendText(ctx, managementRoom, "You've been logged out of Signal")
}
Expand All @@ -504,10 +510,10 @@ func (user *User) startupTryConnect(retryCount int) {
}()
}

func (user *User) clearKeysAndDisconnect() {
func (user *User) clearKeysAndDisconnect(ctx context.Context) {
// We need to clear out keys associated with the Signal device that no longer has valid credentials
user.log.Debug().Msg("Clearing out Signal device keys")
err := user.Client.ClearKeysAndDisconnect(context.TODO())
err := user.Client.ClearKeysAndDisconnect(ctx)
if err != nil {
user.log.Err(err).Msg("Error clearing device keys")
}
Expand Down Expand Up @@ -574,18 +580,18 @@ func (user *User) saveSignalID(ctx context.Context, id uuid.UUID, number string)
if user.SignalID != id {
existingUser := user.bridge.unlockedGetUserBySignalID(id)
if existingUser != nil {
// TODO this doesn't clear the signal store properly
// the store also only has the uuid as primary key, even though it should have uuid + device id
// TODO the store has only the uuid as primary key, even though it should have uuid + device id
zerolog.Ctx(ctx).Warn().
Stringer("previous_user", existingUser.MXID).
Stringer("signal_uuid", id).
Msg("Another user is already logged in with same UUID, logging out previous user")
_ = existingUser.Disconnect()
existingUser.SignalID = uuid.Nil
existingUser.SignalUsername = ""
err := existingUser.Update(ctx)
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to clear previous user's signal UUID")
existingUser.Lock()
existingUser.clearKeysAndDisconnect(ctx)
existingUser.Client = nil
existingUser.handleLoggedOutNoLock(ctx)
existingUser.Unlock()
if managementRoom := existingUser.GetManagementRoomID(); managementRoom != "" {
_, _ = existingUser.bridge.Bot.SendText(ctx, managementRoom, "Another user of this bridge has logged into your Signal account")
}
}
}
Expand All @@ -608,11 +614,9 @@ func (user *User) populateSignalDevice() *signalmeow.Client {

if user.SignalID == uuid.Nil {
return nil
}
// TODO clear client on logout properly so that populating can skip creating if it already exists
/*else if user.Client != nil {
} else if user.Client != nil {
return user.Client
}*/
}

device, err := user.bridge.MeowStore.DeviceByACI(context.TODO(), user.SignalID)
if err != nil {
Expand Down Expand Up @@ -815,16 +819,61 @@ func (user *User) Disconnect() error {
return err
}

func (user *User) Logout() error {
func (user *User) Logout(ctx context.Context) error {
user.Lock()
defer user.Unlock()
user.log.Info().Msg("Logging out of session")
loggedOutDevice, err := user.disconnectNoLock()
user.bridge.MeowStore.DeleteDevice(context.TODO(), &loggedOutDevice.Store.DeviceData)
user.bridge.GetPuppetByCustomMXID(user.MXID).ClearCustomMXID()
err := user.deleteDevice(ctx)
if err != nil {
return err
}
_, err = user.disconnectNoLock()
if err != nil {
user.log.Debug().Err(err).Msg("Error on disconnect")
}
user.bridge.usersLock.Lock()
defer user.bridge.usersLock.Unlock()
user.handleLoggedOutNoLock(ctx)
return nil
}

func (user *User) deleteDevice(ctx context.Context) error {
var err error
if user.Client != nil {
err = user.Client.Store.DeleteDevice(ctx)
} else if user.SignalID == uuid.Nil {
user.log.Debug().Msg("client and signal ID are nil")
} else if device, deviceErr := user.bridge.MeowStore.DeviceByACI(ctx, user.SignalID); deviceErr != nil {
user.log.Debug().Msg("client is nil and did not find device in store")
} else if device != nil {
user.log.Debug().Msg("client is nil but found device in store")
err = device.DeleteDevice(ctx)
}
return err
}

func (user *User) handleLoggedOut(ctx context.Context) {
user.Lock()
defer user.Unlock()
user.bridge.usersLock.Lock()
defer user.bridge.usersLock.Unlock()
user.handleLoggedOutNoLock(ctx)
}

func (user *User) handleLoggedOutNoLock(ctx context.Context) {
id := user.SignalID
user.SignalID = uuid.Nil
user.SignalUsername = ""
if err := user.Update(ctx); err != nil {
user.log.Err(err).Msg("Failed to clear user's signal UUID on logout")
}
delete(user.bridge.usersBySignalID, id)
if puppet := user.GetIDoublePuppet(); puppet != nil {
puppet.ClearCustomMXID()
}
user.bridge.provisioning.clearSession(ctx, user)
}

func (user *User) UpdateDirectChats(ctx context.Context, chats map[id.UserID][]id.RoomID) {
if !user.bridge.Config.Bridge.SyncDirectChatList {
return
Expand Down

0 comments on commit e060c40

Please sign in to comment.