Files
Shivam MishraandGitHub 8dd0d08322 refactor: align conversation direct uploads with standard account auth (#15039)
Conversation attachment uploads now go through the same authentication
that every other account-scoped API endpoint uses. Agents continue to
attach files exactly as before, and the upload request is now tied to
the agent's dashboard session instead of a separately serialized access
token.

Because the upload request is now authenticated, the dashboard proves
the agent's session directly instead of passing
`currentUser.access_token`. This keeps uploads working alongside the
profile access-token changes in #14973, including on accounts where that
token is serialized as empty.

## What changed

- `Api::V1::Accounts::Conversations::DirectUploadsController` now runs
the standard account auth stack: API access token when the
`api_access_token` header is present, dashboard session
(devise-token-auth) otherwise, with agent-bot tokens rejected.
Previously it inherited `ActiveStorage::DirectUploadsController`
directly and did not run any authentication.
- `EnsureCurrentAccountHelper#ensure_current_account` now returns `401`
when a request has neither an authenticated user nor a bot resource,
instead of continuing. This closes the same gap for any controller that
relies on the helper.
- The dashboard direct-upload paths (`useFileUpload.js` and the legacy
`fileUploadMixin.js`) now attach the agent's session headers to the
upload request via a new `directUploadsHelper.js`, instead of sending
`currentUser.access_token`.

## How to test

1. As a logged-in agent, open a conversation and attach a file. Upload
should succeed as before, on installs with direct uploads enabled.
2. Confirm attachments still work for an agent on an account whose
profile access token is not serialized (e.g. a Cloud plan without
`api_and_webhooks`).
3. Send a `POST` to
`/api/v1/accounts/:account_id/conversations/:conversation_id/direct_uploads`
with no credentials, an empty `api_access_token`, or an invalid token,
and confirm it returns `401`.
4. Confirm a valid agent of the account (via API token or session) gets
`200`, while an agent of a different account gets `401`.
2026-07-16 18:17:29 +05:30

62 lines
1.7 KiB
JavaScript

import { setDirectUploadAuthHeaders } from '../directUploadsHelper';
import Auth from 'dashboard/api/auth';
vi.mock('dashboard/api/auth', () => ({
default: { getAuthData: vi.fn() },
}));
describe('setDirectUploadAuthHeaders', () => {
const buildXhr = () => ({ setRequestHeader: vi.fn() });
afterEach(() => {
vi.clearAllMocks();
});
it('sets the five session auth headers from the auth cookie', () => {
Auth.getAuthData.mockReturnValue({
'access-token': 'token-123',
'token-type': 'Bearer',
client: 'client-123',
expiry: '9999',
uid: 'agent@example.com',
});
const xhr = buildXhr();
setDirectUploadAuthHeaders(xhr);
expect(xhr.setRequestHeader).toHaveBeenCalledTimes(5);
expect(xhr.setRequestHeader).toHaveBeenCalledWith(
'access-token',
'token-123'
);
expect(xhr.setRequestHeader).toHaveBeenCalledWith('token-type', 'Bearer');
expect(xhr.setRequestHeader).toHaveBeenCalledWith('client', 'client-123');
expect(xhr.setRequestHeader).toHaveBeenCalledWith('expiry', '9999');
expect(xhr.setRequestHeader).toHaveBeenCalledWith(
'uid',
'agent@example.com'
);
});
it('does not set any header when there is no auth data', () => {
Auth.getAuthData.mockReturnValue(false);
const xhr = buildXhr();
setDirectUploadAuthHeaders(xhr);
expect(xhr.setRequestHeader).not.toHaveBeenCalled();
});
it('does not set any header when the access token is missing', () => {
Auth.getAuthData.mockReturnValue({
client: 'client-123',
uid: 'agent@example.com',
});
const xhr = buildXhr();
setDirectUploadAuthHeaders(xhr);
expect(xhr.setRequestHeader).not.toHaveBeenCalled();
});
});