Simple UI to reset cross-signing keys

This commit is contained in:
Quentin Gliech
2023-11-23 12:22:05 +01:00
parent c11c7a0772
commit eb0b3938ff
5 changed files with 202 additions and 0 deletions

View File

@@ -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"
},

View 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;

View File

@@ -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>
);
};

View File

@@ -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.
*/

View File

@@ -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: [