{"record":{"id":"245accf3c37f3c66","repo":"RocketChat/Rocket.Chat","slug":"error-not-allowed-245acc","errorCode":"error-not-allowed","errorMessage":"Not Allowed","messagePattern":"Not Allowed","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/rooms.ts","lineNumber":1023,"sourceCode":"\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t},\n\t},\n\tasync function action() {\n\t\tconst { rid, type } = this.bodyParams;\n\n\t\tif (!(await hasPermissionAsync(this.user, 'mail-messages', rid))) {\n\t\t\tthrow new Meteor.Error('error-action-not-allowed', 'Mailing is not allowed');\n\t\t}\n\n\t\tconst room = await Rooms.findOneById(rid);\n\t\tif (!room) {\n\t\t\tthrow new Meteor.Error('error-invalid-room');\n\t\t}\n\n\t\tconst user = await Users.findOneById(this.userId);\n\n\t\tif (!user || !(await canAccessRoomAsync(room, user))) {\n\t\t\tthrow new Meteor.Error('error-not-allowed', 'Not Allowed');\n\t\t}\n\n\t\tif (type === 'file') {\n\t\t\tconst { dateFrom, dateTo } = this.bodyParams;\n\t\t\tconst { format } = this.bodyParams;\n\n\t\t\tconst convertedDateFrom = dateFrom ? new Date(dateFrom) : new Date(0);\n\t\t\tconst convertedDateTo = dateTo ? new Date(dateTo) : new Date();\n\t\t\tconvertedDateTo.setDate(convertedDateTo.getDate() + 1);\n\n\t\t\tif (convertedDateFrom > convertedDateTo) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-dates', 'From date cannot be after To date');\n\t\t\t}\n\n\t\t\tvoid dataExport.sendFile(\n\t\t\t\t{\n\t\t\t\t\trid,\n\t\t\t\t\tformat,","sourceCodeStart":1005,"sourceCodeEnd":1041,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/rooms.ts#L1005-L1041","documentation":"Thrown by POST rooms.mail when the calling user can't be loaded or fails canAccessRoomAsync for the target room. This is the access-control gate after the room is confirmed to exist; it blocks users who exist in the system but have no access to the channel (e.g. a private channel they're not in).","triggerScenarios":"POST /api/v1/rooms.mail where the user was deleted between auth and lookup, or where the room is private/DM and the user is not a member.","commonSituations":"Trying to mail/export a private channel the user isn't part of; cross-workroom access attempts; user account disabled mid-session.","solutions":["Only offer the mail action on rooms the user can access (check via rooms.canAccess or by only listing joined rooms).","Re-fetch /me on session resume to confirm the account is still valid.","For private channels, ensure membership before exposing the action."],"exampleFix":"// before\nawait rest.post('/api/v1/rooms.mail', { rid, type: 'file' });\n\n// after\nconst accessible = await rest.get(`/api/v1/rooms.info?roomId=${rid}`);\nif (!accessible.room || !accessible.room._accessible) {\n  notify('You do not have access to this room.');\n  return;\n}\nawait rest.post('/api/v1/rooms.mail', { rid, type: 'file' });","handlingStrategy":"validation","validationCode":"const accessible = await canAccessRoom(rid, userId);\nif (!accessible) throw new Error('User cannot access this room');","typeGuard":"function canAccess(room: { _accessible?: boolean } | undefined): boolean {\n  return !!room?._accessible;\n}","tryCatchPattern":"try {\n  await rest.post('/api/v1/rooms.mail', { rid, type });\n} catch (e) {\n  if (isMeteorError(e, 'error-not-allowed')) {\n    notify('You do not have access to this room.');\n  } else throw e;\n}","preventionTips":["Only show mail/export for joined rooms.","Re-validate access after long sessions.","For private channels, gate the UI on membership."],"tags":["authorization","rooms","mail","rest-api"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}