fix(desktop): send the connection extra gateway headers on remote media streams

createMediaProtocolHandler() set only the session token / bearer on the
/api/files/stream request. The descriptor it resolves now carries the
connection's extra gateway headers, but the handler never read them, so
attachments in session history from a header-gated remote still bounced off
the access proxy even though every fetchJsonForBackend() call got through.

Merge connection.headers into the media request before auth, letting the
forwarded range/cache negotiation headers win, and pin it on both the
token and the OAuth cookie-session legs.

Part of #112072
This commit is contained in:
teknium1
2026-09-15 14:19:28 -07:00
committed by Teknium
parent 47c3683f23
commit c902f50efb
2 changed files with 47 additions and 0 deletions

View File

@@ -164,6 +164,44 @@ describe('createMediaProtocolHandler', () => {
expect(headers.get('range')).toBe('bytes=0-1023')
})
it('sends the connection extra gateway headers on remote media without clobbering range negotiation', async () => {
const deps = dependencies({
resolveRemoteConnection: vi.fn(async () => ({
authMode: 'token' as const,
baseUrl: 'https://gateway.test',
headers: { 'CF-Access-Client-Id': 'client-id', Range: 'bytes=9-9' },
mode: 'remote' as const,
token: 'secret'
}))
})
await createMediaProtocolHandler(deps)(
request('hermes-media://remote/%2Ftmp%2Fclip.mp4', { Range: 'bytes=0-1023' })
)
const [, headers] = vi.mocked(deps.fetchRemote).mock.calls[0]
expect(headers.get('cf-access-client-id')).toBe('client-id')
expect(headers.get('range')).toBe('bytes=0-1023')
expect(headers.get('x-hermes-session-token')).toBe('secret')
})
it('sends the connection extra gateway headers on OAuth cookie-session remote media', async () => {
const deps = dependencies({
resolveRemoteConnection: vi.fn(async () => ({
authMode: 'oauth' as const,
baseUrl: 'https://gateway.test',
headers: { 'CF-Access-Client-Id': 'client-id' },
mode: 'remote' as const,
token: null
}))
})
await createMediaProtocolHandler(deps)(request('hermes-media://remote/%2Ftmp%2Fclip.mp4'))
const [, headers] = vi.mocked(deps.fetchRemoteWithCookies).mock.calls[0]
expect(headers.get('cf-access-client-id')).toBe('client-id')
})
it('adds profile scope when one registry backend serves multiple profiles', async () => {
const deps = dependencies({
resolveRemoteConnection: vi.fn(async () => ({

View File

@@ -36,6 +36,7 @@ export interface MediaRemoteScope {
export interface MediaRemoteConnection {
authMode?: 'oauth' | 'token'
baseUrl: string
headers?: Record<string, string>
mode?: 'local' | 'remote'
token?: null | string
sharedRemote?: boolean
@@ -163,6 +164,14 @@ export function createMediaProtocolHandler(dependencies: MediaProtocolDependenci
connection.sharedRemote ? target.profile : undefined
)
// The gateway's configured extra headers (access-proxy gates) travel on
// every remote request; forwarded range/cache negotiation headers win.
for (const [name, value] of Object.entries(connection.headers ?? {})) {
if (!headers.has(name)) {
headers.set(name, value)
}
}
if (connection.authMode === 'oauth') {
return await requestWithOauthFallback(connection.baseUrl, {
ensureNativeAccessToken: dependencies.ensureRemoteBearer,