{"record":{"id":"0ed87b989c030ef1","repo":"RocketChat/Rocket.Chat","slug":"error-not-authorized-0ed87b","errorCode":null,"errorMessage":"error-not-authorized","messagePattern":"error-not-authorized","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/lib/omnichannel/business-hour/lib/business-hour.ts","lineNumber":14,"sourceCode":"import type { ILivechatBusinessHour } from '@rocket.chat/core-typings';\nimport { LivechatBusinessHours, LivechatDepartment } from '@rocket.chat/models';\nimport { escapeRegExp } from '@rocket.chat/tools';\n\nimport { hasPermissionAsync } from '../../../../../../server/lib/authorization/hasPermission';\nimport type { IPaginatedResponse, IPagination } from '../../../../api/v1/omnichannel/lib/definition';\n\ninterface IResponse extends IPaginatedResponse {\n\tbusinessHours: ILivechatBusinessHour[];\n}\n\nexport async function findBusinessHours(userId: string, { offset, count, sort }: IPagination, name?: string): Promise<IResponse> {\n\tif (!(await hasPermissionAsync(userId, 'view-livechat-business-hours'))) {\n\t\tthrow new Error('error-not-authorized');\n\t}\n\tconst query = {};\n\tif (name) {\n\t\tconst filterReg = new RegExp(escapeRegExp(name), 'i');\n\t\tObject.assign(query, { name: filterReg });\n\t}\n\tconst { cursor, totalCount } = LivechatBusinessHours.findPaginated(query, {\n\t\tsort: sort || { name: 1 },\n\t\tskip: offset,\n\t\tlimit: count,\n\t});\n\n\tconst [businessHours, total] = await Promise.all([cursor.toArray(), totalCount]);\n\n\t// add departments to businessHours\n\tconst businessHoursWithDepartments = await Promise.all(\n\t\tbusinessHours.map(async (businessHour) => {\n\t\t\tconst currentDepartments = await LivechatDepartment.findByBusinessHourId(businessHour._id, {","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2a7de457074cbb4d4373fbd9a4e5bea292c9c764/apps/meteor/ee/server/lib/omnichannel/business-hour/lib/business-hour.ts#L1-L32","documentation":"findBusinessHours is the Enterprise helper behind the livechat business-hours listing API (apps/meteor/ee/server/api/v1/omnichannel/business-hours.ts). It gates on the 'view-livechat-business-hours' permission via hasPermissionAsync and throws a plain Error('error-not-authorized') before the paginated LivechatBusinessHours query ever runs. The throw means the authenticated user's role set does not include that specific permission; other livechat permissions do not cover it.","triggerScenarios":"Calling GET livechat/business-hours (which delegates to findBusinessHours(userId, pagination, name)) with a session whose roles lack 'view-livechat-business-hours' - e.g. a livechat-agent, a plain user, or a userId resolving to no roles. The check is the first statement, so pagination/sort/name filter parameters are irrelevant; authorization alone decides.","commonSituations":"A custom 'business hours manager' role was created without the permission; testing the endpoint with an agent or manager account whose role lacks it; permissions trimmed or reset during a workspace migration/upgrade; assuming 'view-livechat-manager' or admin-adjacent roles cover business hours (they do not - the permission is separate).","solutions":["Grant 'view-livechat-business-hours' to the caller's role under Administration > Permissions (it is normally held by admin and livechat-manager style roles).","When calling findBusinessHours server-side, verify you are passing the authenticated requester's userId, not a system/undefined id.","If wrapping this helper in a custom route, map Error('error-not-authorized') to HTTP 403 so clients get a meaningful status instead of a generic 500."],"exampleFix":"// before\nconst { businessHours } = await findBusinessHours(uid, { offset, count }); // 500-ish raw error for under-privileged callers\n\n// after - pre-check the same permission in the calling route\nif (!(await hasPermissionAsync(uid, 'view-livechat-business-hours'))) {\n\treturn API.v1.unauthorized();\n}\nconst { businessHours } = await findBusinessHours(uid, { offset, count });","handlingStrategy":"validation","validationCode":"import { hasPermissionAsync } from '../../../../server/lib/authorization/hasPermission';\n\nif (!(await hasPermissionAsync(userId, 'view-livechat-business-hours'))) {\n\t// skip the call or return 403; do not invoke findBusinessHours\n}","typeGuard":null,"tryCatchPattern":"try {\n\tconst res = await findBusinessHours(userId, pagination, name);\n} catch (e: any) {\n\tif (e instanceof Error && e.message === 'error-not-authorized') return API.v1.unauthorized(); // 403\n\tthrow e;\n}","preventionTips":["Add 'view-livechat-business-hours' to any custom role that must list business hours before shipping it.","Smoke-test omnichannel EE endpoints with the least-privileged account that should have access.","Prefer route-level permissionsRequired so authorization failures surface as 403 instead of raw errors."],"tags":["omnichannel","business-hours","authorization","permissions","enterprise"],"backgroundTag":"permission-denied","analyzedSha":"2a7de457074cbb4d4373fbd9a4e5bea292c9c764","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}