You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
Iterate cross signing reset flows (#3102)
* Handle UIA fallback authDone API for cross signing reset unlock * Extract VisualList into a more reusable component * Redesign cross signing reset unlock flow * Fix block gap * Hide reset x-signing flow on account view under a collapsible heading * i18n * Iterate * Downgrade Radix react-collapsible * Fix class names * Update snapshots
This commit is contained in:
committed by
GitHub
parent
19cef28151
commit
54d5e94bc4
@ -20,6 +20,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"e2ee": "End-to-end encryption",
|
||||
"loading": "Loading…",
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
@ -185,17 +186,28 @@
|
||||
}
|
||||
},
|
||||
"reset_cross_signing": {
|
||||
"button": "Allow crypto identity reset",
|
||||
"description": "If you are not signed in anywhere else, and have forgotten or lost all recovery options you’ll need to reset your crypto identity. This means you will lose your existing message history, other users will see that you have reset your identity and you will need to verify your existing devices again.",
|
||||
"button": "Reset identity",
|
||||
"cancelled": {
|
||||
"description_1": "You can close this window and go back to the app to continue.",
|
||||
"description_2": "If you're signed out everywhere and don't remember your recovery code, you'll still need to reset your identity.",
|
||||
"heading": "Identity reset cancelled."
|
||||
},
|
||||
"description": "If you're not signed in to any other devices and you've lost your recovery key, then you'll need to reset your identity to continue using the app.",
|
||||
"effect_list": {
|
||||
"negative_1": "You will lose your existing message history",
|
||||
"negative_2": "You will need to verify all your existing devices and contacts again",
|
||||
"positive_1": "Your account details, contacts, preferences, and chat list will be kept"
|
||||
},
|
||||
"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 crypto identity"
|
||||
"heading": "Failed to allow crypto identity reset"
|
||||
},
|
||||
"heading": "Reset crypto identity",
|
||||
"heading": "Reset your identity in case you can't confirm another way",
|
||||
"success": {
|
||||
"description": "A client can now temporarily reset your account crypto identity. Follow the instructions in your client to complete the process.",
|
||||
"title": "Crypto identity reset temporarily allowed"
|
||||
}
|
||||
"description": "The identity reset has been approved for the next {{minutes}} minutes. You can close this window and go back to the app to continue.",
|
||||
"heading": "Identity reset successfully. Go back to the app to finish the process."
|
||||
},
|
||||
"warning": "Only reset your identity if you don't have access to another signed-in device and you've lost your recovery key."
|
||||
},
|
||||
"session": {
|
||||
"client_id_label": "Client ID",
|
||||
|
32
frontend/package-lock.json
generated
32
frontend/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@fontsource/inconsolata": "^5.0.18",
|
||||
"@fontsource/inter": "^5.0.20",
|
||||
"@radix-ui/react-collapsible": "1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@tanstack/react-router": "^1.46.7",
|
||||
"@urql/core": "^5.0.5",
|
||||
@ -5211,6 +5212,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz",
|
||||
"integrity": "sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
||||
|
@ -19,6 +19,7 @@
|
||||
"dependencies": {
|
||||
"@fontsource/inconsolata": "^5.0.18",
|
||||
"@fontsource/inter": "^5.0.20",
|
||||
"@radix-ui/react-collapsible": "1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@tanstack/react-router": "^1.46.7",
|
||||
"@urql/core": "^5.0.5",
|
||||
|
@ -17,5 +17,5 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: flex-start;
|
||||
gap: var(--cpd-space-8x);
|
||||
gap: var(--cpd-space-6x);
|
||||
}
|
||||
|
32
frontend/src/components/Collapsible/Collapsible.module.css
Normal file
32
frontend/src/components/Collapsible/Collapsible.module.css
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
.trigger {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.trigger-title {
|
||||
flex-grow: 1;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
[data-state="closed"] .trigger-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: var(--cpd-space-2x);
|
||||
}
|
50
frontend/src/components/Collapsible/Collapsible.tsx
Normal file
50
frontend/src/components/Collapsible/Collapsible.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import IconChevronUp from "@vector-im/compound-design-tokens/assets/web/icons/chevron-up";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./Collapsible.module.css";
|
||||
|
||||
export const Trigger: React.FC<
|
||||
React.ComponentProps<typeof Collapsible.Trigger>
|
||||
> = ({ children, className, ...props }) => {
|
||||
return (
|
||||
<Collapsible.Trigger
|
||||
{...props}
|
||||
className={classNames(styles.trigger, className)}
|
||||
>
|
||||
<div className={styles.triggerTitle}>{children}</div>
|
||||
<IconChevronUp
|
||||
className={styles.triggerIcon}
|
||||
height="24px"
|
||||
width="24px"
|
||||
/>
|
||||
</Collapsible.Trigger>
|
||||
);
|
||||
};
|
||||
|
||||
export const Content: React.FC<
|
||||
React.ComponentProps<typeof Collapsible.Content>
|
||||
> = ({ className, ...props }) => {
|
||||
return (
|
||||
<Collapsible.Content
|
||||
{...props}
|
||||
className={classNames(styles.content, className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Root = Collapsible.Root;
|
15
frontend/src/components/Collapsible/index.ts
Normal file
15
frontend/src/components/Collapsible/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
export * from "./Collapsible";
|
@ -63,7 +63,7 @@
|
||||
text-align: center;
|
||||
|
||||
& .title {
|
||||
font: var(--cpd-font-heading-lg-semibold);
|
||||
font: var(--cpd-font-heading-md-semibold);
|
||||
letter-spacing: var(--cpd-font-letter-spacing-heading-xl);
|
||||
color: var(--cpd-color-text-primary);
|
||||
text-wrap: balance;
|
||||
|
@ -39,25 +39,3 @@
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scope-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-scale);
|
||||
border-radius: var(--cpd-space-5x);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scope {
|
||||
background: var(--cpd-color-bg-subtle-secondary);
|
||||
padding: var(--cpd-space-3x) var(--cpd-space-5x);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--cpd-space-3x);
|
||||
}
|
||||
|
||||
.scope svg {
|
||||
inline-size: var(--cpd-space-6x);
|
||||
block-size: var(--cpd-space-6x);
|
||||
color: var(--cpd-color-icon-tertiary);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import { useTranslation } from "react-i18next";
|
||||
import Block from "../Block/Block";
|
||||
import DateTime from "../DateTime";
|
||||
import LastActive from "../Session/LastActive";
|
||||
import { VisualList, VisualListItem } from "../VisualList/VisualList";
|
||||
|
||||
import styles from "./SessionDetails.module.css";
|
||||
|
||||
@ -68,12 +69,7 @@ const Scope: React.FC<{ scope: string }> = ({ scope }) => {
|
||||
return (
|
||||
<>
|
||||
{mappedScopes.map(([Icon, text], i) => (
|
||||
<li className={styles.scope} key={i}>
|
||||
<Icon />
|
||||
<Text size="md" weight="medium">
|
||||
{text}
|
||||
</Text>
|
||||
</li>
|
||||
<VisualListItem key={i} Icon={Icon} label={text} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
@ -153,11 +149,11 @@ const SessionDetails: React.FC<Props> = ({
|
||||
<Datum
|
||||
label={t("frontend.session.scopes_label")}
|
||||
value={
|
||||
<ul className={styles.scopeList}>
|
||||
<VisualList>
|
||||
{scopes.map((scope) => (
|
||||
<Scope key={scope} scope={scope} />
|
||||
))}
|
||||
</ul>
|
||||
</VisualList>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
@ -109,12 +109,13 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
|
||||
Scopes
|
||||
</h5>
|
||||
<ul
|
||||
class="_scopeList_040867"
|
||||
class="_list_7f22f8"
|
||||
>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -138,9 +139,10 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -158,9 +160,10 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -366,12 +369,13 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
|
||||
Scopes
|
||||
</h5>
|
||||
<ul
|
||||
class="_scopeList_040867"
|
||||
class="_list_7f22f8"
|
||||
>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -395,9 +399,10 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -415,9 +420,10 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -592,12 +598,13 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
|
||||
Scopes
|
||||
</h5>
|
||||
<ul
|
||||
class="_scopeList_040867"
|
||||
class="_list_7f22f8"
|
||||
>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -621,9 +628,10 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -641,9 +649,10 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -123,12 +123,13 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
|
||||
Scopes
|
||||
</h5>
|
||||
<ul
|
||||
class="_scopeList_040867"
|
||||
class="_list_7f22f8"
|
||||
>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -152,9 +153,10 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -172,9 +174,10 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -367,12 +370,13 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
|
||||
Scopes
|
||||
</h5>
|
||||
<ul
|
||||
class="_scopeList_040867"
|
||||
class="_list_7f22f8"
|
||||
>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -396,9 +400,10 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -416,9 +421,10 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
|
||||
</p>
|
||||
</li>
|
||||
<li
|
||||
class="_scope_040867"
|
||||
class="_item_7f22f8"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
36
frontend/src/components/VisualList/VisualList.module.css
Normal file
36
frontend/src/components/VisualList/VisualList.module.css
Normal file
@ -0,0 +1,36 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-scale);
|
||||
border-radius: var(--cpd-space-5x);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: var(--cpd-color-bg-subtle-secondary);
|
||||
padding: var(--cpd-space-3x) var(--cpd-space-5x);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--cpd-space-3x);
|
||||
}
|
||||
|
||||
.item svg {
|
||||
inline-size: var(--cpd-space-6x);
|
||||
block-size: var(--cpd-space-6x);
|
||||
flex-shrink: 0;
|
||||
}
|
50
frontend/src/components/VisualList/VisualList.tsx
Normal file
50
frontend/src/components/VisualList/VisualList.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 { Text } from "@vector-im/compound-web";
|
||||
import {
|
||||
FC,
|
||||
ForwardRefExoticComponent,
|
||||
ReactNode,
|
||||
RefAttributes,
|
||||
SVGProps,
|
||||
} from "react";
|
||||
|
||||
import styles from "./VisualList.module.css";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const VisualListItem: FC<{
|
||||
Icon: ForwardRefExoticComponent<
|
||||
Omit<SVGProps<SVGSVGElement>, "ref" | "children"> &
|
||||
RefAttributes<SVGSVGElement>
|
||||
>;
|
||||
iconColor?: string;
|
||||
label: string;
|
||||
}> = ({ Icon, iconColor, label }) => {
|
||||
return (
|
||||
<li className={styles.item}>
|
||||
<Icon color={iconColor ?? "var(--cpd-color-icon-tertiary)"} />
|
||||
<Text size="md" weight="medium">
|
||||
{label}
|
||||
</Text>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export const VisualList: React.FC<Props> = ({ children }) => {
|
||||
return <ul className={styles.list}>{children}</ul>;
|
||||
};
|
@ -17,15 +17,15 @@ import {
|
||||
notFound,
|
||||
useNavigate,
|
||||
} from "@tanstack/react-router";
|
||||
import IconKey from "@vector-im/compound-design-tokens/assets/web/icons/key";
|
||||
import { Alert, Separator } from "@vector-im/compound-web";
|
||||
import { Alert, Heading, Separator, Text } from "@vector-im/compound-web";
|
||||
import { Suspense } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery } from "urql";
|
||||
|
||||
import AccountManagementPasswordPreview from "../components/AccountManagementPasswordPreview";
|
||||
import BlockList from "../components/BlockList/BlockList";
|
||||
import BlockList from "../components/BlockList";
|
||||
import { ButtonLink } from "../components/ButtonLink";
|
||||
import * as Collapsible from "../components/Collapsible";
|
||||
import LoadingSpinner from "../components/LoadingSpinner";
|
||||
import UserEmail from "../components/UserEmail";
|
||||
import AddEmailForm from "../components/UserProfile/AddEmailForm";
|
||||
@ -89,14 +89,23 @@ function Index(): React.ReactElement {
|
||||
|
||||
<Separator />
|
||||
|
||||
<ButtonLink
|
||||
to="/reset-cross-signing"
|
||||
kind="tertiary"
|
||||
destructive
|
||||
Icon={IconKey}
|
||||
>
|
||||
{t("frontend.reset_cross_signing.heading")}
|
||||
</ButtonLink>
|
||||
<Collapsible.Root>
|
||||
<Collapsible.Trigger>
|
||||
<Heading size="sm" weight="semibold">
|
||||
{t("common.e2ee")}
|
||||
</Heading>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<BlockList>
|
||||
<Text className="text-secondary" size="md">
|
||||
{t("frontend.reset_cross_signing.description")}
|
||||
</Text>
|
||||
<ButtonLink to="/reset-cross-signing" kind="primary" destructive>
|
||||
{t("frontend.reset_cross_signing.button")}
|
||||
</ButtonLink>
|
||||
</BlockList>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
</BlockList>
|
||||
</>
|
||||
);
|
||||
|
@ -13,9 +13,19 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { createLazyFileRoute, notFound } from "@tanstack/react-router";
|
||||
import IconArrowLeft from "@vector-im/compound-design-tokens/assets/web/icons/arrow-left";
|
||||
import IconKey from "@vector-im/compound-design-tokens/assets/web/icons/key";
|
||||
import { Alert, Button, Text } from "@vector-im/compound-web";
|
||||
import IconCheck from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
||||
import IconCheckCircleSolid from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
|
||||
import IconClose from "@vector-im/compound-design-tokens/assets/web/icons/close";
|
||||
import IconError from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
||||
import IconKeyOffSolid from "@vector-im/compound-design-tokens/assets/web/icons/key-off-solid";
|
||||
import { Button, Text } from "@vector-im/compound-web";
|
||||
import {
|
||||
ForwardRefExoticComponent,
|
||||
RefAttributes,
|
||||
SVGProps,
|
||||
useState,
|
||||
MouseEvent,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation, useQuery } from "urql";
|
||||
|
||||
@ -24,10 +34,23 @@ import { ButtonLink } from "../components/ButtonLink";
|
||||
import Layout from "../components/Layout";
|
||||
import LoadingSpinner from "../components/LoadingSpinner";
|
||||
import PageHeading from "../components/PageHeading";
|
||||
import {
|
||||
VisualList,
|
||||
VisualListItem,
|
||||
} from "../components/VisualList/VisualList";
|
||||
import { graphql } from "../gql";
|
||||
|
||||
import { CURRENT_VIEWER_QUERY } from "./reset-cross-signing";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
// Synapse may fling the user here via UIA fallback,
|
||||
// this is part of the API to signal completion to the calling client
|
||||
// https://spec.matrix.org/v1.11/client-server-api/#fallback
|
||||
onAuthDone?(): void;
|
||||
}
|
||||
}
|
||||
|
||||
const ALLOW_CROSS_SIGING_RESET_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation AllowCrossSigningReset($userId: ID!) {
|
||||
allowUserCrossSigningReset(input: { userId: $userId }) {
|
||||
@ -42,6 +65,10 @@ export const Route = createLazyFileRoute("/reset-cross-signing")({
|
||||
component: ResetCrossSigning,
|
||||
});
|
||||
|
||||
// This value comes from Synapse and we have no way to query it from here
|
||||
// https://github.com/element-hq/synapse/blob/34b758644611721911a223814a7b35d8e14067e6/synapse/rest/admin/users.py#L1335
|
||||
const CROSS_SIGNING_REPLACEMENT_PERIOD_MS = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
function ResetCrossSigning(): React.ReactNode {
|
||||
const { deepLink } = Route.useSearch();
|
||||
const { t } = useTranslation();
|
||||
@ -51,58 +78,139 @@ function ResetCrossSigning(): React.ReactNode {
|
||||
const userId = viewer.data.viewer.id;
|
||||
|
||||
const [result, allowReset] = useMutation(ALLOW_CROSS_SIGING_RESET_MUTATION);
|
||||
const success = !!result.data && !result.error;
|
||||
const error = !success && result.error;
|
||||
|
||||
const onClick = (): void => {
|
||||
allowReset({ userId });
|
||||
const onClick = async (): Promise<void> => {
|
||||
await allowReset({ userId });
|
||||
setTimeout(() => {
|
||||
// Synapse may fling the user here via UIA fallback,
|
||||
// this is part of the API to signal completion to the calling client
|
||||
// https://spec.matrix.org/v1.11/client-server-api/#fallback
|
||||
if (window.onAuthDone) {
|
||||
window.onAuthDone();
|
||||
} else if (window.opener && window.opener.postMessage) {
|
||||
window.opener.postMessage("authDone", "*");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const [cancelled, setCancelled] = useState(false);
|
||||
|
||||
let cancelButton;
|
||||
if (!deepLink) {
|
||||
cancelButton = (
|
||||
<ButtonLink to="/" kind="tertiary">
|
||||
{t("action.back")}
|
||||
</ButtonLink>
|
||||
);
|
||||
} else if (!success && !error && !cancelled) {
|
||||
// Only show the back button for a deep link if the user hasn't yet completed the interaction
|
||||
cancelButton = (
|
||||
<Button
|
||||
as="a"
|
||||
kind="tertiary"
|
||||
onClick={(ev: MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
setCancelled(true);
|
||||
}}
|
||||
>
|
||||
{t("action.cancel")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
let Icon: ForwardRefExoticComponent<
|
||||
Omit<SVGProps<SVGSVGElement>, "ref" | "children"> &
|
||||
RefAttributes<SVGSVGElement>
|
||||
>;
|
||||
let title: string;
|
||||
let body: JSX.Element;
|
||||
|
||||
if (cancelled) {
|
||||
Icon = IconKeyOffSolid;
|
||||
title = t("frontend.reset_cross_signing.cancelled.heading");
|
||||
body = (
|
||||
<>
|
||||
<Text className="text-center text-secondary" size="lg">
|
||||
{t("frontend.reset_cross_signing.cancelled.description_1")}
|
||||
</Text>
|
||||
<Text className="text-center text-secondary" size="lg">
|
||||
{t("frontend.reset_cross_signing.cancelled.description_2")}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
} else if (success) {
|
||||
Icon = IconCheckCircleSolid;
|
||||
title = t("frontend.reset_cross_signing.success.heading");
|
||||
body = (
|
||||
<Text className="text-center text-secondary" size="lg">
|
||||
{t("frontend.reset_cross_signing.success.description", {
|
||||
minutes: CROSS_SIGNING_REPLACEMENT_PERIOD_MS / (60 * 1000),
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
} else if (error) {
|
||||
Icon = IconError;
|
||||
title = t("frontend.reset_cross_signing.failure.heading");
|
||||
body = (
|
||||
<Text className="text-center text-secondary" size="lg">
|
||||
{t("frontend.reset_cross_signing.failure.description")}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
Icon = IconError;
|
||||
title = t("frontend.reset_cross_signing.heading");
|
||||
body = (
|
||||
<>
|
||||
<Text className="text-center text-secondary" size="lg">
|
||||
{t("frontend.reset_cross_signing.description")}
|
||||
</Text>
|
||||
<VisualList>
|
||||
<VisualListItem
|
||||
Icon={IconCheck}
|
||||
iconColor="var(--cpd-color-icon-success-primary)"
|
||||
label={t("frontend.reset_cross_signing.effect_list.positive_1")}
|
||||
/>
|
||||
<VisualListItem
|
||||
Icon={IconClose}
|
||||
iconColor="var(--cpd-color-icon-critical-primary)"
|
||||
label={t("frontend.reset_cross_signing.effect_list.negative_1")}
|
||||
/>
|
||||
<VisualListItem
|
||||
Icon={IconClose}
|
||||
iconColor="var(--cpd-color-icon-critical-primary)"
|
||||
label={t("frontend.reset_cross_signing.effect_list.negative_2")}
|
||||
/>
|
||||
</VisualList>
|
||||
<Text className="text-center" size="md" weight="semibold">
|
||||
{t("frontend.reset_cross_signing.warning")}
|
||||
</Text>
|
||||
<Button
|
||||
kind="primary"
|
||||
destructive
|
||||
disabled={result.fetching}
|
||||
onClick={onClick}
|
||||
>
|
||||
{!!result.fetching && <LoadingSpinner inline />}
|
||||
{t("frontend.reset_cross_signing.button")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<BlockList>
|
||||
<PageHeading
|
||||
Icon={IconKey}
|
||||
title={t("frontend.reset_cross_signing.heading")}
|
||||
invalid
|
||||
Icon={Icon}
|
||||
title={title}
|
||||
invalid={!success}
|
||||
success={success}
|
||||
/>
|
||||
|
||||
{!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="/" kind="tertiary" Icon={IconArrowLeft}>
|
||||
{t("action.back")}
|
||||
</ButtonLink>
|
||||
)}
|
||||
{body}
|
||||
{cancelButton}
|
||||
</BlockList>
|
||||
</Layout>
|
||||
);
|
||||
|
Reference in New Issue
Block a user