chore: support PFX filetype in attachment uploads (#14456)

# Pull Request Template

## Description

This PR expands the default upload rules to support PFX certificate
files (`application/x-pkcs12`, `application/pkcs12`, `.pfx`) across
private notes, Website, Email, and Telegram channels.

Also adds `.xls` / `.xlsx` extension fallbacks for cases where browsers
upload Excel files with an empty or generic MIME type.

### Utils Repo PR: https://github.com/chatwoot/utils/pull/61

Fixes
https://linear.app/chatwoot/issue/CW-7085/support-more-file-types-in-private-notes-and-in-app

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

### Screenshots
<img width="330" height="218" alt="image"
src="https://github.com/user-attachments/assets/80823250-893e-4509-adb9-61f845359151"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
This commit is contained in:
Sivin Varghese
2026-05-22 11:09:27 +05:30
committed by GitHub
co-authored by aakashb95
parent d0ecdc14d8
commit 3c67c41544
7 changed files with 42 additions and 20 deletions
@@ -18,6 +18,7 @@ const fileTypeIcon = computed(() => {
json: 'i-woot-file-txt',
odt: 'i-woot-file-doc',
pdf: 'i-woot-file-pdf',
pfx: 'i-woot-file-pfx',
ppt: 'i-woot-file-ppt',
pptx: 'i-woot-file-ppt',
rar: 'i-woot-file-zip',
@@ -7,7 +7,6 @@ import * as ActiveStorage from 'activestorage';
import inboxMixin from 'shared/mixins/inboxMixin';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { getAllowedFileTypesByChannel } from '@chatwoot/utils';
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
import VideoCallButton from '../VideoCallButton.vue';
import { INBOX_TYPES } from 'dashboard/helper/inbox';
import { mapGetters } from 'vuex';
@@ -166,11 +165,6 @@ export default {
uploadRef,
};
},
data() {
return {
ALLOWED_FILE_TYPES,
};
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
@@ -212,13 +206,11 @@ export default {
return this.conversationType === 'instagram_direct_message';
},
allowedFileTypes() {
// Use default file types for private notes
if (this.isOnPrivateNote) {
return this.ALLOWED_FILE_TYPES;
return getAllowedFileTypesByChannel();
}
let channelType = this.channelType || this.inbox?.channel_type;
if (this.isAnInstagramChannel || this.isInstagramDM) {
channelType = INBOX_TYPES.INSTAGRAM;
}
+1 -3
View File
@@ -1,6 +1,5 @@
import { getAllowedFileTypesByChannel } from '@chatwoot/utils';
import { INBOX_TYPES } from 'dashboard/helper/inbox';
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
export const DEFAULT_MAXIMUM_FILE_UPLOAD_SIZE = 40;
@@ -58,9 +57,8 @@ export const isFileTypeAllowedForChannel = (file, options = {}) => {
isOnPrivateNote,
} = options;
// Use broader file types for private notes (matches file picker behavior)
const allowedFileTypes = isOnPrivateNote
? ALLOWED_FILE_TYPES
? getAllowedFileTypesByChannel()
: getAllowedFileTypesByChannel({
channelType:
isInstagramChannel || conversationType === 'instagram_direct_message'
+16 -2
View File
@@ -33,7 +33,10 @@ class Attachment < ApplicationRecord
application/vnd.openxmlformats-officedocument.presentationml.presentation
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.openxmlformats-officedocument.wordprocessingml.document
application/x-pkcs12 application/pkcs12
].freeze
ACCEPTABLE_FILE_EXTENSIONS = %w[pfx].freeze
GENERIC_FILE_CONTENT_TYPES = %w[application/octet-stream].freeze
belongs_to :account
belongs_to :message
has_one_attached :file
@@ -195,7 +198,10 @@ class Attachment < ApplicationRecord
end
def validate_file_content_type(file_content_type)
errors.add(:file, 'type not supported') unless media_file?(file_content_type) || ACCEPTABLE_FILE_TYPES.include?(file_content_type)
return if media_file?(file_content_type) || ACCEPTABLE_FILE_TYPES.include?(file_content_type)
return if generic_file_content_type?(file_content_type) && ACCEPTABLE_FILE_EXTENSIONS.include?(file_extension)
errors.add(:file, 'type not supported')
end
def validate_file_size(byte_size)
@@ -206,7 +212,15 @@ class Attachment < ApplicationRecord
end
def media_file?(file_content_type)
file_content_type.start_with?('image/', 'video/', 'audio/')
file_content_type.to_s.start_with?('image/', 'video/', 'audio/')
end
def generic_file_content_type?(file_content_type)
file_content_type.blank? || GENERIC_FILE_CONTENT_TYPES.include?(file_content_type)
end
def file_extension
File.extname(file.filename.to_s).delete_prefix('.').downcase
end
end
+1 -1
View File
@@ -35,7 +35,7 @@
"@breezystack/lamejs": "^1.2.7",
"@chatwoot/ninja-keys": "1.2.3",
"@chatwoot/prosemirror-schema": "1.3.13",
"@chatwoot/utils": "^0.0.52",
"@chatwoot/utils": "^0.0.55",
"@formkit/core": "^1.7.2",
"@formkit/vue": "^1.7.2",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
+5 -5
View File
@@ -29,8 +29,8 @@ importers:
specifier: 1.3.13
version: 1.3.13
'@chatwoot/utils':
specifier: ^0.0.52
version: 0.0.52
specifier: ^0.0.55
version: 0.0.55
'@formkit/core':
specifier: ^1.7.2
version: 1.7.2
@@ -462,8 +462,8 @@ packages:
'@chatwoot/prosemirror-schema@1.3.13':
resolution: {integrity: sha512-T6FBUinMJbwDCD7975g8M/Tsn2+G3O2pTGIXdcLkMRpbAAC6mVdl4ZcZektlt5y/PVmPVqNHPsfee1XB/C3vAw==}
'@chatwoot/utils@0.0.52':
resolution: {integrity: sha512-e57uVqyVW4tj1gql4YJPNMykqMJPkETn5Y9AmHdhc6Y7oxDXfRXBq27fZrrDadLkZdn5RYVCZjfIhXOumyYv2Q==}
'@chatwoot/utils@0.0.55':
resolution: {integrity: sha512-8G6HYQe1ZEYfJEsSYfDVvE+uhf98JDRjtGlpB+bzMko+yltbrk4yACSo/ImC3jSaJ6K8yPTSjJToSRmsQbL2iQ==}
engines: {node: '>=10'}
'@codemirror/commands@6.7.0':
@@ -5011,7 +5011,7 @@ snapshots:
prosemirror-utils: 1.2.2(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)
prosemirror-view: 1.34.1
'@chatwoot/utils@0.0.52':
'@chatwoot/utils@0.0.55':
dependencies:
date-fns: 2.30.0
+17
View File
@@ -113,6 +113,23 @@ export const icons = {
width: 16,
height: 20,
},
'file-pfx': {
body: `<g clip-path="url(#clip0_pfx_icon)">
<path d="M12.6562 19.7578H2.94141C0.972656 19.7578 0 18.7734 0 16.7695V2.98828C0 0.996094 0.972656 0 2.94141 0H7.07812V6.80859C7.07812 8.05078 7.66406 8.63672 8.90625 8.63672H15.6094V16.7695C15.6094 18.7617 14.625 19.7578 12.6562 19.7578Z" fill="#4F46E5"/>
<path d="M15.5039 7.26562H8.90625C8.61328 7.26562 8.44922 7.11328 8.44922 6.80859V0.0820312C8.88281 0.152344 9.31641 0.457031 9.79688 0.949219L14.6602 5.91797C15.1523 6.42188 15.4453 6.83203 15.5039 7.26562Z" fill="#4F46E5"/>
<path d="M3.83969 14.71H4.3377C4.4297 14.71 4.5347 14.703 4.6527 14.689C4.7727 14.673 4.8947 14.642 5.0187 14.596C5.14471 14.55 5.26071 14.483 5.36671 14.395C5.47471 14.305 5.56071 14.186 5.62471 14.038C5.69071 13.89 5.72371 13.706 5.72371 13.486C5.72371 13.266 5.69071 13.084 5.62471 12.94C5.56071 12.794 5.47471 12.677 5.36671 12.589C5.26071 12.501 5.14471 12.436 5.0187 12.394C4.8947 12.35 4.7727 12.321 4.6527 12.307C4.5347 12.293 4.4297 12.286 4.3377 12.286H3.41069C3.04269 12.286 2.85868 12.47 2.85868 12.838V15.448C2.85868 15.816 3.02268 16 3.35069 16C3.67669 16 3.83969 15.816 3.83969 15.448V14.71Z" fill="white"/>
<path d="M3.83969 14.011V12.985H4.1847C4.2547 12.985 4.3227 12.99 4.3887 13C4.4547 13.01 4.5137 13.032 4.5657 13.066C4.6197 13.1 4.6617 13.151 4.6917 13.219C4.7237 13.285 4.7397 13.374 4.7397 13.486C4.7397 13.6 4.7237 13.692 4.6917 13.762C4.6617 13.832 4.6197 13.885 4.5657 13.921C4.5137 13.955 4.4547 13.979 4.3887 13.993C4.3227 14.005 4.2547 14.011 4.1847 14.011H3.83969Z" fill="#4F46E5"/>
<path d="M13.1405 12.895C13.2345 12.833 13.2815 12.746 13.2815 12.634C13.2815 12.52 13.2345 12.434 13.1405 12.376C13.0465 12.316 12.9085 12.286 12.7265 12.286H11.1755C10.8095 12.286 10.6264 12.47 10.6264 12.838V15.502C10.6264 15.868 10.7894 16.051 11.1155 16.051C11.4455 16.051 11.6105 15.868 11.6105 15.502V14.788H12.5915C12.8955 14.788 13.0475 14.672 13.0475 14.44C13.0475 14.206 12.8955 14.089 12.5915 14.089H11.6105V12.985H12.7265C12.9085 12.985 13.1405 12.955 13.1405 12.895Z" fill="white" transform="translate(-3.8 0)"/>
<path d="M2.82268 15.952C2.90068 16.022 3.00968 16.057 3.14969 16.057C3.28169 16.057 3.38169 16.022 3.44969 15.952C3.51969 15.882 3.59569 15.782 3.67769 15.652L4.1787 14.869L4.6737 15.652C4.7557 15.78 4.8307 15.88 4.8987 15.952C4.9687 16.022 5.07171 16.057 5.20771 16.057C5.34371 16.057 5.45271 16.024 5.53471 15.958C5.61871 15.892 5.66071 15.808 5.66071 15.706C5.66071 15.656 5.65171 15.605 5.63371 15.553C5.61771 15.501 5.58671 15.44 5.54071 15.37L4.7337 14.158L5.60071 12.907C5.66471 12.817 5.69671 12.723 5.69671 12.625C5.69671 12.497 5.65471 12.403 5.57071 12.343C5.48871 12.281 5.38671 12.25 5.26471 12.25C5.13671 12.25 5.0337 12.282 4.9557 12.346C4.8777 12.408 4.7897 12.52 4.6917 12.682L4.1907 13.501L3.67769 12.682C3.61169 12.576 3.55469 12.492 3.50669 12.43C3.45869 12.366 3.40569 12.32 3.34769 12.292C3.29169 12.264 3.21869 12.25 3.12869 12.25C3.00868 12.25 2.90368 12.282 2.81368 12.346C2.72368 12.41 2.67868 12.503 2.67868 12.625C2.67868 12.669 2.68368 12.714 2.69368 12.76C2.70568 12.804 2.72968 12.852 2.76568 12.904L3.64169 14.194L2.82568 15.37C2.78168 15.436 2.75068 15.493 2.73268 15.541C2.71668 15.589 2.70868 15.638 2.70868 15.688C2.70868 15.792 2.74668 15.88 2.82268 15.952Z" fill="white" transform="translate(7.65 0)"/>
</g>
<defs>
<clipPath id="clip0_pfx_icon">
<rect width="15.9609" height="19.7695" fill="white"/>
</clipPath>
</defs>`,
width: 16,
height: 20,
},
bin: {
body: `<g fill="none" stroke="currentColor"><path d="M12.3999 5.7334L11.3359 11.7848C11.2463 12.2948 10.8032 12.6667 10.2854 12.6667H5.98111C5.46327 12.6667 5.02023 12.2948 4.93056 11.7848L3.86658 5.7334" stroke="currentColor" stroke-width="0.960001" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.9334 4.13334H9.93338M9.93338 4.13334V3.06667C9.93338 2.47756 9.45584 2 8.86671 2H7.40005C6.81094 2 6.33338 2.47756 6.33338 3.06667V4.13334M9.93338 4.13334H6.33338M3.33337 4.13334H6.33338" stroke="currentColor" stroke-width="0.960001" stroke-linecap="round" stroke-linejoin="round"/></g>`,
width: 16,