diff --git a/README.md b/README.md index a0c6e14..46c98d7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The following changes are already implemented: * [Federation page improvements](https://github.com/Awesome-Technologies/synapse-admin/pull/583) (using theme colors) * [Add UI option to block deleted rooms from being rejoined](https://github.com/etkecc/synapse-admin/pull/26) * [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32) +* [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33) _the list will be updated as new changes are added_ diff --git a/src/components/ImportFeature.tsx b/src/components/ImportFeature.tsx index ebd9b0f..3407fda 100644 --- a/src/components/ImportFeature.tsx +++ b/src/components/ImportFeature.tsx @@ -15,7 +15,7 @@ import { import { DataProvider, useTranslate } from "ra-core"; import { useDataProvider, useNotify, RaRecord, Title } from "react-admin"; -import { generateRandomMxId, generateRandomPassword } from "../synapse/synapse"; +import { generateRandomMxId, generateRandomPassword, returnMXID } from "../synapse/synapse"; const LOGGING = true; @@ -74,7 +74,7 @@ const FilePicker = () => { const [conflictMode, setConflictMode] = useState("stop"); const [passwordMode, setPasswordMode] = useState(true); - const [useridMode, setUseridMode] = useState("ignore"); + const [useridMode, setUseridMode] = useState("update"); const translate = useTranslate(); const notify = useNotify(); @@ -266,12 +266,15 @@ const FilePicker = () => { const userRecord = { ...entry }; // No need to do a bunch of cryptographic random number getting if // we are using neither a generated password nor a generated user id. - if (useridMode === "ignore" || userRecord.id === undefined) { + if (useridMode === "ignore" || userRecord.id === undefined || userRecord.id === "") { userRecord.id = generateRandomMxId(); } - if (passwordMode === false || entry.password === undefined) { + if (passwordMode === false || entry.password === undefined || entry.password === "") { userRecord.password = generateRandomPassword(); } + // we want to ensure that the ID is always full MXID, otherwise randomly-generated MXIDs will be in the full + // form, but the ones from the CSV will be localpart-only. + userRecord.id = returnMXID(userRecord.id); /* TODO record update stats (especially admin no -> yes, deactivated x -> !x, ... */ /* For these modes we will consider the ID that's in the record. diff --git a/src/synapse/dataProvider.ts b/src/synapse/dataProvider.ts index d69a760..ada0b91 100644 --- a/src/synapse/dataProvider.ts +++ b/src/synapse/dataProvider.ts @@ -3,6 +3,7 @@ import { stringify } from "query-string"; import { DataProvider, DeleteParams, HttpError, Identifier, Options, RaRecord, fetchUtils } from "react-admin"; import storage from "../storage"; +import { returnMXID } from "./synapse.ts" import { MatrixError, displayError } from "../components/error"; // Adds the access token to all requests @@ -234,7 +235,7 @@ const resourceMap = { path: "/_synapse/admin/v2/users", map: (u: User) => ({ ...u, - id: u.name, + id: returnMXID(u.name), avatar_src: u.avatar_url ? mxcUrlToHttp(u.avatar_url) : undefined, is_guest: !!u.is_guest, admin: !!u.admin, @@ -245,12 +246,12 @@ const resourceMap = { data: "users", total: json => json.total, create: (data: RaRecord) => ({ - endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(data.id)}:${storage.getItem("home_server")}`, + endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(returnMXID(data.id))}`, body: data, method: "PUT", }), delete: (params: DeleteParams) => ({ - endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(params.id)}`, + endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(returnMXID(params.id))}`, body: { erase: true }, method: "POST", }), @@ -349,7 +350,7 @@ const resourceMap = { id: um.media_id, }), reference: (id: Identifier) => ({ - endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`, + endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/media`, }), data: "media", total: json => json.total, @@ -384,7 +385,7 @@ const resourceMap = { create: (data: RaServerNotice) => ({ endpoint: "/_synapse/admin/v1/send_server_notice", body: { - user_id: data.id, + user_id: returnMXID(data.id), content: { msgtype: "m.text", body: data.body, @@ -397,7 +398,7 @@ const resourceMap = { path: "/_synapse/admin/v1/statistics/users/media", map: (usms: UserMediaStatistic) => ({ ...usms, - id: usms.user_id, + id: returnMXID(usms.user_id), }), data: "users", total: json => json.total, diff --git a/src/synapse/synapse.ts b/src/synapse/synapse.ts index 3180fb9..13c18f3 100644 --- a/src/synapse/synapse.ts +++ b/src/synapse/synapse.ts @@ -72,6 +72,26 @@ export function generateRandomMxId(): string { return `@${localpart}:${homeserver}`; } +/** + * Return the full MXID from an arbitrary input + * @param input the input string + * @returns full MXID as string + */ +export function returnMXID(input: string): string { + const homeserver = storage.getItem("home_server"); + + // Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":") + const mxidPattern = /^@[^@:]+:[^@:]+$/; + if (mxidPattern.test(input)) { + return input; // Already a valid MXID + } + + // If input is not a valid MXID, assume it's a localpart and construct the MXID + const localpart = input.startsWith('@') ? input.slice(1) : input; + return `@${localpart}:${homeserver}`; +} + + /** * Generate a random user password * @returns a new random password as string