RocketChat/Rocket.Chat · error · Meteor.Error

not_authorized

not_authorized

Error message

Unauthorized

What it means

Thrown by updateIncomingIntegration when the caller holds neither 'manage-incoming-integrations' nor 'manage-own-incoming-integrations'. With the first, the integration is looked up globally; with the second, only among records created by the caller; with neither, the method aborts with not_authorized before any update.

Source

Thrown at apps/meteor/server/meteor-methods/integrations/incoming/updateIncomingIntegration.ts:62

export const updateIncomingIntegration = async (
	userId: string,
	integrationId: string,
	integration: INewIncomingIntegration | IUpdateIncomingIntegration,
): Promise<IIntegration | null> => {
	const channels = validateChannels(integration.channel);

	let currentIntegration;

	if (await hasPermissionAsync(userId, 'manage-incoming-integrations')) {
		currentIntegration = await Integrations.findOneById(integrationId);
	} else if (await hasPermissionAsync(userId, 'manage-own-incoming-integrations')) {
		currentIntegration = await Integrations.findOne({
			'_id': integrationId,
			'_createdBy._id': userId,
		});
	} else {
		throw new Meteor.Error('not_authorized', 'Unauthorized', {
			method: 'updateIncomingIntegration',
		});
	}

	if (!currentIntegration) {
		throw new Meteor.Error('error-invalid-integration', 'Invalid integration', {
			method: 'updateIncomingIntegration',
		});
	}

	const oldScriptEngine = currentIntegration.scriptEngine;
	const scriptEngine = integration.scriptEngine ?? oldScriptEngine ?? 'isolated-vm';
	if (
		integration.script?.trim() &&
		(scriptEngine !== oldScriptEngine || integration.script?.trim() !== currentIntegration.script?.trim())
	) {
		wrapExceptions(() => validateScriptEngine(scriptEngine)).catch((e) => {
			throw new Meteor.Error(e.message);

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Grant the caller one of the two permissions: 'manage-incoming-integrations' for all records, 'manage-own-incoming-integrations' for their own
  2. Re-login after permission changes so refreshed roles reach the client, then retry
  3. For automated flows use POST /api/v1/integrations.update with an authorized admin token

Example fix

// before
await Meteor.callAsync('updateIncomingIntegration', id, payload); // caller lacks both permissions
// after: call through an authorized service account via REST
await fetch(`${root}/api/v1/integrations.update`, { method: 'POST', headers: { 'X-Auth-Token': token, 'X-User-Id': uid }, body: JSON.stringify({ integrationId: id, ...payload }) });
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await Meteor.callAsync('updateIncomingIntegration', integrationId, integration);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'not_authorized') { /* hide edit UI, prompt for permission or admin */ }
}

Prevention

When it happens

Trigger: A user whose integration permissions were revoked (but whose client UI still shows the edit screen) submits an update; a fresh user account without any integration role attempts an update.

Common situations: Role cleanup after team changes; permission grants not propagated to the client's cached roles until re-login; automation using a service account that was never given integration permissions.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/61d8c9ccfceedd03. Report an issue: GitHub.