{"record":{"id":"a07096eeb1e6522f","repo":"halo-dev/halo","slug":"no-permission-to-upload-attachment","errorCode":null,"errorMessage":"No permission to upload attachment","messagePattern":"No permission to upload attachment","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/src/formkit/inputs/attachment/UploadDropdownItem.vue","lineNumber":46,"sourceCode":"    return;\n  }\n\n  const attachments: Attachment[] = [];\n  for (const file of files) {\n    if (utils.permission.has([\"system:attachments:manage\"])) {\n      const { data } =\n        await consoleApiClient.storage.attachment.uploadAttachmentForConsole({\n          file: file,\n        });\n      attachments.push(data);\n    } else if (utils.permission.has([\"uc:attachments:manage\"])) {\n      const { data } =\n        await ucApiClient.storage.attachment.uploadAttachmentForUc({\n          file: file,\n        });\n      attachments.push(data);\n    } else {\n      throw new Error(\"No permission to upload attachment\");\n    }\n  }\n\n  emit(\"selected\", attachments);\n});\n</script>\n<template>\n  <VDropdownItem @click=\"openFileInputDialog()\">\n    {{ $t(\"core.common.buttons.upload\") }}\n  </VDropdownItem>\n</template>\n","sourceCodeStart":28,"sourceCodeEnd":58,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/src/formkit/inputs/attachment/UploadDropdownItem.vue#L28-L58","documentation":"Thrown in UploadDropdownItem.vue (a FormKit attachment input) when iterating selected files: the user has neither the system:attachments:manage nor the uc:attachments:manage permission. The component branches on permission — console upload API for system scope, UC API for user-center scope — and if neither permission is held, there is no valid upload endpoint, so it throws to abort the loop.","triggerScenarios":"A user opens the attachment upload dropdown in a FormKit input (e.g. editor attachment picker) and selects files, but their role lacks both system:attachments:manage and uc:attachments:manage. The permission checks via utils.permission.has both return false, so the upload is rejected client-side before any network call.","commonSituations":"A logged-in user whose role was not granted any attachment-upload permission tries to upload through a FormKit field; permissions changed/migrated and the role lost uc:attachments:manage; the component is shown to a user who should not see it (missing UI permission gate); a custom role with a typo'd permission identifier.","solutions":["Grant the role the appropriate permission: uc:attachments:manage for user-center uploads or system:attachments:manage for console uploads (System → Roles → edit role → Permissions).","Hide the UploadDropdownItem entirely when the user lacks upload permission — gate its rendering on utils.permission.has([...]) so the option never appears.","Catch the thrown error in the caller and show a localized 'no permission' toast instead of letting it propagate as an unhandled rejection.","Verify the permission identifiers used match the role template definitions (typos in 'uc:attachments:manage' silently fail)."],"exampleFix":"// before\n} else {\n  throw new Error(\"No permission to upload attachment\");\n}\n// after — surface a localized message and avoid rendering the item for unauthorized users\n} else {\n  Toast.warning(t(\"core.attachment.no_upload_permission\"));\n  return;\n}\n// and in the template, gate the dropdown item:\n// <VDropdownItem v-if=\"canUpload\" @click=\"openFileInputDialog()\">","handlingStrategy":"validation","validationCode":"// Check upload permission before showing the picker / iterating files\nconst canUploadConsole = utils.permission.has([\"system:attachments:manage\"]);\nconst canUploadUc = utils.permission.has([\"uc:attachments:manage\"]);\nif (!canUploadConsole && !canUploadUc) {\n  Toast.warning(t(\"core.attachment.no_upload_permission\"));\n  return;\n}\n// then proceed with the upload loop","typeGuard":"function canUploadAttachment(): boolean {\n  return (\n    utils.permission.has([\"system:attachments:manage\"]) ||\n    utils.permission.has([\"uc:attachments:manage\"])\n  );\n}","tryCatchPattern":"onFileInputChange(async (files) => {\n  try {\n    // ... upload loop that may throw 'No permission to upload attachment'\n  } catch (e) {\n    Toast.error(e instanceof Error ? e.message : \"Upload failed\");\n  }\n});","preventionTips":["Grant the role uc:attachments:manage (user-center) or system:attachments:manage (console).","Hide the UploadDropdownItem via v-if=\"canUploadAttachment()\" so unauthorized users never see the option.","Verify permission identifiers match the role template exactly — typos silently fail the check."],"tags":["attachments","permissions","rbac","formkit","vue","upload","validation"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}