matrix-art

An image gallery for Matrix
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-art.git
Log | Files | Refs | README | LICENSE

commit d6fb88bcac5e3b0e1e567be455a9e501ff37284a
parent c39a746d2388be39ceff2f251c5bcc7eec852b93
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sat, 22 Jan 2022 22:12:49 +0100

Secure the Directory API endpoint to make sure only the one trying to register can add themself or remove themself.

Diffstat:
Mhelpers/matrix_client.ts | 5+++++
Ahelpers/ss-well-known.ts | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackage-lock.json | 168-------------------------------------------------------------------------------
Mpages/api/directory.tsx | 51+++++++++++++++++++++++++++++++--------------------
Mpages/login.tsx | 3++-
5 files changed, 229 insertions(+), 189 deletions(-)

diff --git a/helpers/matrix_client.ts b/helpers/matrix_client.ts @@ -140,6 +140,11 @@ export default class MatrixClient { } } + async getOpenidToken(): Promise<string> { + const data = await this.fetchJson(`${this.userId!.slice(this.userId!.indexOf(":") + 1)}/_matrix/client/r0/user/${this.userId}/openid/request_token`, { method: "POST" }); + return data.access_token; + } + async register(serverUrl: string, username: string, password: string) { const data = await this.fetchJson(`${serverUrl}/r0/register`, { method: "POST", diff --git a/helpers/ss-well-known.ts b/helpers/ss-well-known.ts @@ -0,0 +1,190 @@ +import { promises } from "node:dns"; +import { isIP } from "node:net"; + +type SSWellKnownContent = { + "m.server": string; +}; + +type OpenIDResp = { + sub: string; +}; + +export default class ServerOpenID { + private resolver = new promises.Resolver(); + private splitRegex = /(.*)(:)(\d+)$/; + + constructor() { + this.resolver.setServers(['95.217.202.35', '8.8.8.8', '8.8.4.4']); + } + + private async lookupWellKnown(server: string): Promise<{ address: string; host_header: string; } | undefined> { + // Step 3 of server resolving + const resp = await fetch(`https://${server}/.well-known/matrix/server`, { method: "GET" }); + if (resp.status === 200) { + console.log("Step 3"); + const data: SSWellKnownContent = await resp.json(); + + + // If we have 1 part we have no port. If we have 5 we have a port. + // This regex gives us the port at index 4 and ip at index 1. + const delegatedServer = data["m.server"]; + const possibleDelegatedServerParts = delegatedServer.split(this.splitRegex); + if (possibleDelegatedServerParts.length === 1) { + // Step 3.1 + console.log("Step 3.1"); + if (isIP(possibleDelegatedServerParts[0]) > 0) { + return { address: `${delegatedServer}:8448`, host_header: delegatedServer }; + } else { + // Step 3.3.1 + console.log("Step 3.3.1"); + const srv = await this.resolver.resolveSrv(`_matrix._tcp.${possibleDelegatedServerParts[0]}`); + if (srv.length > 0) { + // Step 3.3.3 + console.log("Step 3.3.3"); + if (isIP(srv[0].name) > 0) { + return { address: `${srv[0].name}:${srv[0].port}`, host_header: delegatedServer }; + } else { + console.log("Step 3.3.2"); + // Step 3.3.2 + const result_v4 = await this.resolver.resolve(srv[0].name, "A"); + if (result_v4.length > 0) { + return { address: `${result_v4[0]}:${srv[0].port}`, host_header: delegatedServer }; + } + + const result_v6 = await this.resolver.resolve(srv[0].name, "AAAA"); + if (result_v6.length > 0) { + return { address: `[${result_v6[0]}]:${srv[0].port}`, host_header: delegatedServer }; + } + throw new Error("Unable to resolve the server"); + } + } else { + // Step 3.4 + console.log("Step 3.4"); + const result_v4 = await this.resolver.resolve(possibleDelegatedServerParts[0], "A"); + if (result_v4.length > 0) { + return { address: `${result_v4[0]}:8448`, host_header: delegatedServer }; + } + + const result_v6 = await this.resolver.resolve(possibleDelegatedServerParts[0], "AAAA"); + if (result_v6.length > 0) { + return { address: `[${result_v6[0]}]:8448`, host_header: delegatedServer }; + } + + throw new Error("Unable to resolve the server"); + } + } + } else if (possibleDelegatedServerParts.length === 5) { + // Step 3.1 + if (isIP(possibleDelegatedServerParts[1]) > 0) { + console.log("Step 3.1"); + return { address: delegatedServer, host_header: delegatedServer }; + } else { + // Step 3.2 + console.log("Step 3.2"); + const result_v4 = await this.resolver.resolve(possibleDelegatedServerParts[1], "A"); + if (result_v4.length > 0) { + return { address: `${result_v4[0]}:${possibleDelegatedServerParts[4]}`, host_header: delegatedServer }; + } + + const result_v6 = await this.resolver.resolve(possibleDelegatedServerParts[1], "AAAA"); + if (result_v6.length > 0) { + return { address: `[${result_v6[0]}]:${possibleDelegatedServerParts[4]}`, host_header: delegatedServer }; + } + + throw new Error("Unable to resolve the server"); + } + } + } + return undefined; + } + + private async lookup(server: string): Promise<{ address: string; host_header: string; }> { + // If we have 1 part we have no port. If we have 5 we have a port. + // This regex gives us the port at index 4 and ip at index 1. + const possibleServerParts = server.split(this.splitRegex); + + // Step 1 of Server Discovery + if (possibleServerParts.length === 1) { + if (isIP(possibleServerParts[0]) > 0) { + console.log("Step 1a"); + return { address: `${server}:8448`, host_header: server }; + } + } else if (possibleServerParts.length === 5) { + if (isIP(possibleServerParts[1]) > 0) { + console.log("Step 1b"); + return { address: server, host_header: server }; + } else { + // Step 2 of Server Discovery. + console.log("Step 2"); + const result_v4 = await this.resolver.resolve(possibleServerParts[1], "A"); + if (result_v4.length > 0) { + return { address: `${result_v4[0]}:${possibleServerParts[4]}`, host_header: server }; + } + + const result_v6 = await this.resolver.resolve(possibleServerParts[1], "AAAA"); + if (result_v6.length > 0) { + return { address: `[${result_v6[0]}]:${possibleServerParts[4]}`, host_header: server }; + } + + throw new Error("Unable to resolve the server"); + } + } + try { + const wellKnownResult = await this.lookupWellKnown(server); + if (wellKnownResult) { + return wellKnownResult; + } + } catch (error) { + throw error; + } + // Step 4 + const srv = await this.resolver.resolveSrv(`_matrix._tcp.${server}`); + if (srv.length > 0) { + console.log("Step 4"); + if (isIP(srv[0].name) > 0) { + return { address: `${srv[0].name}:${srv[0].port}`, host_header: server }; + } else { + const result_v4 = await this.resolver.resolve(srv[0].name, "A"); + if (result_v4.length > 0) { + return { address: `${result_v4[0]}:${srv[0].port}`, host_header: server }; + } + + const result_v6 = await this.resolver.resolve(srv[0].name, "AAAA"); + if (result_v6.length > 0) { + return { address: `[${result_v6[0]}]:${srv[0].port}`, host_header: server }; + } + throw new Error("Unable to resolve the server"); + } + } else { + console.log("Step 5"); + // Step 5 + const result_v4 = await this.resolver.resolve(server, "A"); + if (result_v4.length > 0) { + return { address: `${result_v4[0]}:8448`, host_header: server }; + } + + const result_v6 = await this.resolver.resolve(server, "AAAA"); + if (result_v6.length > 0) { + return { address: `[${result_v6[0]}]:8448`, host_header: server }; + } + + throw new Error("Unable to resolve the server"); + } + } + + public async verify(mxid: string, openidToken: string): Promise<boolean> { + try { + const server_address = await this.lookup(mxid.slice(mxid.indexOf(":") + 1)); + const resp = await fetch(`https://${server_address.address}/_matrix/federation/v1/openid/userinfo?access_token=${openidToken}`, { method: "GET", headers: { "Host": server_address.host_header } }); + if (resp.status === 200) { + const data: OpenIDResp = await resp.json(); + return (data.sub === mxid); + } else { + return false; + } + } catch (error: any) { + console.error(error); + return false; + } + } +} +\ No newline at end of file diff --git a/package-lock.json b/package-lock.json @@ -1012,96 +1012,6 @@ } } }, - "node_modules/@next/swc-android-arm64": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.0.8.tgz", - "integrity": "sha512-BiXMcOZNnXSIXv+FQvbRgbMb+iYayLX/Sb2MwR0wja+eMs46BY1x/ssXDwUBADP1M8YtrGTlSPHZqUiCU94+Mg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.0.8.tgz", - "integrity": "sha512-6EGMmvcIwPpwt0/iqLbXDGx6oKHAXzbowyyVXK8cqmIvhoghRFjqfiNGBs+ar6wEBGt68zhwn/77vE3iQWoFJw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.0.8.tgz", - "integrity": "sha512-todxgQOGP/ucz5UH2kKR3XGDdkWmWr0VZAAbzgTbiFm45Ol4ih602k2nNR3xSbza9IqNhxNuUVsMpBgeo19CFQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm-gnueabihf": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.0.8.tgz", - "integrity": "sha512-KULmdrfI+DJxBuhEyV47MQllB/WpC3P2xbwhHezxL/LkC2nkz5SbV4k432qpx2ebjIRf9SjdQ5Oz1FjD8Urayw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.0.8.tgz", - "integrity": "sha512-1XO87wgIVPvt5fx5i8CqdhksRdcpqyzCOLW4KrE0f9pUCIT04EbsFiKdmsH9c73aqjNZMnCMXpbV+cn4hN8x1w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.0.8.tgz", - "integrity": "sha512-NStRZEy/rkk2G18Yhc/Jzi1Q2Dv+zH176oO8479zlDQ5syRfc6AvRHVV4iNRc8Pai58If83r/nOJkwFgGwkKLw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-linux-x64-gnu": { "version": "12.0.8", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.0.8.tgz", @@ -1132,36 +1042,6 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.0.8.tgz", - "integrity": "sha512-QuRe49jqCV61TysGopC1P0HPqFAMZMWe1nbIQLyOkDLkULmZR8N2eYZq7fwqvZE5YwhMmJA/grwWFVBqSEh5Kg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.0.8.tgz", - "integrity": "sha512-0RV3/julybJr1IlPCowIWrJJZyAl+sOakJEM15y1NOOsbwTQ5eKZZXSi+7e23TN4wmy5HwNvn2dKzgOEVJ+jbA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { "version": "12.0.8", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.0.8.tgz", @@ -7575,42 +7455,6 @@ "integrity": "sha512-Bq4T/aOOFQUkCF9b8k9x+HpjOevu65ZPxsYJOpgEtBuJyvb+sZREtDDLKb/RtjUeLMrWrsGD0aLteyFFtiS8Og==", "requires": {} }, - "@next/swc-android-arm64": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.0.8.tgz", - "integrity": "sha512-BiXMcOZNnXSIXv+FQvbRgbMb+iYayLX/Sb2MwR0wja+eMs46BY1x/ssXDwUBADP1M8YtrGTlSPHZqUiCU94+Mg==", - "optional": true - }, - "@next/swc-darwin-arm64": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.0.8.tgz", - "integrity": "sha512-6EGMmvcIwPpwt0/iqLbXDGx6oKHAXzbowyyVXK8cqmIvhoghRFjqfiNGBs+ar6wEBGt68zhwn/77vE3iQWoFJw==", - "optional": true - }, - "@next/swc-darwin-x64": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.0.8.tgz", - "integrity": "sha512-todxgQOGP/ucz5UH2kKR3XGDdkWmWr0VZAAbzgTbiFm45Ol4ih602k2nNR3xSbza9IqNhxNuUVsMpBgeo19CFQ==", - "optional": true - }, - "@next/swc-linux-arm-gnueabihf": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.0.8.tgz", - "integrity": "sha512-KULmdrfI+DJxBuhEyV47MQllB/WpC3P2xbwhHezxL/LkC2nkz5SbV4k432qpx2ebjIRf9SjdQ5Oz1FjD8Urayw==", - "optional": true - }, - "@next/swc-linux-arm64-gnu": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.0.8.tgz", - "integrity": "sha512-1XO87wgIVPvt5fx5i8CqdhksRdcpqyzCOLW4KrE0f9pUCIT04EbsFiKdmsH9c73aqjNZMnCMXpbV+cn4hN8x1w==", - "optional": true - }, - "@next/swc-linux-arm64-musl": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.0.8.tgz", - "integrity": "sha512-NStRZEy/rkk2G18Yhc/Jzi1Q2Dv+zH176oO8479zlDQ5syRfc6AvRHVV4iNRc8Pai58If83r/nOJkwFgGwkKLw==", - "optional": true - }, "@next/swc-linux-x64-gnu": { "version": "12.0.8", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.0.8.tgz", @@ -7623,18 +7467,6 @@ "integrity": "sha512-1F4kuFRQE10GSx7LMSvRmjMXFGpxT30g8rZzq9r/p/WKdErA4WB4uxaKEX0P8AINfuN63i4luKdR+LoacgBhYw==", "optional": true }, - "@next/swc-win32-arm64-msvc": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.0.8.tgz", - "integrity": "sha512-QuRe49jqCV61TysGopC1P0HPqFAMZMWe1nbIQLyOkDLkULmZR8N2eYZq7fwqvZE5YwhMmJA/grwWFVBqSEh5Kg==", - "optional": true - }, - "@next/swc-win32-ia32-msvc": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.0.8.tgz", - "integrity": "sha512-0RV3/julybJr1IlPCowIWrJJZyAl+sOakJEM15y1NOOsbwTQ5eKZZXSi+7e23TN4wmy5HwNvn2dKzgOEVJ+jbA==", - "optional": true - }, "@next/swc-win32-x64-msvc": { "version": "12.0.8", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.0.8.tgz", diff --git a/pages/api/directory.tsx b/pages/api/directory.tsx @@ -3,6 +3,7 @@ import initMiddleware from '../../helpers/init-middleware'; import type { NextApiRequest, NextApiResponse } from 'next'; import PouchDB from 'pouchdb'; import path from 'node:path'; +import ServerOpenID from '../../helpers/ss-well-known'; const db = new PouchDB(path.join(process.cwd(), "matrix-art-db")); @@ -51,34 +52,44 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const data: { user_id: string; user_room: string; + access_token: string; } = req.body; - const db_data = { - _id: data.user_id, - user_id: data.user_id, - user_room: data.user_room, - }; - try { - await db.put(db_data); - res.status(200).json({}); - } catch (error) { - res.status(502).json({}); - console.error(error); + if (await (new ServerOpenID().verify(data.user_id, data.access_token))) { + const db_data = { + _id: data.user_id, + user_id: data.user_id, + user_room: data.user_room, + }; + try { + await db.put(db_data); + res.status(200).json({}); + } catch (error) { + res.status(502).json({}); + console.error(error); + } + } else { + res.status(401).json({}); } + + } else if (req.method == "DELETE") { const data: { user_id: string; + access_token: string; } = req.body; - console.log(data.user_id); - try { - const db_data = await db.get(data.user_id); - await db.remove(db_data); - res.status(200).json({}); - } catch (error) { - res.status(502).json({}); - console.error(error); + if (await (new ServerOpenID().verify(data.user_id, data.access_token))) { + try { + const db_data = await db.get(data.user_id); + await db.remove(db_data); + res.status(200).json({}); + } catch (error) { + res.status(502).json({}); + console.error(error); + } + } else { + res.status(401).json({}); } - } else { res.status(405).json({}); } diff --git a/pages/login.tsx b/pages/login.tsx @@ -82,7 +82,8 @@ class Login extends PureComponent<Props, State> { await this.context.client.login(serverUrl, this.state.mxid, this.state.password, true); if (this.state.generateProfile) { await this.context.client.followUser(`#${this.context.client.userId}`); - await fetch("/api/directory", { method: "POST", body: JSON.stringify({ user_id: this.context.client.userId, user_room: `#${this.context.client.userId}` }) }); + const token = await this.context.client.getOpenidToken(); + await fetch("/api/directory", { method: "POST", body: JSON.stringify({ access_token: token, user_id: this.context.client.userId, user_room: `#${this.context.client.userId}` }) }); } if (typeof window !== "undefined") { window.location.reload();