{"record":{"id":"0ea355faa35a4707","repo":"RocketChat/Rocket.Chat","slug":"user-not-on-private-team","errorCode":null,"errorMessage":"user-not-on-private-team","messagePattern":"user-not-on-private-team","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/services/team/service.ts","lineNumber":528,"sourceCode":"\t\tteamId: string,\n\t\tfilter: IListRoomsFilter,\n\t\t{ offset: skip, count: limit }: IPaginationOptions = { offset: 0, count: 50 },\n\t): Promise<IRecordsWithTotal<IRoom>> {\n\t\tif (!teamId) {\n\t\t\tthrow new Error('missing-teamId');\n\t\t}\n\t\tconst team = await Team.findOneById<Pick<ITeam, '_id' | 'type'>>(teamId, {\n\t\t\tprojection: { _id: 1, type: 1 },\n\t\t});\n\t\tif (!team) {\n\t\t\tthrow new Error('invalid-team');\n\t\t}\n\n\t\tconst { getAllRooms, allowPrivateTeam, name, isDefault } = filter;\n\n\t\tconst isMember = await TeamMember.findOneByUserIdAndTeamId(uid, teamId);\n\t\tif (team.type === TeamType.PRIVATE && !allowPrivateTeam && !isMember) {\n\t\t\tthrow new Error('user-not-on-private-team');\n\t\t}\n\n\t\tif (getAllRooms) {\n\t\t\tconst { cursor, totalCount } = Rooms.findPaginatedByTeamIdContainingNameAndDefault(teamId, name, isDefault, undefined, {\n\t\t\t\tskip,\n\t\t\t\tlimit,\n\t\t\t});\n\t\t\tconst [records, total] = await Promise.all([cursor.toArray(), totalCount]);\n\t\t\treturn {\n\t\t\t\ttotal,\n\t\t\t\trecords,\n\t\t\t};\n\t\t}\n\n\t\tconst user = await Users.findOneById<{ __rooms: string[] }>(uid, {\n\t\t\tprojection: { __rooms: 1 },\n\t\t});\n\t\tconst userRooms = user?.__rooms;","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/services/team/service.ts#L510-L546","documentation":"Thrown by TeamService.listRooms (apps/meteor/server/services/team/service.ts:528) when three conditions align: the team's type is PRIVATE, the caller lacks the allowPrivateTeam escape hatch, and TeamMember.findOneByUserIdAndTeamId finds no membership. allowPrivateTeam is computed by the REST layer from the 'view-all-teams' permission, so for ordinary users this is the private-team membership gate for listing channels.","triggerScenarios":"GET /api/v1/teams.listRooms?teamId=... for a private team by a non-member without 'view-all-teams'; direct service calls with allowPrivateTeam=false (its usual value) for a private team the uid does not belong to; users removed from the team while listing its rooms.","commonSituations":"Building directory/search UIs that enumerate teams without filtering by visibility; former members holding stale client state; admins assumed to see everything but lacking the view-all-teams permission.","solutions":["Only call listRooms for private teams when the caller is a member (or holds 'view-all-teams')","Grant 'view-all-teams' to roles that legitimately need to browse private teams (admins, auditors)","Pre-check membership: await TeamMember.findOneByUserIdAndTeamId(uid, teamId) and skip/403 before calling"],"exampleFix":"// before\nawait Team.listRooms(uid, teamId, { allowPrivateTeam: false, ... }); // private team, non-member → throws\n\n// after\nconst isMember = !!(await TeamMember.findOneByUserIdAndTeamId(uid, teamId));\nconst allowPrivateTeam = await Authorization.hasPermission(uid, 'view-all-teams', team.roomId);\nif (!isMember && !allowPrivateTeam) {\n\tthrow new Error('user-not-on-private-team'); // fail fast with context\n}\nawait Team.listRooms(uid, teamId, { allowPrivateTeam, ... });","handlingStrategy":"validation","validationCode":"const isMember = !!(await TeamMember.findOneByUserIdAndTeamId(uid, teamId));\nconst allowPrivateTeam = await Authorization.hasPermission(uid, 'view-all-teams', team.roomId);\nif (!isMember && !allowPrivateTeam) {\n\tthrow new Error('user-not-on-private-team');\n}","typeGuard":null,"tryCatchPattern":"try {\n\tawait Team.listRooms(uid, teamId, filter);\n} catch (e) {\n\tif (e instanceof Error && e.message === 'user-not-on-private-team') {\n\t\t// map to 403 with 'join the team or ask an admin' guidance\n\t}\n\tthrow e;\n}","preventionTips":["Filter team listings by visibility: check team.type before offering private teams to non-members","Grant 'view-all-teams' only to roles that genuinely need private-team visibility","Remember membership is checked for the CALLER's uid, not the resource owner"],"tags":["rocket-chat","teams","authorization","private-teams","permissions"],"backgroundTag":"permission-denied","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}