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

110 lines
2.8 KiB
JavaScript

import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { getMaxUploadSizeByChannel } from '@chatwoot/utils';
import { DirectUpload } from 'activestorage';
import { setDirectUploadAuthHeaders } from 'dashboard/helper/directUploadsHelper';
import {
DEFAULT_MAXIMUM_FILE_UPLOAD_SIZE,
resolveMaximumFileUploadSize,
} from 'shared/helpers/FileHelper';
import { INBOX_TYPES } from 'dashboard/helper/inbox';
export default {
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
}),
installationLimit() {
return resolveMaximumFileUploadSize(
this.globalConfig.maximumFileUploadSize
);
},
},
methods: {
maxSizeFor(mime) {
// Use default/installation limit for private notes
if (this.isOnPrivateNote) {
return this.installationLimit;
}
const channelType = this.inbox?.channel_type;
if (!channelType || channelType === INBOX_TYPES.WEB) {
return this.installationLimit;
}
const channelLimit = getMaxUploadSizeByChannel({
channelType,
medium: this.inbox?.medium, // e.g. 'sms' | 'whatsapp'
mime, // e.g. 'image/png'
});
if (channelLimit === DEFAULT_MAXIMUM_FILE_UPLOAD_SIZE) {
return this.installationLimit;
}
return Math.min(channelLimit, this.installationLimit);
},
alertOverLimit(maxSizeMB) {
useAlert(
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE: maxSizeMB,
})
);
},
onFileUpload(file) {
if (this.globalConfig.directUploadsEnabled) {
this.onDirectFileUpload(file);
} else {
this.onIndirectFileUpload(file);
}
},
onDirectFileUpload(file) {
if (!file) return;
const mime = file.file?.type || file.type;
const maxSizeMB = this.maxSizeFor(mime);
if (!checkFileSizeLimit(file, maxSizeMB)) {
this.alertOverLimit(maxSizeMB);
return;
}
const upload = new DirectUpload(
file.file,
`/api/v1/accounts/${this.accountId}/conversations/${this.currentChat.id}/direct_uploads`,
{
directUploadWillCreateBlobWithXHR: xhr => {
setDirectUploadAuthHeaders(xhr);
},
}
);
upload.create((error, blob) => {
if (error) {
useAlert(error);
} else {
this.attachFile({ file, blob });
}
});
},
onIndirectFileUpload(file) {
if (!file) return;
const mime = file.file?.type || file.type;
const maxSizeMB = this.maxSizeFor(mime);
if (!checkFileSizeLimit(file, maxSizeMB)) {
this.alertOverLimit(maxSizeMB);
return;
}
this.attachFile({ file });
},
},
};