Add token based registration creation and management (#200)

* Add token based registration creation and management

* yarn fix

* Apply suggestions from code review

Remove empty line

* move date to `const date_format`
This commit is contained in:
Dirk Klimpel 2022-02-17 20:45:21 +01:00 committed by GitHub
parent c891afa611
commit efed8b2774
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 2 deletions

View File

@ -5,7 +5,7 @@
This project is built using [react-admin](https://marmelab.com/react-admin/).
It needs at least Synapse v1.41.0 for all functions to work as expected!
It needs at least Synapse v1.42.0 for all functions to work as expected!
You get your server version with the request `/_synapse/admin/v1/server_version`.
See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).

View File

@ -8,12 +8,18 @@ import { RoomList, RoomShow } from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports";
import LoginPage from "./components/LoginPage";
import UserIcon from "@material-ui/icons/Group";
import ConfirmationNumberIcon from "@material-ui/icons/ConfirmationNumber";
import EqualizerIcon from "@material-ui/icons/Equalizer";
import { UserMediaStatsList } from "./components/statistics";
import RoomIcon from "@material-ui/icons/ViewList";
import ReportIcon from "@material-ui/icons/Warning";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
import { ImportFeature } from "./components/ImportFeature";
import {
RegistrationTokenCreate,
RegistrationTokenEdit,
RegistrationTokenList,
} from "./components/RegistrationTokens";
import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom";
import germanMessages from "./i18n/de";
@ -66,6 +72,13 @@ const App = () => (
list={RoomDirectoryList}
icon={FolderSharedIcon}
/>
<Resource
name="registration_tokens"
list={RegistrationTokenList}
create={RegistrationTokenCreate}
edit={RegistrationTokenEdit}
icon={ConfirmationNumberIcon}
/>
<Resource name="connections" />
<Resource name="devices" />
<Resource name="room_members" />

View File

@ -0,0 +1,132 @@
import React from "react";
import {
BooleanInput,
Create,
Datagrid,
DateField,
DateTimeInput,
Edit,
Filter,
List,
maxValue,
number,
NumberField,
NumberInput,
regex,
SimpleForm,
TextInput,
TextField,
Toolbar,
} from "react-admin";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const validateToken = [regex(/^[A-Za-z0-9._~-]{0,64}$/)];
const validateUsesAllowed = [number()];
const validateLength = [number(), maxValue(64)];
const dateParser = v => {
const d = new Date(v);
if (isNaN(d)) return 0;
return d.getTime();
};
const dateFormatter = v => {
if (v === undefined || v === null) return;
const d = new Date(v);
const pad = "00";
const year = d.getFullYear().toString();
const month = (pad + (d.getMonth() + 1).toString()).slice(-2);
const day = (pad + d.getDate().toString()).slice(-2);
const hour = (pad + d.getHours().toString()).slice(-2);
const minute = (pad + d.getMinutes().toString()).slice(-2);
// target format yyyy-MM-ddThh:mm
return `${year}-${month}-${day}T${hour}:${minute}`;
};
const RegistrationTokenFilter = props => (
<Filter {...props}>
<BooleanInput source="valid" alwaysOn />
</Filter>
);
export const RegistrationTokenList = props => {
return (
<List
{...props}
filters={<RegistrationTokenFilter />}
filterDefaultValues={{ valid: true }}
pagination={false}
perPage={500}
>
<Datagrid rowClick="edit">
<TextField source="token" sortable={false} />
<NumberField source="uses_allowed" sortable={false} />
<NumberField source="pending" sortable={false} />
<NumberField source="completed" sortable={false} />
<DateField
source="expiry_time"
showTime
options={date_format}
sortable={false}
/>
</Datagrid>
</List>
);
};
export const RegistrationTokenCreate = props => (
<Create {...props}>
<SimpleForm redirect="list" toolbar={<Toolbar alwaysEnableSaveButton />}>
<TextInput
source="token"
autoComplete="off"
validate={validateToken}
resettable
/>
<NumberInput
source="length"
validate={validateLength}
helperText="resources.registration_tokens.helper.length"
step={1}
/>
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput source="expiry_time" parse={dateParser} />
</SimpleForm>
</Create>
);
export const RegistrationTokenEdit = props => {
return (
<Edit {...props}>
<SimpleForm>
<TextInput source="token" disabled />
<NumberInput source="pending" disabled />
<NumberInput source="completed" disabled />
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput
source="expiry_time"
parse={dateParser}
format={dateFormatter}
/>
</SimpleForm>
</Edit>
);
};

View File

@ -352,6 +352,19 @@ const de = {
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
},
},
registration_tokens: {
name: "Registrierungstoken",
fields: {
token: "Token",
valid: "Gültige Token",
uses_allowed: "Anzahl",
pending: "Ausstehend",
completed: "Abgeschlossen",
expiry_time: "Ablaufzeit",
length: "Länge",
},
helper: { length: "Länge des Tokens, wenn kein Token vorgegeben wird." },
},
},
ra: {
...germanMessages.ra,

View File

@ -351,5 +351,18 @@ const en = {
},
},
},
registration_tokens: {
name: "Registration tokens",
fields: {
token: "Token",
valid: "Valid token",
uses_allowed: "Uses allowed",
pending: "Pending",
completed: "Completed",
expiry_time: "Expiry time",
length: "Length",
},
helper: { length: "Length of the token if no token is given." },
},
};
export default en;

View File

@ -275,6 +275,25 @@ const resourceMap = {
method: "PUT",
}),
},
registration_tokens: {
path: "/_synapse/admin/v1/registration_tokens",
map: rt => ({
...rt,
id: rt.token,
}),
data: "registration_tokens",
total: json => {
return json.registration_tokens.length;
},
create: params => ({
endpoint: "/_synapse/admin/v1/registration_tokens/new",
body: params,
method: "POST",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/registration_tokens/${params.id}`,
}),
},
};
function filterNullValues(key, value) {
@ -296,7 +315,8 @@ function getSearchOrder(order) {
const dataProvider = {
getList: (resource, params) => {
console.log("getList " + resource);
const { user_id, name, guests, deactivated, search_term } = params.filter;
const { user_id, name, guests, deactivated, search_term, valid } =
params.filter;
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const from = (page - 1) * perPage;
@ -308,6 +328,7 @@ const dataProvider = {
name: name,
guests: guests,
deactivated: deactivated,
valid: valid,
order_by: field,
dir: getSearchOrder(order),
};