From 529ffda4ed0e3b2263e898dbb57ca7bc56a06255 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 3 Dec 2024 23:36:03 +0200 Subject: [PATCH] main: add support for logging out --- desktop/main.go | 4 +- pkg/gomuks/gomuks.go | 5 +- pkg/gomuks/logout.go | 64 +++++++++++++++++++++++++ pkg/hicli/hicli.go | 11 +++++ pkg/hicli/json-commands.go | 5 ++ web/src/api/client.ts | 6 +++ web/src/api/rpc.ts | 4 ++ web/src/api/statestore/main.ts | 13 +++++ web/src/ui/login/BeeperLogin.tsx | 2 +- web/src/ui/login/VerificationScreen.tsx | 2 +- web/src/ui/settings/SettingsView.css | 10 ++++ web/src/ui/settings/SettingsView.tsx | 9 ++++ 12 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 pkg/gomuks/logout.go diff --git a/desktop/main.go b/desktop/main.go index 928eb59..f953eca 100644 --- a/desktop/main.go +++ b/desktop/main.go @@ -148,9 +148,9 @@ func main() { URL: "/", }) - gmx.Client.EventHandler = hicli.JSONEventHandler(func(command *hicli.JSONCommand) { + gmx.SubscribeEvents(nil, func(command *hicli.JSONCommand) { app.EmitEvent("hicli_event", command) - }).HandleEvent + }) err = app.Run() if err != nil { diff --git a/pkg/gomuks/gomuks.go b/pkg/gomuks/gomuks.go index 5044652..d294943 100644 --- a/pkg/gomuks/gomuks.go +++ b/pkg/gomuks/gomuks.go @@ -178,6 +178,7 @@ func (gmx *Gomuks) StartClient() { []byte("meow"), hicli.JSONEventHandler(gmx.OnEvent).HandleEvent, ) + gmx.Client.LogoutFunc = gmx.Logout httpClient := gmx.Client.Client.Client httpClient.Transport.(*http.Transport).ForceAttemptHTTP2 = false if !gmx.Config.Matrix.DisableHTTP2 { @@ -246,7 +247,9 @@ func (gmx *Gomuks) SubscribeEvents(closeForRestart WebsocketCloseFunc, cb func(c gmx.nextListenerID++ id := gmx.nextListenerID gmx.eventListeners[id] = cb - gmx.websocketClosers[id] = closeForRestart + if closeForRestart != nil { + gmx.websocketClosers[id] = closeForRestart + } return func() { gmx.eventListenersLock.Lock() defer gmx.eventListenersLock.Unlock() diff --git a/pkg/gomuks/logout.go b/pkg/gomuks/logout.go new file mode 100644 index 0000000..c45de23 --- /dev/null +++ b/pkg/gomuks/logout.go @@ -0,0 +1,64 @@ +// gomuks - A Matrix client written in Go. +// Copyright (C) 2024 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gomuks + +import ( + "context" + "errors" + "os" + "path/filepath" + + "github.com/rs/zerolog" + "maunium.net/go/mautrix" +) + +func (gmx *Gomuks) Logout(ctx context.Context) error { + log := zerolog.Ctx(ctx) + log.Info().Msg("Stopping client and logging out") + gmx.Client.Stop() + _, err := gmx.Client.Client.Logout(ctx) + if err != nil && !errors.Is(err, mautrix.MUnknownToken) { + log.Warn().Err(err).Msg("Failed to log out") + return err + } + log.Info().Msg("Logout complete, removing data") + err = os.RemoveAll(gmx.CacheDir) + if err != nil { + log.Err(err).Str("cache_dir", gmx.CacheDir).Msg("Failed to remove cache dir") + } + if gmx.DataDir == gmx.ConfigDir { + err = os.Remove(filepath.Join(gmx.DataDir, "gomuks.db")) + if err != nil && !errors.Is(err, os.ErrNotExist) { + log.Err(err).Str("data_dir", gmx.DataDir).Msg("Failed to remove database") + } + _ = os.Remove(filepath.Join(gmx.DataDir, "gomuks.db-shm")) + _ = os.Remove(filepath.Join(gmx.DataDir, "gomuks.db-wal")) + } else { + err = os.RemoveAll(gmx.DataDir) + if err != nil { + log.Err(err).Str("data_dir", gmx.DataDir).Msg("Failed to remove data dir") + } + } + log.Info().Msg("Re-initializing directories") + gmx.InitDirectories() + log.Info().Msg("Restarting client") + gmx.StartClient() + gmx.Client.EventHandler(gmx.Client.State()) + gmx.Client.EventHandler(gmx.Client.SyncStatus.Load()) + log.Info().Msg("Client restarted") + return nil +} diff --git a/pkg/hicli/hicli.go b/pkg/hicli/hicli.go index 371010c..aa8e5e9 100644 --- a/pkg/hicli/hicli.go +++ b/pkg/hicli/hicli.go @@ -32,6 +32,7 @@ import ( type HiClient struct { DB *database.Database + CryptoDB *dbutil.Database Account *database.Account Client *mautrix.Client Crypto *crypto.OlmMachine @@ -50,6 +51,7 @@ type HiClient struct { lastSync time.Time EventHandler func(evt any) + LogoutFunc func(context.Context) error firstSyncReceived bool syncingID int @@ -90,6 +92,9 @@ func New(rawDB, cryptoDB *dbutil.Database, log zerolog.Logger, pickleKey []byte, EventHandler: evtHandler, } + if cryptoDB != rawDB { + c.CryptoDB = cryptoDB + } c.SyncStatus.Store(syncWaiting) c.ClientStore = &database.ClientStateStore{Database: db} c.Client = &mautrix.Client{ @@ -267,4 +272,10 @@ func (h *HiClient) Stop() { if err != nil { h.Log.Err(err).Msg("Failed to close database cleanly") } + if h.CryptoDB != nil { + err = h.CryptoDB.Close() + if err != nil { + h.Log.Err(err).Msg("Failed to close crypto database cleanly") + } + } } diff --git a/pkg/hicli/json-commands.go b/pkg/hicli/json-commands.go index ce33f18..7028bde 100644 --- a/pkg/hicli/json-commands.go +++ b/pkg/hicli/json-commands.go @@ -114,6 +114,11 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any return unmarshalAndCall(req.Data, func(params *resolveAliasParams) (*mautrix.RespAliasResolve, error) { return h.Client.ResolveAlias(ctx, params.Alias) }) + case "logout": + if h.LogoutFunc == nil { + return nil, errors.New("logout not supported") + } + return true, h.LogoutFunc(ctx) case "login": return unmarshalAndCall(req.Data, func(params *loginParams) (bool, error) { return true, h.LoginPassword(ctx, params.HomeserverURL, params.Username, params.Password) diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 8d54254..63b30e1 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -293,4 +293,10 @@ export default class Client { room.paginating = false } } + + async logout() { + await this.rpc.logout() + localStorage.clear() + this.store.clear() + } } diff --git a/web/src/api/rpc.ts b/web/src/api/rpc.ts index a84bbc3..e0be430 100644 --- a/web/src/api/rpc.ts +++ b/web/src/api/rpc.ts @@ -129,6 +129,10 @@ export default abstract class RPCClient { }, this.cancelRequest.bind(this, request_id)) } + logout(): Promise { + return this.request("logout", {}) + } + sendMessage(params: SendMessageParams): Promise { return this.request("send_message", params) } diff --git a/web/src/api/statestore/main.ts b/web/src/api/statestore/main.ts index 4f064b8..fc77e6a 100644 --- a/web/src/api/statestore/main.ts +++ b/web/src/api/statestore/main.ts @@ -386,4 +386,17 @@ export class StateStore { } return { deletedEvents, deletedState } as const } + + clear() { + this.rooms.clear() + this.roomList.emit([]) + this.accountData.clear() + this.currentRoomListFilter = "" + this.#frequentlyUsedEmoji = null + this.#emojiPackKeys = null + this.#watchedRoomEmojiPacks = null + this.#personalEmojiPack = null + this.serverPreferenceCache = {} + this.activeRoomID = undefined + } } diff --git a/web/src/ui/login/BeeperLogin.tsx b/web/src/ui/login/BeeperLogin.tsx index 2b97e8a..ac0bc90 100644 --- a/web/src/ui/login/BeeperLogin.tsx +++ b/web/src/ui/login/BeeperLogin.tsx @@ -82,7 +82,7 @@ const BeeperLogin = ({ domain, client }: BeeperLoginProps) => { onChange={onChangeCode} />} {error &&
diff --git a/web/src/ui/login/VerificationScreen.tsx b/web/src/ui/login/VerificationScreen.tsx index 2090060..d407f21 100644 --- a/web/src/ui/login/VerificationScreen.tsx +++ b/web/src/ui/login/VerificationScreen.tsx @@ -44,7 +44,7 @@ export const VerificationScreen = ({ client, clientState }: LoginScreenProps) => value={recoveryKey} onChange={evt => setRecoveryKey(evt.target.value)} /> - + {error &&
{error} diff --git a/web/src/ui/settings/SettingsView.css b/web/src/ui/settings/SettingsView.css index 8791fa2..61ae52e 100644 --- a/web/src/ui/settings/SettingsView.css +++ b/web/src/ui/settings/SettingsView.css @@ -72,4 +72,14 @@ div.settings-view { } } } + + button.logout { + margin-top: 1rem; + padding: .5rem 1rem; + + &:hover, &:focus { + background-color: var(--error-color); + color: var(--inverted-text-color); + } + } } diff --git a/web/src/ui/settings/SettingsView.tsx b/web/src/ui/settings/SettingsView.tsx index afc9afa..0c1d3fe 100644 --- a/web/src/ui/settings/SettingsView.tsx +++ b/web/src/ui/settings/SettingsView.tsx @@ -259,6 +259,14 @@ const SettingsView = ({ room }: SettingsViewProps) => { } } }, [client, room]) + const onClickLogout = useCallback(() => { + if (window.confirm("Really log out and delete all local data?")) { + client.logout().then( + () => console.info("Successfully logged out"), + err => window.alert(`Failed to log out: ${err}`), + ) + } + }, [client]) usePreferences(client.store, room) const globalServer = client.store.serverPreferenceCache const globalLocal = client.store.localPreferenceCache @@ -293,6 +301,7 @@ const SettingsView = ({ room }: SettingsViewProps) => { + }