You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
Move the cross signing reset UI in its own page
This commit is contained in:
@@ -183,6 +183,7 @@ pub(crate) async fn get(
|
|||||||
"org.matrix.sessions_list".to_owned(),
|
"org.matrix.sessions_list".to_owned(),
|
||||||
"org.matrix.session_view".to_owned(),
|
"org.matrix.session_view".to_owned(),
|
||||||
"org.matrix.session_end".to_owned(),
|
"org.matrix.session_end".to_owned(),
|
||||||
|
"org.matrix.cross_signing_reset".to_owned(),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -475,6 +475,9 @@ pub enum AccountAction {
|
|||||||
OrgMatrixSessionEnd { device_id: String },
|
OrgMatrixSessionEnd { device_id: String },
|
||||||
#[serde(rename = "session_end")]
|
#[serde(rename = "session_end")]
|
||||||
SessionEnd { device_id: String },
|
SessionEnd { device_id: String },
|
||||||
|
|
||||||
|
#[serde(rename = "org.matrix.cross_signing_reset")]
|
||||||
|
OrgMatrixCrossSigningReset,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `GET /account/`
|
/// `GET /account/`
|
||||||
|
45
frontend/src/components/ButtonLink.tsx
Normal file
45
frontend/src/components/ButtonLink.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2024 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 { LinkComponent, useLinkProps } from "@tanstack/react-router";
|
||||||
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
kind?: "primary" | "secondary" | "tertiary";
|
||||||
|
size?: "sm" | "lg";
|
||||||
|
Icon?: React.ComponentType<React.SVGAttributes<SVGElement>>;
|
||||||
|
destructive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ButtonLink: LinkComponent<Props> = forwardRef<
|
||||||
|
HTMLAnchorElement,
|
||||||
|
Parameters<typeof useLinkProps>[0] & Props
|
||||||
|
>(({ children, kind, size, destructive, Icon, ...props }, ref) => {
|
||||||
|
const linkProps = useLinkProps(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
kind={kind}
|
||||||
|
size={size}
|
||||||
|
destructive={destructive}
|
||||||
|
Icon={Icon}
|
||||||
|
ref={ref}
|
||||||
|
{...linkProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}) as LinkComponent<Props>;
|
@@ -14,24 +14,21 @@
|
|||||||
|
|
||||||
import { LinkComponent, useLinkProps } from "@tanstack/react-router";
|
import { LinkComponent, useLinkProps } from "@tanstack/react-router";
|
||||||
import { Link as CompoundLink } from "@vector-im/compound-web";
|
import { Link as CompoundLink } from "@vector-im/compound-web";
|
||||||
import cx from "classnames";
|
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
import styles from "./Link.module.css";
|
type Props = {
|
||||||
|
kind?: "primary" | "critical";
|
||||||
|
};
|
||||||
|
|
||||||
export const Link: LinkComponent = forwardRef<
|
export const Link: LinkComponent<Props> = forwardRef<
|
||||||
HTMLAnchorElement,
|
HTMLAnchorElement,
|
||||||
Parameters<typeof useLinkProps>[0]
|
Parameters<typeof useLinkProps>[0] & Props
|
||||||
>(({ children, ...props }, ref) => {
|
>(({ children, kind, ...props }, ref) => {
|
||||||
const { className, ...newProps } = useLinkProps(props);
|
const linkProps = useLinkProps(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CompoundLink
|
<CompoundLink kind={kind} ref={ref} {...linkProps}>
|
||||||
kind="primary"
|
{children}
|
||||||
ref={ref}
|
</CompoundLink>
|
||||||
className={cx(className, styles.linkButton)}
|
|
||||||
children={children}
|
|
||||||
{...newProps}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}) as LinkComponent;
|
}) as LinkComponent<Props>;
|
@@ -1,31 +0,0 @@
|
|||||||
/* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.link-button {
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: var(--cpd-color-text-primary);
|
|
||||||
font-weight: var(--cpd-font-weight-medium);
|
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
|
||||||
padding-inline: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-button:hover {
|
|
||||||
background: var(--cpd-color-gray-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-button:active {
|
|
||||||
color: var(--cpd-color-text-on-solid-primary);
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
export { Link } from "./Link";
|
|
@@ -36,7 +36,7 @@ exports[`<UnverifiedEmailAlert /> > renders a warning when there are unverified
|
|||||||
You have 2 unverified email addresses.
|
You have 2 unverified email addresses.
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="_link_1mzip_17 active _linkButton_379d57"
|
class="_link_1mzip_17 active"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-status="active"
|
data-status="active"
|
||||||
href="/#emails"
|
href="/#emails"
|
||||||
|
@@ -1,79 +0,0 @@
|
|||||||
// 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 { useTranslation } from "react-i18next";
|
|
||||||
import { useMutation } from "urql";
|
|
||||||
|
|
||||||
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 CrossSigningReset: React.FC<{ userId: string }> = ({ userId }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [result, allowReset] = useMutation(ALLOW_CROSS_SIGING_RESET_MUTATION);
|
|
||||||
|
|
||||||
const onClick = (): void => {
|
|
||||||
allowReset({ userId });
|
|
||||||
};
|
|
||||||
|
|
||||||
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={result.fetching}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{!!result.fetching && <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;
|
|
@@ -22,7 +22,7 @@ exports[`BrowserSessionsOverview > renders with sessions 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
class="_link_1mzip_17 _linkButton_379d57"
|
class="_link_1mzip_17"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
href="/sessions/browsers"
|
href="/sessions/browsers"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
|
@@ -49,8 +49,6 @@ const documents = {
|
|||||||
types.UserGreeting_UserFragmentDoc,
|
types.UserGreeting_UserFragmentDoc,
|
||||||
"\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":
|
"\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,
|
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":
|
"\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,
|
types.UserEmailListQueryDocument,
|
||||||
"\n fragment UserEmailList_user on User {\n id\n primaryEmail {\n id\n }\n }\n":
|
"\n fragment UserEmailList_user on User {\n id\n primaryEmail {\n id\n }\n }\n":
|
||||||
@@ -79,12 +77,14 @@ const documents = {
|
|||||||
types.CurrentUserGreetingDocument,
|
types.CurrentUserGreetingDocument,
|
||||||
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n":
|
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n":
|
||||||
types.OAuth2ClientQueryDocument,
|
types.OAuth2ClientQueryDocument,
|
||||||
"\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n }\n }\n":
|
"\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n":
|
||||||
types.CurrentViewerQueryDocument,
|
types.CurrentViewerQueryDocument,
|
||||||
"\n query DeviceRedirectQuery($deviceId: String!, $userId: ID!) {\n session(deviceId: $deviceId, userId: $userId) {\n __typename\n ... on Node {\n id\n }\n }\n }\n":
|
"\n query DeviceRedirectQuery($deviceId: String!, $userId: ID!) {\n session(deviceId: $deviceId, userId: $userId) {\n __typename\n ... on Node {\n id\n }\n }\n }\n":
|
||||||
types.DeviceRedirectQueryDocument,
|
types.DeviceRedirectQueryDocument,
|
||||||
"\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n":
|
"\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n":
|
||||||
types.VerifyEmailQueryDocument,
|
types.VerifyEmailQueryDocument,
|
||||||
|
"\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n":
|
||||||
|
types.AllowCrossSigningResetDocument,
|
||||||
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n }\n }\n":
|
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n }\n }\n":
|
||||||
types.CurrentViewerSessionQueryDocument,
|
types.CurrentViewerSessionQueryDocument,
|
||||||
};
|
};
|
||||||
@@ -211,12 +211,6 @@ export function graphql(
|
|||||||
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",
|
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"];
|
): (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.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -305,8 +299,8 @@ export function graphql(
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n }\n }\n",
|
source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n",
|
||||||
): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n }\n }\n"];
|
): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on Node {\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.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -319,6 +313,12 @@ export function graphql(
|
|||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n",
|
source: "\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n",
|
||||||
): (typeof documents)["\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n"];
|
): (typeof documents)["\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\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.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
@@ -1423,18 +1423,6 @@ 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<{
|
export type UserEmailListQueryQueryVariables = Exact<{
|
||||||
userId: Scalars["ID"]["input"];
|
userId: Scalars["ID"]["input"];
|
||||||
first?: InputMaybe<Scalars["Int"]["input"]>;
|
first?: InputMaybe<Scalars["Int"]["input"]>;
|
||||||
@@ -1698,7 +1686,9 @@ export type CurrentViewerQueryQueryVariables = Exact<{ [key: string]: never }>;
|
|||||||
|
|
||||||
export type CurrentViewerQueryQuery = {
|
export type CurrentViewerQueryQuery = {
|
||||||
__typename?: "Query";
|
__typename?: "Query";
|
||||||
viewer: { __typename: "Anonymous" } | { __typename: "User"; id: string };
|
viewer:
|
||||||
|
| { __typename: "Anonymous"; id: string }
|
||||||
|
| { __typename: "User"; id: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeviceRedirectQueryQueryVariables = Exact<{
|
export type DeviceRedirectQueryQueryVariables = Exact<{
|
||||||
@@ -1729,6 +1719,18 @@ export type VerifyEmailQueryQuery = {
|
|||||||
| null;
|
| null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AllowCrossSigningResetMutationVariables = Exact<{
|
||||||
|
userId: Scalars["ID"]["input"];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type AllowCrossSigningResetMutation = {
|
||||||
|
__typename?: "Mutation";
|
||||||
|
allowUserCrossSigningReset: {
|
||||||
|
__typename?: "AllowUserCrossSigningResetPayload";
|
||||||
|
user?: { __typename?: "User"; id: string } | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type CurrentViewerSessionQueryQueryVariables = Exact<{
|
export type CurrentViewerSessionQueryQueryVariables = Exact<{
|
||||||
[key: string]: never;
|
[key: string]: never;
|
||||||
}>;
|
}>;
|
||||||
@@ -3160,75 +3162,6 @@ export const AddEmailDocument = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as unknown as DocumentNode<AddEmailMutation, AddEmailMutationVariables>;
|
} 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 = {
|
export const UserEmailListQueryDocument = {
|
||||||
kind: "Document",
|
kind: "Document",
|
||||||
definitions: [
|
definitions: [
|
||||||
@@ -4576,7 +4509,7 @@ export const CurrentViewerQueryDocument = {
|
|||||||
kind: "InlineFragment",
|
kind: "InlineFragment",
|
||||||
typeCondition: {
|
typeCondition: {
|
||||||
kind: "NamedType",
|
kind: "NamedType",
|
||||||
name: { kind: "Name", value: "User" },
|
name: { kind: "Name", value: "Node" },
|
||||||
},
|
},
|
||||||
selectionSet: {
|
selectionSet: {
|
||||||
kind: "SelectionSet",
|
kind: "SelectionSet",
|
||||||
@@ -4748,6 +4681,75 @@ export const VerifyEmailQueryDocument = {
|
|||||||
VerifyEmailQueryQuery,
|
VerifyEmailQueryQuery,
|
||||||
VerifyEmailQueryQueryVariables
|
VerifyEmailQueryQueryVariables
|
||||||
>;
|
>;
|
||||||
|
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 CurrentViewerSessionQueryDocument = {
|
export const CurrentViewerSessionQueryDocument = {
|
||||||
kind: "Document",
|
kind: "Document",
|
||||||
definitions: [
|
definitions: [
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
|
import { Route as ResetCrossSigningImport } from './routes/reset-cross-signing'
|
||||||
import { Route as AccountImport } from './routes/_account'
|
import { Route as AccountImport } from './routes/_account'
|
||||||
import { Route as AccountIndexImport } from './routes/_account.index'
|
import { Route as AccountIndexImport } from './routes/_account.index'
|
||||||
import { Route as DevicesIdImport } from './routes/devices.$id'
|
import { Route as DevicesIdImport } from './routes/devices.$id'
|
||||||
@@ -22,6 +23,11 @@ import { Route as AccountSessionsIdImport } from './routes/_account.sessions.$id
|
|||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
|
const ResetCrossSigningRoute = ResetCrossSigningImport.update({
|
||||||
|
path: '/reset-cross-signing',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const AccountRoute = AccountImport.update({
|
const AccountRoute = AccountImport.update({
|
||||||
id: '/_account',
|
id: '/_account',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
@@ -70,6 +76,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AccountImport
|
preLoaderRoute: typeof AccountImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/reset-cross-signing': {
|
||||||
|
preLoaderRoute: typeof ResetCrossSigningImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/clients/$id': {
|
'/clients/$id': {
|
||||||
preLoaderRoute: typeof ClientsIdImport
|
preLoaderRoute: typeof ClientsIdImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
@@ -110,6 +120,7 @@ export const routeTree = rootRoute.addChildren([
|
|||||||
AccountSessionsBrowsersRoute,
|
AccountSessionsBrowsersRoute,
|
||||||
AccountSessionsIndexRoute,
|
AccountSessionsIndexRoute,
|
||||||
]),
|
]),
|
||||||
|
ResetCrossSigningRoute,
|
||||||
ClientsIdRoute,
|
ClientsIdRoute,
|
||||||
DevicesIdRoute,
|
DevicesIdRoute,
|
||||||
EmailsIdVerifyRoute,
|
EmailsIdVerifyRoute,
|
||||||
|
@@ -40,6 +40,9 @@ const actionSchema = z
|
|||||||
action: z.enum(["session_end", "org.matrix.session_end"]),
|
action: z.enum(["session_end", "org.matrix.session_end"]),
|
||||||
device_id: z.string().optional(),
|
device_id: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
action: z.literal("org.matrix.cross_signing_reset"),
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
action: z.undefined(),
|
action: z.undefined(),
|
||||||
}),
|
}),
|
||||||
@@ -53,7 +56,7 @@ export const Route = createRootRouteWithContext<{
|
|||||||
}>()({
|
}>()({
|
||||||
validateSearch: (search): Action => actionSchema.parse(search),
|
validateSearch: (search): Action => actionSchema.parse(search),
|
||||||
|
|
||||||
beforeLoad: ({ search }) => {
|
beforeLoad({ search }) {
|
||||||
switch (search.action) {
|
switch (search.action) {
|
||||||
case "profile":
|
case "profile":
|
||||||
case "org.matrix.profile":
|
case "org.matrix.profile":
|
||||||
@@ -80,6 +83,12 @@ export const Route = createRootRouteWithContext<{
|
|||||||
params: { id: search.device_id },
|
params: { id: search.device_id },
|
||||||
});
|
});
|
||||||
throw redirect({ to: "/sessions" });
|
throw redirect({ to: "/sessions" });
|
||||||
|
|
||||||
|
case "org.matrix.cross_signing_reset":
|
||||||
|
throw redirect({
|
||||||
|
to: "/reset-cross-signing",
|
||||||
|
search: { deepLink: true },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -13,14 +13,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { createFileRoute, notFound } from "@tanstack/react-router";
|
import { createFileRoute, notFound } from "@tanstack/react-router";
|
||||||
|
import IconKey from "@vector-im/compound-design-tokens/icons/key.svg?react";
|
||||||
import { H3, Separator } from "@vector-im/compound-web";
|
import { H3, Separator } from "@vector-im/compound-web";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useQuery } from "urql";
|
import { useQuery } from "urql";
|
||||||
|
|
||||||
import BlockList from "../components/BlockList/BlockList";
|
import BlockList from "../components/BlockList/BlockList";
|
||||||
|
import { ButtonLink } from "../components/ButtonLink";
|
||||||
import LoadingSpinner from "../components/LoadingSpinner";
|
import LoadingSpinner from "../components/LoadingSpinner";
|
||||||
import CrossSigningReset from "../components/UserProfile/CrossSigningReset";
|
|
||||||
import UserEmailList from "../components/UserProfile/UserEmailList";
|
import UserEmailList from "../components/UserProfile/UserEmailList";
|
||||||
import UserName from "../components/UserProfile/UserName";
|
import UserName from "../components/UserProfile/UserName";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
@@ -71,7 +72,15 @@ function Index(): React.ReactElement {
|
|||||||
</BlockList>
|
</BlockList>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<CrossSigningReset userId={user.id} />
|
|
||||||
|
<ButtonLink
|
||||||
|
to="/reset-cross-signing"
|
||||||
|
kind="tertiary"
|
||||||
|
destructive
|
||||||
|
Icon={IconKey}
|
||||||
|
>
|
||||||
|
{t("frontend.reset_cross_signing.heading")}
|
||||||
|
</ButtonLink>
|
||||||
</BlockList>
|
</BlockList>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@@ -23,7 +23,7 @@ const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
|
|||||||
query CurrentViewerQuery {
|
query CurrentViewerQuery {
|
||||||
viewer {
|
viewer {
|
||||||
__typename
|
__typename
|
||||||
... on User {
|
... on Node {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
139
frontend/src/routes/reset-cross-signing.tsx
Normal file
139
frontend/src/routes/reset-cross-signing.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2024 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 { createFileRoute, notFound } from "@tanstack/react-router";
|
||||||
|
import IconArrowLeft from "@vector-im/compound-design-tokens/icons/arrow-left.svg?react";
|
||||||
|
import IconKey from "@vector-im/compound-design-tokens/icons/key.svg?react";
|
||||||
|
import { Alert, Button, Text } from "@vector-im/compound-web";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useMutation, useQuery } from "urql";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import BlockList from "../components/BlockList";
|
||||||
|
import { ButtonLink } from "../components/ButtonLink";
|
||||||
|
import LoadingSpinner from "../components/LoadingSpinner";
|
||||||
|
import PageHeading from "../components/PageHeading";
|
||||||
|
import { graphql } from "../gql";
|
||||||
|
|
||||||
|
const searchSchema = z.object({
|
||||||
|
deepLink: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Search = z.infer<typeof searchSchema>;
|
||||||
|
|
||||||
|
const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
|
||||||
|
query CurrentViewerQuery {
|
||||||
|
viewer {
|
||||||
|
__typename
|
||||||
|
... on Node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const ALLOW_CROSS_SIGING_RESET_MUTATION = graphql(/* GraphQL */ `
|
||||||
|
mutation AllowCrossSigningReset($userId: ID!) {
|
||||||
|
allowUserCrossSigningReset(input: { userId: $userId }) {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/reset-cross-signing")({
|
||||||
|
async loader({ context, abortController: { signal } }) {
|
||||||
|
const viewer = await context.client.query(
|
||||||
|
CURRENT_VIEWER_QUERY,
|
||||||
|
{},
|
||||||
|
{ fetchOptions: { signal } },
|
||||||
|
);
|
||||||
|
if (viewer.error) throw viewer.error;
|
||||||
|
if (viewer.data?.viewer.__typename !== "User") throw notFound();
|
||||||
|
},
|
||||||
|
|
||||||
|
validateSearch: (search): Search => searchSchema.parse(search),
|
||||||
|
|
||||||
|
component: ResetCrossSigning,
|
||||||
|
});
|
||||||
|
|
||||||
|
function ResetCrossSigning(): React.ReactNode {
|
||||||
|
const { deepLink } = Route.useSearch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [viewer] = useQuery({ query: CURRENT_VIEWER_QUERY });
|
||||||
|
if (viewer.error) throw viewer.error;
|
||||||
|
if (viewer.data?.viewer.__typename !== "User") throw notFound();
|
||||||
|
const userId = viewer.data.viewer.id;
|
||||||
|
|
||||||
|
const [result, allowReset] = useMutation(ALLOW_CROSS_SIGING_RESET_MUTATION);
|
||||||
|
|
||||||
|
const onClick = (): void => {
|
||||||
|
allowReset({ userId });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BlockList>
|
||||||
|
<PageHeading
|
||||||
|
Icon={IconKey}
|
||||||
|
title={t("frontend.reset_cross_signing.heading")}
|
||||||
|
invalid
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!result.data && !result.error && (
|
||||||
|
<>
|
||||||
|
<Text className="text-justify">
|
||||||
|
{t("frontend.reset_cross_signing.description")}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
kind="primary"
|
||||||
|
destructive
|
||||||
|
disabled={result.fetching}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{!!result.fetching && <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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!deepLink && (
|
||||||
|
<ButtonLink
|
||||||
|
to=".."
|
||||||
|
from={Route.fullPath}
|
||||||
|
kind="tertiary"
|
||||||
|
Icon={IconArrowLeft}
|
||||||
|
>
|
||||||
|
{t("action.back")}
|
||||||
|
</ButtonLink>
|
||||||
|
)}
|
||||||
|
</BlockList>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user