mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
The gateway keeps one PairingStore per served profile, but every `/api/pairing` endpoint built the global one. An operator managing a named profile saw the wrong pending list, and approving wrote a grant into a whitelist their running gateway never consults — the user stays locked out while the UI shows them as approved. `_pairing_store(profile)` now resolves per profile and validates the name (400/404 on an unknown one). No `_profile_scope` needed: PairingStore resolves the profile's home itself, so nothing process-global is swapped across an await. Both GUIs had to change to match. The listing rides the query param — for the dashboard that meant deleting `pairing` from the "machine-global, must NOT be rewritten" exclusion list, a comment this change makes false. The mutating endpoints read the profile off the BODY, which no query-param rewrite reaches, so approve/revoke send it explicitly on both surfaces.
43 lines
1.6 KiB
TypeScript
43 lines
1.6 KiB
TypeScript
// Pairing writes must target the profile the user is actually looking at.
|
|
// The approve/revoke endpoints read `profile` off the BODY (a POST body is
|
|
// not touched by query-param scoping), so a request that only carried
|
|
// `profileScoped()` at the top level would approve into the default
|
|
// profile's whitelist while the operator was managing another one — a grant
|
|
// the running gateway for that profile never consults.
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const api = vi.fn().mockResolvedValue({ ok: true })
|
|
|
|
vi.stubGlobal('window', { hermesDesktop: { api } })
|
|
|
|
describe('pairing requests carry the active profile', () => {
|
|
beforeEach(() => api.mockClear())
|
|
|
|
it('scopes approve and revoke by body, and the listing by query', async () => {
|
|
const mod = await import('@/hermes')
|
|
mod.setApiRequestProfile('work')
|
|
|
|
await mod.approvePairing('telegram', 'a'.repeat(16))
|
|
await mod.revokePairing('telegram', 'U1')
|
|
await mod.getPairing()
|
|
|
|
const [approve, revoke, list] = api.mock.calls.map(call => call[0])
|
|
|
|
expect(approve.body.profile).toBe('work')
|
|
expect(revoke.body.profile).toBe('work')
|
|
expect(list.profile).toBe('work')
|
|
})
|
|
|
|
it('omits the profile entirely for single-profile users', async () => {
|
|
const mod = await import('@/hermes')
|
|
mod.setApiRequestProfile(null)
|
|
|
|
await mod.approvePairing('telegram', 'a'.repeat(16))
|
|
await mod.getPairing()
|
|
|
|
const [approve, list] = api.mock.calls.map(call => call[0])
|
|
|
|
expect(approve.body.profile).toBeUndefined()
|
|
expect(list.profile).toBeUndefined()
|
|
})
|
|
})
|