RocketChat/Rocket.Chat · error · Meteor.Error

not_authorized

not_authorized

Error message

Unauthorized

What it means

While resolving the current integration, updateOutgoingIntegration branches on permissions: 'manage-outgoing-integrations' allows findOneById on any integration, 'manage-own-outgoing-integrations' scopes the query to _createdBy._id === userId, and holding neither throws not_authorized 'Unauthorized'. Note this throw happens before the not-found check, so an unauthorized caller cannot distinguish 'no permission' from 'does not exist'.

Source

Thrown at apps/meteor/server/meteor-methods/integrations/outgoing/updateOutgoingIntegration.ts:46

	const integration = await validateOutgoingIntegration(_integration, userId);

	if (!integration.token || integration.token.trim() === '') {
		throw new Meteor.Error('error-invalid-token', 'Invalid token', {
			method: 'updateOutgoingIntegration',
		});
	}

	let currentIntegration: IIntegration | null;

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

	if (!currentIntegration) {
		throw new Meteor.Error('invalid_integration', '[methods] updateOutgoingIntegration -> integration not found');
	}

	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 'manage-outgoing-integrations' or 'manage-own-outgoing-integrations' to the caller's role
  2. Re-login/refresh so the new permission reaches the session or token context
  3. Retry with an admin-owned API token for the PUT /v1/integrations.update call
Defensive patterns

Strategy: validation

Validate before calling

const canManageAll = await hasPermissionAsync(uid, 'manage-outgoing-integrations');
const canManageOwn = !canManageAll && (await hasPermissionAsync(uid, 'manage-own-outgoing-integrations'));
if (!canManageAll && !canManageOwn) {
  throw new Meteor.Error('not_authorized', 'Missing integration permissions');
}

Try / catch

try {
  await Meteor.callAsync('updateOutgoingIntegration', id, payload);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'not_authorized') {
    // request one of the two manage permissions; do not retry unchanged
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling updateOutgoingIntegration (DDP or PUT /v1/integrations.update) with a user whose roles lack both integration permissions; permissions revoked after the admin UI was loaded.

Common situations: Non-admin users editing outgoing webhooks without manage-own-outgoing-integrations; stale sessions after role changes; API tokens belonging to under-privileged users.

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/07f018e58aab3591. Report an issue: GitHub.