Merge pull request #434 from naturallaw777/fix/hub-logout-persistence

fix(hub): persistent browser profile so logout survives window reopen
This commit is contained in:
Sovran Systems
2026-08-15 17:24:14 -05:00
committed by GitHub
3 changed files with 61 additions and 12 deletions
+8 -4
View File
@@ -17,10 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Sovran Hub launcher runs it.
### Fixed
- The Hub launcher now passes `--skip-origin-startup-dialog` to Brave
Origin so its one-time "Proceed with Origin for free on Linux" onboarding
screen doesn't reappear (and block auto-login) on every launch, since the
Hub runs with a throwaway profile.
- The Hub launcher now uses a persistent per-user browser profile
(`$XDG_STATE_HOME/sovran-hub-browser`) instead of a throwaway `/tmp`
profile that was deleted on exit. The throwaway profile wiped the
`hub_manual_logout` marker cookie on every close, so `/auto-login`
silently logged the user straight back in after they signed out and
reopened the Hub window. The persistent profile makes explicit logout
stick (until the next password login) while still skipping Brave Origin's
one-time startup dialog.
- The Hub now redirects to the login page when an API request finds an
expired browser session, instead of leaving the dashboard tile grid in a
loading state while `/api/services` continues returning 401 every five
+18 -8
View File
@@ -282,19 +282,29 @@ let
fi
'';
# ── Brave Origin launcher wrapper: stable profile dir so Wayland app_id is
# deterministic and GNOME Shell can match the window to the .desktop
# entry (fixes generic gear icon appearing in the dock).
# ── Brave Origin launcher wrapper: a *persistent* per-user profile dir.
# It must NOT be wiped on exit: the Hub's logout marker cookie
# (hub_manual_logout) and the session cookie live in this profile.
# Launching from a fresh/ephemeral profile every time throws away the
# marker, so /auto-login would mint a new session and silently log the
# user straight back in after they signed out and reopened the window.
# A stable directory also keeps the Wayland app_id deterministic so
# GNOME Shell can match the window to the .desktop entry (dock icon).
hub-brave-wrapper = pkgs.writeShellScript "sovran-hub-brave.sh" ''
export PATH="${lib.makeBinPath [ pkgs.brave-origin pkgs.coreutils ]}:$PATH"
HUB_DATA="/tmp/sovran-hub-brave-$(id -u)"
# Per-user, persistent browser state. $XDG_STATE_HOME keeps it out of the
# way of backups and survives reboots and window close/reopen.
if [ -n "$XDG_STATE_HOME" ]; then
HUB_DATA="$XDG_STATE_HOME/sovran-hub-browser"
else
HUB_DATA="$HOME/.local/state/sovran-hub-browser"
fi
mkdir -p "$HUB_DATA"
trap '[ -n "$HUB_DATA" ] && rm -rf "$HUB_DATA"' EXIT INT TERM
export BAMF_DESKTOP_FILE_HINT="/run/current-system/sw/share/applications/sovran-hub.desktop"
export GIO_LAUNCHED_DESKTOP_FILE="/run/current-system/sw/share/applications/sovran-hub.desktop"
# The Hub launches with a throwaway profile, so Brave Origin's one-time
# "Proceed with Origin for free on Linux" onboarding dialog would reappear
# on every launch and block the auto-login. Skip it (Linux-only switch).
# With a persistent profile Brave Origin's one-time "Proceed with Origin
# for free on Linux" onboarding dialog only appears once; keep skipping it
# anyway so it can never block auto-login (Linux-only switch).
brave-origin --app=http://localhost:8937/auto-login \
--skip-origin-startup-dialog \
--class=sovran-hub \
+35
View File
@@ -424,6 +424,41 @@ class TestManualLogoutPersistence(unittest.TestCase):
)
class TestHubBrowserProfilePersistence(unittest.TestCase):
"""The desktop launcher must keep a persistent browser profile.
The hub_manual_logout marker (and the session cookie) are stored in this
profile. If the launcher used an ephemeral /tmp profile that it deleted on
exit, closing and reopening the Hub window would wipe the marker and
/auto-login would silently log the user back in without a password.
"""
@classmethod
def setUpClass(cls):
path = os.path.join(
_REPO_ROOT, "modules", "core", "sovran-hub.nix"
)
with open(path, encoding="utf-8") as f:
cls.wrapper = f.read()
def test_profile_is_not_under_tmp(self):
# The profile must live in a persistent per-user location, not /tmp.
self.assertNotIn("/tmp/sovran-hub-brave", self.wrapper)
def test_profile_is_not_deleted_on_exit(self):
# There must be no trap that removes the user-data-dir on exit.
self.assertNotRegex(self.wrapper, r"rm\s+-rf\s+.*HUB_DATA")
self.assertNotIn("trap '", self.wrapper)
def test_profile_is_persistent_per_user_location(self):
self.assertIn("sovran-hub-browser", self.wrapper)
# It should honour XDG_STATE_HOME (standard, persistent per-user dir).
self.assertIn("XDG_STATE_HOME", self.wrapper)
def test_launcher_still_uses_user_data_dir(self):
self.assertIn("--user-data-dir=", self.wrapper)
# ---------------------------------------------------------------------------
# Persistent session store
# ---------------------------------------------------------------------------