Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/old-geckos-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Added the ability to serve .well-known paths directly from Rocket.Chat, if using federation, removing the need for special reverse proxy configuration or another component layer for specific types of reverse proxies / loadbalancers.
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@ export class RocketChatSettingsAdapter {
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_serve_well_known', true, {
readonly: false,
type: 'boolean',
i18nLabel: 'Federation_Matrix_serve_well_known',
alert: 'Federation_Matrix_serve_well_known_Alert',
group: 'Federation',
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_enable_ephemeral_events', false, {
readonly: false,
type: 'boolean',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { ServerResponse, IncomingMessage } from 'node:http';
import { URL } from 'node:url';

import { WebApp } from 'meteor/webapp';

import { settings } from '../../../../../app/settings/server';

async function returnMatrixServerJSON(_: IncomingMessage, res: ServerResponse) {
if (!settings.get<boolean>('Federation_Matrix_enabled') || !settings.get<boolean>('Federation_Matrix_serve_well_known')) {
return res.writeHead(404).end();
}

const homeserverUrl = settings.get<string>('Federation_Matrix_homeserver_url');
const { hostname, port = '443' } = new URL(homeserverUrl); // a case where port isn't specified would be if it's 80 or 443, if 80, federation isn't going to work, so we simply assume 443.

res.setHeader('content-type', 'application/json');

res.write(JSON.stringify({ 'm.server': `${hostname}:${port || '443'}` }));

res.end();
}

async function returnMatrixClientJSON(_: IncomingMessage, res: ServerResponse) {
if (!settings.get<boolean>('Federation_Matrix_enabled') || !settings.get<boolean>('Federation_Matrix_serve_well_known')) {
return res.writeHead(404).end();
}

const homeserverUrl = settings.get<string>('Federation_Matrix_homeserver_url');
const { protocol = 'https:', hostname } = new URL(homeserverUrl);

res.setHeader('content-type', 'application/json');

res.write(JSON.stringify({ 'm.homeserver': `${protocol}//${hostname}` }));

res.end();
}

WebApp.connectHandlers.use('/.well-known/matrix/server', returnMatrixServerJSON);
WebApp.connectHandlers.use('/.well-known/matrix/client', returnMatrixClientJSON);
6 changes: 5 additions & 1 deletion apps/meteor/server/services/federation/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type { RocketChatUserAdapter } from './infrastructure/rocket-chat/adapter
import { FederationRoomSenderConverter } from './infrastructure/rocket-chat/converters/RoomSender';
import { FederationHooks } from './infrastructure/rocket-chat/hooks';

import './infrastructure/rocket-chat/well-known';

export abstract class AbstractFederationService extends ServiceClassInternal {
private cancelSettingsObserver: () => void;

Expand Down Expand Up @@ -121,10 +123,12 @@ export abstract class AbstractFederationService extends ServiceClassInternal {
if (!this.isRunning) {
return;
}

if (isFederationEnabled) {
await this.onDisableFederation();
return this.onEnableFederation();
}

return this.onDisableFederation();
}

Expand All @@ -151,7 +155,7 @@ export abstract class AbstractFederationService extends ServiceClassInternal {
this.internalQueueInstance,
this.bridge,
);
const federationMessageServiceReceiver = await FederationFactory.buildMessageServiceReceiver(
const federationMessageServiceReceiver = FederationFactory.buildMessageServiceReceiver(
this.internalRoomAdapter,
this.internalUserAdapter,
this.internalMessageAdapter,
Expand Down
77 changes: 77 additions & 0 deletions apps/meteor/tests/end-to-end/api/33-federation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';

import { getCredentials, request } from '../../data/api-data';
import { updateSetting } from '../../data/permissions.helper';

describe('federation', function () {
this.retries(0);

before((done) => getCredentials(done));

describe('well-known', () => {
describe('when matrix disabled', () => {
before(async () => {
await updateSetting('Federation_Matrix_enabled', false);
await updateSetting('Federation_Matrix_serve_well_known', true);
});

after(async () => {
await updateSetting('Federation_Matrix_serve_well_known', false);
});

it('should return 404 not found', async () => {
await request.get('/.well-known/matrix/server').expect(404);

await request.get('/.well-known/matrix/client').expect(404);
});
});

describe('when matrix enabled but well-known disabled', () => {
before(async () => {
await updateSetting('Federation_Matrix_enabled', true);
await updateSetting('Federation_Matrix_serve_well_known', false);
});

after(async () => {
await updateSetting('Federation_Matrix_enabled', false);
});

it('should return 404 not found', async () => {
await request.get('/.well-known/matrix/server').expect(404);

await request.get('/.well-known/matrix/client').expect(404);
});
});

describe('when enabled', () => {
before(async () => {
await updateSetting('Federation_Matrix_enabled', true);
await updateSetting('Federation_Matrix_serve_well_known', true);
});

after(async () => {
await updateSetting('Federation_Matrix_enabled', false);
await updateSetting('Federation_Matrix_serve_well_known', false);
});

it('should return matrix information', async () => {
await request
.get('/.well-known/matrix/server')
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('m.server', 'localhost:8008');
});

await request
.get('/.well-known/matrix/client')
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('m.homeserver', 'http://localhost');
});
});
});
});
});
3 changes: 3 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2308,6 +2308,9 @@
"Federation_Matrix_max_size_of_public_rooms_users": "Maximum number of users when joining a public room in a remote server",
"Federation_Matrix_max_size_of_public_rooms_users_desc": "The number of the maximum users when joining a public room in a remote server. Public Rooms with more users will be ignored in the list of Public Rooms to join.",
"Federation_Matrix_max_size_of_public_rooms_users_Alert": "Keep in mind, that the bigger the room you allow for users to join, the more time it will take to join that room, besides the amount of resource it will use. <a target=\"_blank\" href=\"https://matrix.org/blog/2022/10/18/testing-faster-remote-room-joins\">Read more</a>",
"Federation_Matrix_serve_well_known": "Serve Well Known",
"Federation_Matrix_serve_well_known_Description": "Serve /.well-known/matrix/server and /.well-known/matrix/client directly from within Rocket.Chat instead of reverse proxy for federation",
"Federation_Matrix_serve_well_known_Alert": "Keep this off if using DNS srv records for federation, or use a reverse proxy to return static JSON if federation traffic is heavy. <a target=\"_blank\" href=\"https://matrix-org.github.io/synapse/latest/federate.html\">Read mode</a>.",
"Field": "Field",
"Field_removed": "Field removed",
"Field_required": "Field required",
Expand Down