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:
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();