fix(desktop): keep cookie partitions colon-free so Windows jars work
Electron escapes ':' in a session partition name as '%3A' for the on-disk profile folder. On Windows, a profile folder whose name contains '%3A' gets a cookie store the network stack can neither read nor write: a jar placed there reads back zero cookies, `cookies.set()` never reaches disk, and every request goes out with no cookies at all — the gateway answers 401 `no_cookie` and the desktop concludes the user is signed out. Per-connection partitions (#92183) are the only ones carrying a colon beyond the 'persist:' prefix, so every NON-primary cookie-auth remote hits this: the dial mints no ticket, the reauth copy fires ("Remote Hermes gateway uses OAuth, but you are not signed in…"), and the login window — riding the same partition — writes into the same invisible jar. Nothing ever persists, so the prompt returns on every connect. The registry primary and the v1 remote keep the legacy `persist:hermes-remote-oauth` jar, whose folder has no '%3A', which is why a single-gateway setup never shows the symptom. Verified against the app's own Electron runtime (40.10.2, Windows): identical jar bytes seeded into two partition folders read 3 cookies and mint a ws-ticket (200) from `…-conn-…`, and 0 cookies / 401 from `…%3Aconn%3A…`. Keep the partition path component inside [A-Za-z0-9._-]; a regression test pins that it never contains ':' or '%'.
This commit is contained in:
@@ -131,6 +131,24 @@ describe('resolveOauthPartition (#92183 per-connection cookie jars)', () => {
|
||||
expect(resolveOauthPartition('https://gw-a.example.com', { registry: reg })).toBe(got)
|
||||
})
|
||||
|
||||
it('keeps the on-disk path component colon-free and %-free (Windows cookie-store regression)', () => {
|
||||
// Electron escapes ':' in a partition name to '%3A' for the folder name.
|
||||
// A Windows profile folder containing '%3A' gets a cookie store that reads
|
||||
// empty and never persists, so every cookie-auth connection would 401 and
|
||||
// re-prompt for sign-in on each dial. The partition path component must
|
||||
// therefore never need escaping, for ANY connection id.
|
||||
for (const id of ['10-0-0-88-9119', 'we ird/id:€', 'a:b/c%3Ad']) {
|
||||
const reg = registry('local', [remote(id, 'https://gw-a.example.com')])
|
||||
|
||||
const pathComponent = resolveOauthPartition('https://gw-a.example.com/api/auth/ws-ticket', {
|
||||
registry: reg
|
||||
}).slice('persist:'.length)
|
||||
|
||||
expect(pathComponent).not.toContain(':')
|
||||
expect(pathComponent).not.toContain('%')
|
||||
}
|
||||
})
|
||||
|
||||
it('breaks same-URL ties deterministically (identical jar for identical gateway)', () => {
|
||||
const reg = registry('local', [
|
||||
remote('zeta', 'https://gw-a.example.com'),
|
||||
|
||||
@@ -36,7 +36,15 @@
|
||||
|
||||
export const LEGACY_OAUTH_PARTITION = 'persist:hermes-remote-oauth'
|
||||
|
||||
const CONNECTION_PARTITION_PREFIX = `${LEGACY_OAUTH_PARTITION}:conn:`
|
||||
// Colon-free ON PURPOSE: Electron escapes ':' in a partition name to '%3A' in
|
||||
// the on-disk profile folder, and a Windows profile folder whose name contains
|
||||
// '%3A' gets a cookie store the network stack can neither read nor write (a
|
||||
// jar seeded into it reads back zero cookies, `cookies.set()` never reaches
|
||||
// disk, and every cookie-authenticated request 401s as `no_cookie`). A jar
|
||||
// the app cannot see means the dial always looks signed-out and the user is
|
||||
// asked to sign in again on every connect. Keep this path component to
|
||||
// [A-Za-z0-9._-] — never a character Electron has to escape.
|
||||
const CONNECTION_PARTITION_PREFIX = `${LEGACY_OAUTH_PARTITION}-conn-`
|
||||
|
||||
export interface PartitionRegistrySnapshot {
|
||||
primary?: unknown
|
||||
|
||||
Reference in New Issue
Block a user