RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-user

error-invalid-user

Error message

Invalid user

What it means

getS3FileUrl resolves the caller via Meteor.userId(); when the workspace setting FileUpload_ProtectFiles is enabled and there is no logged-in user, it throws error-invalid-user. The details object misleadingly says method 'sendFileMessage' — a copy-paste artifact; match on the code, not the method detail. The method then returns a redirect URL from the AmazonS3:Uploads store, which is why anonymous access is blocked when protection is on.

Source

Thrown at apps/meteor/server/meteor-methods/media/getS3FileUrl.ts:22

import { Meteor } from 'meteor/meteor';

import { canAccessRoomAsync } from '../../lib/authorization';
import { settings } from '../../settings';
import { UploadFS } from '../../ufs';

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		getS3FileUrl(fileId: string): string;
	}
}

Meteor.methods<ServerMethods>({
	async getS3FileUrl(fileId) {
		check(fileId, String);
		const uid = Meteor.userId();
		if (settings.get<boolean>('FileUpload_ProtectFiles') && !uid) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'sendFileMessage' });
		}
		const file = await Uploads.findOneById(fileId);
		if (!file?.rid) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed');
		}
		const room = await Rooms.findOneById(file.rid);
		if (uid && room && !(await canAccessRoomAsync(room, { _id: uid }))) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed');
		}

		return UploadFS.getStore('AmazonS3:Uploads').getRedirectURL(file);
	},
});

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Authenticate the client before requesting the URL
  2. For external sharing, use the signed/upload token returned by the upload flow (file link tokens) rather than this method
  3. Only if anonymous access is intentionally public, disable FileUpload_ProtectFiles — understand this removes protection for all uploads

Example fix

// before (anonymous call on a protected workspace)
const url = Meteor.call('getS3FileUrl', fileId);

// after
if (!Meteor.userId()) {
  // settings.get('FileUpload_ProtectFiles') is true: login first
  throw new Meteor.Error('error-invalid-user', 'Login required');
}
const url = await Meteor.callAsync('getS3FileUrl', fileId);
Defensive patterns

Strategy: validation

Validate before calling

if (!Meteor.userId()) {
  // FileUpload_ProtectFiles blocks anonymous getS3FileUrl: login first
  throw new Meteor.Error('error-invalid-user', 'Login required');
}
const url = await Meteor.callAsync('getS3FileUrl', fileId);

Try / catch

try {
  const url = await Meteor.callAsync('getS3FileUrl', fileId);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'error-invalid-user') {
    // protected files need a session: authenticate and retry once
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling getS3FileUrl(fileId) from an anonymous/guest context while FileUpload_ProtectFiles = true; also DDP calls after session expiry on protected workspaces.

Common situations: Embedding or hot-linking S3-hosted uploads in external pages or scripts without a session; enabling FileUpload_ProtectFiles on a workspace whose consumers were previously anonymous.

Related errors


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