Simple UI to reset cross-signing keys
This commit is contained in:
@@ -94,6 +94,19 @@
|
||||
"pagination_controls": {
|
||||
"total": "Total: {{totalCount}}"
|
||||
},
|
||||
"reset_cross_signing": {
|
||||
"button": "Allow cross-signing reset",
|
||||
"description": "If you have lost access to all your verified devices and your security key, you can reset your cross-signing keys. Deleting cross-signing keys is permanent, but will allow you to go through the verification process again and set up new keys.",
|
||||
"failure": {
|
||||
"description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.",
|
||||
"title": "Failed to allow cross-signing"
|
||||
},
|
||||
"heading": "Cross-signing reset",
|
||||
"success": {
|
||||
"description": "A client can now temporarily reset your account cross-signing keys. Follow the instructions in your client to complete the process.",
|
||||
"title": "Cross-signing reset temporarily allowed"
|
||||
}
|
||||
},
|
||||
"selectable_session": {
|
||||
"label": "Select session"
|
||||
},
|
||||
|
||||
95
frontend/src/components/UserProfile/CrossSigningReset.tsx
Normal file
95
frontend/src/components/UserProfile/CrossSigningReset.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Alert, Button, H3, Text } from "@vector-im/compound-web";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { graphql } from "../../gql";
|
||||
import BlockList from "../BlockList";
|
||||
import LoadingSpinner from "../LoadingSpinner";
|
||||
|
||||
const ALLOW_CROSS_SIGING_RESET_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation AllowCrossSigningReset($userId: ID!) {
|
||||
allowUserCrossSigningReset(input: { userId: $userId }) {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const allowCrossSigningResetFamily = atomFamily((id: string) => {
|
||||
const allowCrossSigingReset = atomWithMutation(
|
||||
ALLOW_CROSS_SIGING_RESET_MUTATION,
|
||||
);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const allowCrossSigningResetAtom = atom(
|
||||
(get) => get(allowCrossSigingReset),
|
||||
(_get, set) => set(allowCrossSigingReset, { userId: id }),
|
||||
);
|
||||
|
||||
return allowCrossSigningResetAtom;
|
||||
});
|
||||
|
||||
const CrossSigningReset: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const { t } = useTranslation();
|
||||
const [result, allowReset] = useAtom(allowCrossSigningResetFamily(userId));
|
||||
const [inProgress, setInProgress] = useState(false);
|
||||
|
||||
const onClick = (): void => {
|
||||
if (inProgress) return;
|
||||
setInProgress(true);
|
||||
allowReset().finally(() => setInProgress(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<BlockList>
|
||||
<H3>{t("frontend.reset_cross_signing.heading")}</H3>
|
||||
{!result.data && !result.error && (
|
||||
<>
|
||||
<Text className="text-justify">
|
||||
{t("frontend.reset_cross_signing.description")}
|
||||
</Text>
|
||||
<Button kind="destructive" disabled={inProgress} onClick={onClick}>
|
||||
{!!inProgress && <LoadingSpinner inline />}
|
||||
{t("frontend.reset_cross_signing.button")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{result.data && (
|
||||
<Alert
|
||||
type="info"
|
||||
title={t("frontend.reset_cross_signing.success.title")}
|
||||
>
|
||||
{t("frontend.reset_cross_signing.success.description")}
|
||||
</Alert>
|
||||
)}
|
||||
{result.error && (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={t("frontend.reset_cross_signing.failure.title")}
|
||||
>
|
||||
{t("frontend.reset_cross_signing.failure.description")}
|
||||
</Alert>
|
||||
)}
|
||||
</BlockList>
|
||||
);
|
||||
};
|
||||
|
||||
export default CrossSigningReset;
|
||||
@@ -12,8 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Separator } from "@vector-im/compound-web";
|
||||
|
||||
import BlockList from "../BlockList/BlockList";
|
||||
|
||||
import CrossSigningReset from "./CrossSigningReset";
|
||||
import UserEmailList from "./UserEmailList";
|
||||
import UserName from "./UserName";
|
||||
|
||||
@@ -22,6 +25,8 @@ const UserProfile: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
<BlockList>
|
||||
<UserName userId={userId} />
|
||||
<UserEmailList userId={userId} />
|
||||
<Separator />
|
||||
<CrossSigningReset userId={userId} />
|
||||
</BlockList>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,6 +53,8 @@ const documents = {
|
||||
types.UserGreetingDocument,
|
||||
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n violations\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.AddEmailDocument,
|
||||
"\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n":
|
||||
types.AllowCrossSigningResetDocument,
|
||||
"\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.UserEmailListQueryDocument,
|
||||
"\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n":
|
||||
@@ -213,6 +215,12 @@ export function graphql(
|
||||
export function graphql(
|
||||
source: "\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n violations\n email {\n id\n ...UserEmail_email\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n violations\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n",
|
||||
): (typeof documents)["\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -1438,6 +1438,18 @@ export type AddEmailMutation = {
|
||||
};
|
||||
};
|
||||
|
||||
export type AllowCrossSigningResetMutationVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type AllowCrossSigningResetMutation = {
|
||||
__typename?: "Mutation";
|
||||
allowUserCrossSigningReset: {
|
||||
__typename?: "AllowUserCrossSigningResetPayload";
|
||||
user?: { __typename?: "User"; id: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type UserEmailListQueryQueryVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
first?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
@@ -3168,6 +3180,75 @@ export const AddEmailDocument = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<AddEmailMutation, AddEmailMutationVariables>;
|
||||
export const AllowCrossSigningResetDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "AllowCrossSigningReset" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "allowUserCrossSigningReset" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "userId" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
AllowCrossSigningResetMutation,
|
||||
AllowCrossSigningResetMutationVariables
|
||||
>;
|
||||
export const UserEmailListQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
|
||||
Reference in New Issue
Block a user