Files

145 lines
5.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { LEGACY_OAUTH_PARTITION, resolveOauthPartition } from './oauth-partition'
// #92183 — two basic-auth (cookie-flow) gateways registered in the v2
// connections registry must not share one cookie jar. Chromium cookie jars
// ignore the port, so two gateways on the same VPN host (different ports)
// evict each other's `hermes_session*` cookies when they ride the single
// shared `persist:hermes-remote-oauth` partition — and, worse, gateway A's
// cookie is silently PRESENTED to gateway B on every request. The resolver
// under test keys the jar on the registry connection's identity instead.
const registry = (primary: string, connections: any[]) => ({ primary, connections })
const remote = (id: string, url: string, extra: Record<string, unknown> = {}) => ({
id,
kind: 'remote',
label: id,
url,
authMode: 'oauth',
...extra
})
describe('resolveOauthPartition (#92183 per-connection cookie jars)', () => {
it('gives two same-host different-port registry gateways DISTINCT partitions (eviction fix)', () => {
const reg = registry('local', [
{ id: 'local', kind: 'local' },
remote('conn-a', 'https://10.27.27.7:9119'),
remote('conn-b', 'https://10.27.27.7:9220')
])
const a = resolveOauthPartition('https://10.27.27.7:9119', { registry: reg })
const b = resolveOauthPartition('https://10.27.27.7:9220', { registry: reg })
expect(a).not.toBe(LEGACY_OAUTH_PARTITION)
expect(b).not.toBe(LEGACY_OAUTH_PARTITION)
// Fail closed: B's requests must never ride a jar that can hold A's cookie.
expect(a).not.toBe(b)
})
it('scopes a full request URL (REST/ws-ticket path) to its connection jar via longest base-url prefix', () => {
const reg = registry('local', [
remote('conn-a', 'https://gw.example.com'),
remote('conn-b', 'https://gw.example.com/team-b')
])
const a = resolveOauthPartition('https://gw.example.com/api/auth/ws-ticket', { registry: reg })
const b = resolveOauthPartition('https://gw.example.com/team-b/api/auth/ws-ticket', { registry: reg })
expect(a).not.toBe(b)
expect(b).toContain('conn-b')
})
it('keeps the v1 primary remote on the LEGACY partition so upgrades do not sign the user out', () => {
const reg = registry('mig-1', [remote('mig-1', 'https://gw-a.example.com')])
expect(
resolveOauthPartition('https://gw-a.example.com', {
registry: reg,
v1RemoteUrl: 'https://gw-a.example.com'
})
).toBe(LEGACY_OAUTH_PARTITION)
})
it('keeps the registry PRIMARY connection on the legacy partition', () => {
const reg = registry('conn-a', [
remote('conn-a', 'https://gw-a.example.com'),
remote('conn-b', 'https://gw-b.example.com')
])
expect(resolveOauthPartition('https://gw-a.example.com/api/status', { registry: reg })).toBe(LEGACY_OAUTH_PARTITION)
expect(resolveOauthPartition('https://gw-b.example.com/api/status', { registry: reg })).not.toBe(
LEGACY_OAUTH_PARTITION
)
})
it('keeps cloud connections on the legacy partition (silent portal cascade needs the shared jar)', () => {
const reg = registry('local', [
{ id: 'cloud-1', kind: 'cloud', url: 'https://agent.nousresearch.com', authMode: 'oauth' }
])
expect(resolveOauthPartition('https://agent.nousresearch.com/api/status', { registry: reg })).toBe(
LEGACY_OAUTH_PARTITION
)
})
it('keeps token-auth registry remotes on the legacy partition (no cookies involved)', () => {
const reg = registry('local', [remote('tok-1', 'https://gw-t.example.com', { authMode: 'token' })])
expect(resolveOauthPartition('https://gw-t.example.com', { registry: reg })).toBe(LEGACY_OAUTH_PARTITION)
})
it('falls back to the legacy partition for unmatched, portal, and malformed inputs', () => {
const reg = registry('local', [remote('conn-a', 'https://gw-a.example.com')])
expect(resolveOauthPartition('https://portal.nousresearch.com/api/agents', { registry: reg })).toBe(
LEGACY_OAUTH_PARTITION
)
expect(resolveOauthPartition('not a url', { registry: reg })).toBe(LEGACY_OAUTH_PARTITION)
expect(resolveOauthPartition('', { registry: reg })).toBe(LEGACY_OAUTH_PARTITION)
expect(resolveOauthPartition('https://gw-a.example.com', { registry: null as any })).toBe(LEGACY_OAUTH_PARTITION)
expect(
resolveOauthPartition('https://gw-a.example.com', { registry: { primary: 'x', connections: 'junk' } as any })
).toBe(LEGACY_OAUTH_PARTITION)
})
it('does not treat a hostname PREFIX as a base-url match', () => {
const reg = registry('local', [remote('conn-a', 'https://gw.example.com')])
expect(resolveOauthPartition('https://gw.example.com.evil.tld/login', { registry: reg })).toBe(
LEGACY_OAUTH_PARTITION
)
})
it('normalizes trailing slashes and default ports when matching entry URLs', () => {
const reg = registry('local', [remote('conn-a', 'https://gw-a.example.com:443/')])
const got = resolveOauthPartition('https://gw-a.example.com/api/auth/ws-ticket', { registry: reg })
expect(got).not.toBe(LEGACY_OAUTH_PARTITION)
expect(got).toContain('conn-a')
})
it('produces a deterministic, partition-safe name from hostile connection ids', () => {
const reg = registry('local', [remote('we ird/id:€', 'https://gw-a.example.com')])
const got = resolveOauthPartition('https://gw-a.example.com', { registry: reg })
expect(got.startsWith('persist:')).toBe(true)
expect(got).not.toMatch(/[\s/€]/)
expect(resolveOauthPartition('https://gw-a.example.com', { registry: reg })).toBe(got)
})
it('breaks same-URL ties deterministically (identical jar for identical gateway)', () => {
const reg = registry('local', [
remote('zeta', 'https://gw-a.example.com'),
remote('alpha', 'https://gw-a.example.com')
])
const got = resolveOauthPartition('https://gw-a.example.com', { registry: reg })
expect(got).toContain('alpha')
})
})