{"record":{"id":"d1dfa50b23e5f101","repo":"RocketChat/Rocket.Chat","slug":"error-not-allowed-d1dfa5","errorCode":"error-not-allowed","errorMessage":"Change avatar is not allowed","messagePattern":"Change avatar is not allowed","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/media/file-upload/lib/FileUpload.ts","lineNumber":449,"sourceCode":"\t\t\tsize = await MultipartUploadHandler.stripExifFromFile(tmpFile);\n\t\t}\n\n\t\tawait this.getCollection().updateOne(\n\t\t\t{ _id: file._id },\n\t\t\t{\n\t\t\t\t$set: { size, identify },\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t},\n\n\tasync avatarsOnFinishUpload(file: IUpload) {\n\t\tif (file.rid) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!file.userId) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', 'Change avatar is not allowed');\n\t\t}\n\n\t\t// update file record to match user's username\n\t\tconst user = await Users.findOneById(file.userId);\n\t\tif (!user?.username) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', 'Change avatar is not allowed');\n\t\t}\n\t\tconst oldAvatar = await Avatars.findOneByName(user.username);\n\t\tif (oldAvatar) {\n\t\t\tawait Avatars.deleteFile(oldAvatar._id);\n\t\t}\n\t\tawait Avatars.updateFileNameById(file._id, user.username);\n\t},\n\n\tasync getRequestUserId({ headers = {}, url }: http.IncomingMessage): Promise<string | undefined> {\n\t\tif (!url) {\n\t\t\treturn undefined;\n\t\t}","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/media/file-upload/lib/FileUpload.ts#L431-L467","documentation":"avatarsOnFinishUpload (FileUpload.ts:449-452) runs when a file finishes uploading to the Avatars store. For non-room files (no file.rid) it must bind the avatar to a user; if the upload record has no userId it throws error-not-allowed 'Change avatar is not allowed'. The userId is derived from the upload request's authenticated context, so a missing one means the upload arrived without a valid user.","triggerScenarios":"POSTing directly to the avatar file-upload URL without X-Auth-Token/X-User-Id headers or a valid session cookie; uploads whose request-user resolution (getRequestUserId) fails because the URL is malformed; anonymous uploads to the Avatars store.","commonSituations":"Custom scripts calling the raw upload endpoint instead of users.setAvatar; expired auth tokens where the file POST succeeds but the user cannot be resolved; load balancers/proxies stripping auth headers; broken federation/omnichannel avatar flows.","solutions":["Upload avatars through the supported flows: the UI avatar dialog or the users.setAvatar REST endpoint with a valid auth token","When hitting the raw upload URL, include valid X-User-Id/X-Auth-Token headers or a logged-in cookie session","Verify the URL you POST to is the one returned by the avatar upload API, not a hand-built one","If writing server code, ensure the upload record carries userId before the store finishes"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// before POSTing to the avatar upload URL\nconst userId = await resolveCurrentUserId();\nif (!userId) throw new Error('Avatar upload requires an authenticated user');","typeGuard":null,"tryCatchPattern":"try {\n  await uploadAvatar(file);\n} catch (error: any) {\n  if (error instanceof Meteor.Error && error.error === 'error-not-allowed' && /avatar/i.test(error.reason)) {\n    reauthenticateAndRetry(); // upload arrived without a user binding\n    return;\n  }\n  throw error;\n}","preventionTips":["Use users.setAvatar instead of the raw upload endpoint","Always send valid auth headers with file uploads","Treat a missing userId as a session bug, not a server flake"],"tags":["avatar","file-upload","authentication","error-not-allowed"],"backgroundTag":"avatar-upload-not-allowed","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}