{"record":{"id":"9ad4978e02d88003","repo":"RocketChat/Rocket.Chat","slug":"not-authorized-9ad497","errorCode":"not-authorized","errorMessage":"Not Authorized","messagePattern":"Not Authorized","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/messaging/reactions/setReaction.ts","lineNumber":139,"sourceCode":"\n\tconst userAlreadyReacted = Boolean(message.reactions?.[reaction]?.usernames?.includes(user.username as string));\n\n\t// When shouldReact was not informed, toggle the reaction.\n\tif (shouldReact === undefined) {\n\t\tshouldReact = !userAlreadyReacted;\n\t}\n\n\tif (userAlreadyReacted === shouldReact) {\n\t\treturn;\n\t}\n\n\tconst room = await Rooms.findOneById(message.rid);\n\tif (!room) {\n\t\tthrow new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' });\n\t}\n\n\tif (!(await canAccessRoomAsync(room, user))) {\n\t\tthrow new Meteor.Error('not-authorized', 'Not Authorized', { method: 'setReaction' });\n\t}\n\n\treturn setReaction(room, user, message, reaction, userAlreadyReacted);\n}\n\ndeclare module '@rocket.chat/ddp-client' {\n\t// eslint-disable-next-line @typescript-eslint/naming-convention\n\tinterface ServerMethods {\n\t\tsetReaction(reaction: string, messageId: IMessage['_id'], shouldReact?: boolean): boolean | undefined;\n\t}\n}\n","sourceCodeStart":121,"sourceCodeEnd":151,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/messaging/reactions/setReaction.ts#L121-L151","documentation":"The final gate in executeSetReaction: canAccessRoomAsync(room, user) must pass or Meteor.Error('not-authorized', 'Not Authorized') is thrown. The room exists but the caller is not allowed to see it (not a member of the private channel/discussion/DM). Distinct from 'error-not-allowed': this is an authorization failure, not a missing resource.","triggerScenarios":"Reacting in a room the user was removed from while their UI stayed stale; an Apps Engine bot user that was never added to a private channel; a guest without access to the room type; a DM whose counterpart blocked or deleted the account.","commonSituations":"User kicked from a private channel but the tab/session kept the room open; bots reacting via apps in channels where they lost membership; kick and react racing each other.","solutions":["Ensure the acting user/bot has an active membership (subscription) in the room before triggering reactions","Refresh room subscriptions after removal events and close stale room views","For apps, add the bot to the private channel before reacting on its behalf"],"exampleFix":"// before - bot reacts right after losing membership\nawait executeSetReaction(botId, 'tada', messageId);\n\n// after - verify membership first\nconst sub = await Subscriptions.findOneByRoomIdAndUserId(rid, botId, { projections: { _id: 1 } });\nif (sub) {\n  await executeSetReaction(botId, 'tada', messageId);","handlingStrategy":"validation","validationCode":"// Mirror the method's own gate before delegating\nif (!(await canAccessRoomAsync(room, user))) {\n  return forbidden(room._id);\n}\nawait executeSetReaction(user._id, 'tada', messageId);","typeGuard":null,"tryCatchPattern":"Meteor.call('setReaction', 'tada', messageId, (err) => {\n  if (err?.error === 'not-authorized') {\n    closeRoomAndResubscribe(rid); // membership is gone - stop interacting with the room\n  }\n});","preventionTips":["Treat not-authorized as a membership failure - resync subscriptions, never retry","Bots and app users must be room members even when acting via Apps Engine","Invalidate room UIs upon subscription-remove stream events"],"tags":["reactions","authorization","room-access","ddp-method"],"backgroundTag":"room-access-denied","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}