Skip to content

Commit da5ca74

Browse files
babolivierpaulmelnikow
authored andcommitted
Improve [Matrix] badge generation (#2527)
Fixes #2524 This PR addresses the issues expressed in #2524, in that it: * checks if a server has advertised a FQDN it can be reached at and if that FQDN hosts Matrix's client APIs * uses room aliases instead of room IDs, in order to avoid a badge being impossible to generate if the server that created the room leaves it This includes a breaking change to the badge endpoint.
1 parent 2fe61d2 commit da5ca74

File tree

2 files changed

+339
-42
lines changed

2 files changed

+339
-42
lines changed

services/matrix/matrix.service.js

+97-25
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22

33
const Joi = require('joi')
44
const BaseJsonService = require('../base-json')
5+
const errors = require('../errors')
6+
7+
const queryParamSchema = Joi.object({
8+
server_fqdn: Joi.string().hostname(),
9+
}).required()
510

611
const matrixRegisterSchema = Joi.object({
712
access_token: Joi.string().required(),
813
}).required()
914

15+
const matrixAliasLookupSchema = Joi.object({
16+
room_id: Joi.string().required(),
17+
})
18+
1019
const matrixStateSchema = Joi.array()
1120
.items(
1221
Joi.object({
@@ -31,15 +40,36 @@ const documentation = `
3140
<ul>
3241
<li>Select the desired room inside the Riot.im client</li>
3342
<li>Click on the room settings button (gear icon) located near the top right of the client</li>
34-
<li>Scroll to the very bottom of the settings page and look under the <code>Advanced</code> tab</li>
35-
<li>You should see the <code>Internal room ID</code> with your rooms ID next to it (ex: <code>!ltIjvaLydYAWZyihee:matrix.org</code>)</li>
36-
<li>Replace the IDs <code>:</code> with <code>/</code></li>
37-
<li>The final badge URL should look something like this <code>/matrix/!ltIjvaLydYAWZyihee/matrix.org.svg</code></li>
43+
<li>Scroll to the very bottom of the settings page and look under the <code>Addresses</code> section</li>
44+
<li>You should see one or more <code>room addresses (or aliases)</code>, which can be easily identified with their starting hash (<code>#</code>) character (ex: <code>#twim:matrix.org</code>)</li>
45+
<li>If there is no address for this room, add one under <code>Local addresses for this room</code></li>
46+
<li>Remove the starting hash character (<code>#</code>)</li>
47+
<li>The final badge URL should look something like this <code>/matrix/twim:matrix.org.svg</code></li>
3848
</ul>
49+
</br>
50+
Some Matrix homeservers don't hold a server name matching where they live (e.g. if the homeserver <code>example.com</code> that created the room alias <code>#mysuperroom:example.com</code> lives at <code>matrix.example.com</code>).
51+
</br>
52+
If that is the case of the homeserver that created the room alias used for generating the badge, you will need to add the server's FQDN (fully qualified domain name) as a query parameter.
53+
</br>
54+
The final badge URL should then look something like this <code>/matrix/mysuperroom:example.com.svg?server_fqdn=matrix.example.com</code>.
3955
</p>
4056
`
4157

4258
module.exports = class Matrix extends BaseJsonService {
59+
async retrieveAccessToken({ host }) {
60+
let auth
61+
try {
62+
auth = await this.registerAccount({ host, guest: true })
63+
} catch (e) {
64+
if (e.prettyMessage === 'guests not allowed') {
65+
// attempt fallback method
66+
auth = await this.registerAccount({ host, guest: false })
67+
} else throw e
68+
}
69+
70+
return auth.access_token
71+
}
72+
4373
async registerAccount({ host, guest }) {
4474
return this._requestJson({
4575
url: `https://${host}/_matrix/client/r0/register`,
@@ -59,27 +89,59 @@ module.exports = class Matrix extends BaseJsonService {
5989
errorMessages: {
6090
401: 'auth failed',
6191
403: 'guests not allowed',
62-
429: 'rate limited by rooms host',
92+
429: 'rate limited by remote server',
6393
},
6494
})
6595
}
6696

67-
async fetch({ host, roomId }) {
68-
let auth
69-
try {
70-
auth = await this.registerAccount({ host, guest: true })
71-
} catch (e) {
72-
if (e.prettyMessage === 'guests not allowed') {
73-
// attempt fallback method
74-
auth = await this.registerAccount({ host, guest: false })
75-
} else throw e
97+
async lookupRoomAlias({ host, roomAlias, accessToken }) {
98+
return this._requestJson({
99+
url: `https://${host}/_matrix/client/r0/directory/room/${encodeURIComponent(
100+
`#${roomAlias}`
101+
)}`,
102+
schema: matrixAliasLookupSchema,
103+
options: {
104+
qs: {
105+
access_token: accessToken,
106+
},
107+
},
108+
errorMessages: {
109+
401: 'bad auth token',
110+
404: 'room not found',
111+
429: 'rate limited by remote server',
112+
},
113+
})
114+
}
115+
116+
async fetch({ roomAlias, serverFQDN }) {
117+
let host
118+
if (serverFQDN === undefined) {
119+
const splitAlias = roomAlias.split(':')
120+
// A room alias can either be in the form #localpart:server or
121+
// #localpart:server:port.
122+
switch (splitAlias.length) {
123+
case 2:
124+
host = splitAlias[1]
125+
break
126+
case 3:
127+
host = `${splitAlias[1]}:${splitAlias[2]}`
128+
break
129+
default:
130+
throw new errors.InvalidParameter({ prettyMessage: 'invalid alias' })
131+
}
132+
} else {
133+
host = serverFQDN
76134
}
135+
const accessToken = await this.retrieveAccessToken({ host })
136+
const lookup = await this.lookupRoomAlias({ host, roomAlias, accessToken })
77137
const data = await this._requestJson({
78-
url: `https://${host}/_matrix/client/r0/rooms/${roomId}/state`,
138+
url: `https://${host}/_matrix/client/r0/rooms/${encodeURIComponent(
139+
lookup.room_id
140+
)}/state`,
79141
schema: matrixStateSchema,
80142
options: {
81143
qs: {
82-
access_token: auth.access_token,
144+
access_token: accessToken,
83145
},
84146
},
85147
errorMessages: {
@@ -109,11 +171,12 @@ module.exports = class Matrix extends BaseJsonService {
109171
}
110172
}
111173

112-
async handle({ roomId, host, authServer }) {
113-
const members = await this.fetch({
114-
host,
115-
roomId: `${roomId}:${host}`,
116-
})
174+
async handle({ roomAlias }, queryParams) {
175+
const { server_fqdn: serverFQDN } = this.constructor._validateQueryParams(
176+
queryParams,
177+
queryParamSchema
178+
)
179+
const members = await this.fetch({ roomAlias, serverFQDN })
117180
return this.constructor.render({ members })
118181
}
119182

@@ -128,17 +191,26 @@ module.exports = class Matrix extends BaseJsonService {
128191
static get route() {
129192
return {
130193
base: 'matrix',
131-
format: '([^/]+)/([^/]+)',
132-
capture: ['roomId', 'host'],
194+
format: '([^/]+)',
195+
capture: ['roomAlias'],
196+
queryParams: ['server_fqdn'],
133197
}
134198
}
135199

136200
static get examples() {
137201
return [
138202
{
139203
title: 'Matrix',
140-
exampleUrl: '!ltIjvaLydYAWZyihee/matrix.org',
141-
pattern: ':roomId/:host',
204+
namedParams: { roomAlias: 'twim:matrix.org' },
205+
pattern: ':roomAlias',
206+
staticExample: this.render({ members: 42 }),
207+
documentation,
208+
},
209+
{
210+
title: 'Matrix',
211+
namedParams: { roomAlias: 'twim:matrix.org' },
212+
queryParams: { server_fqdn: 'matrix.org' },
213+
pattern: ':roomAlias',
142214
staticExample: this.render({ members: 42 }),
143215
documentation,
144216
},

0 commit comments

Comments
 (0)