From f5e74ec59e21c9a2ea96d0117d902df536158543 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 10 Jun 2024 12:31:43 +0100 Subject: [PATCH 01/15] Add fake password preview and 'change account' link to account management page --- ...ccountManagementPasswordPreview.module.css | 36 +++++++++++++++ .../AccountManagementPasswordPreview.tsx | 46 +++++++++++++++++++ .../AccountManagementPasswordPreview/index.ts | 15 ++++++ frontend/src/routes/_account.index.tsx | 7 +++ 4 files changed, 104 insertions(+) create mode 100644 frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css create mode 100644 frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx create mode 100644 frontend/src/components/AccountManagementPasswordPreview/index.ts diff --git a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css new file mode 100644 index 000000000..bb6216288 --- /dev/null +++ b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css @@ -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. + */ + +.password-preview-field { + flex: 1; +} + +.link { + 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:hover { + background: var(--cpd-color-gray-300); +} + +.link:active { + background: var(--cpd-color-text-primary); + color: var(--cpd-color-text-on-solid-primary); +} diff --git a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx new file mode 100644 index 000000000..7bae25113 --- /dev/null +++ b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx @@ -0,0 +1,46 @@ +// 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 { Link } from "@tanstack/react-router"; +import { Form } from "@vector-im/compound-web"; +import { useTranslation } from "react-i18next"; + +import styles from "./AccountManagementPasswordPreview.module.css"; + +export default function AccountManagementPasswordPreview(): React.ReactElement { + const { t } = useTranslation(); + + return ( + + + {t("frontend.account.password.label")} + +
+ +
+ + + + {t("frontend.account.password.change")} + + +
+
+ ); +} diff --git a/frontend/src/components/AccountManagementPasswordPreview/index.ts b/frontend/src/components/AccountManagementPasswordPreview/index.ts new file mode 100644 index 000000000..a791ceab4 --- /dev/null +++ b/frontend/src/components/AccountManagementPasswordPreview/index.ts @@ -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 { default } from "./AccountManagementPasswordPreview"; diff --git a/frontend/src/routes/_account.index.tsx b/frontend/src/routes/_account.index.tsx index a637cf39c..92e282949 100644 --- a/frontend/src/routes/_account.index.tsx +++ b/frontend/src/routes/_account.index.tsx @@ -19,6 +19,7 @@ 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 { ButtonLink } from "../components/ButtonLink"; import LoadingSpinner from "../components/LoadingSpinner"; @@ -109,6 +110,12 @@ function Index(): React.ReactElement { +
+ +
+ + + Date: Wed, 19 Jun 2024 14:50:03 +0100 Subject: [PATCH 02/15] Add password change form to React frontend --- frontend/src/gql/gql.ts | 5 + frontend/src/gql/graphql.ts | 10 + frontend/src/routeTree.gen.ts | 11 + frontend/src/routes/password.change.tsx | 293 ++++++++++++++++++++++++ 4 files changed, 319 insertions(+) create mode 100644 frontend/src/routes/password.change.tsx diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 54d0cf492..3e4ca3558 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -51,6 +51,7 @@ const documents = { "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n": 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": types.DeviceRedirectQueryDocument, "\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n": types.VerifyEmailQueryDocument, + "\n mutation ChangePasswordMutation($userId: ID!, $oldPassword: String!, $newPassword: String!) {\n setPassword(input: { userId: $userId, currentPassword: $oldPassword, newPassword: $newPassword }) {\n status\n }\n }\n": types.ChangePasswordMutationDocument, "\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n": types.AllowCrossSigningResetDocument, }; @@ -220,6 +221,10 @@ export function graphql(source: "\n query DeviceRedirectQuery($deviceId: String * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(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"]; +/** + * 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 ChangePasswordMutation($userId: ID!, $oldPassword: String!, $newPassword: String!) {\n setPassword(input: { userId: $userId, currentPassword: $oldPassword, newPassword: $newPassword }) {\n status\n }\n }\n"): (typeof documents)["\n mutation ChangePasswordMutation($userId: ID!, $oldPassword: String!, $newPassword: String!) {\n setPassword(input: { userId: $userId, currentPassword: $oldPassword, newPassword: $newPassword }) {\n status\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index dd9ae3671..3b40b63ce 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -1541,6 +1541,15 @@ export type VerifyEmailQueryQuery = { __typename?: 'Query', userEmail?: ( & { ' $fragmentRefs'?: { 'UserEmail_VerifyEmailFragment': UserEmail_VerifyEmailFragment } } ) | null }; +export type ChangePasswordMutationMutationVariables = Exact<{ + userId: Scalars['ID']['input']; + oldPassword: Scalars['String']['input']; + newPassword: Scalars['String']['input']; +}>; + + +export type ChangePasswordMutationMutation = { __typename?: 'Mutation', setPassword: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } }; + export type AllowCrossSigningResetMutationVariables = Exact<{ userId: Scalars['ID']['input']; }>; @@ -1586,4 +1595,5 @@ export const OAuth2ClientQueryDocument = {"kind":"Document","definitions":[{"kin export const CurrentViewerQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CurrentViewerQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const DeviceRedirectQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeviceRedirectQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"deviceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"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":"session"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"deviceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"deviceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const VerifyEmailQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"VerifyEmailQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_verifyEmail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_verifyEmail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]} as unknown as DocumentNode; +export const ChangePasswordMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ChangePasswordMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oldPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setPassword"},"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"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"currentPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oldPassword"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"newPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; 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; \ No newline at end of file diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index f54f9b3d3..02a68acff 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -14,6 +14,7 @@ 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 AccountIndexImport } from './routes/_account.index' +import { Route as PasswordChangeImport } from './routes/password.change' import { Route as DevicesSplatImport } from './routes/devices.$' import { Route as ClientsIdImport } from './routes/clients.$id' import { Route as AccountSessionsIndexImport } from './routes/_account.sessions.index' @@ -38,6 +39,11 @@ const AccountIndexRoute = AccountIndexImport.update({ getParentRoute: () => AccountRoute, } as any) +const PasswordChangeRoute = PasswordChangeImport.update({ + path: '/password/change', + getParentRoute: () => rootRoute, +} as any) + const DevicesSplatRoute = DevicesSplatImport.update({ path: '/devices/$', getParentRoute: () => rootRoute, @@ -88,6 +94,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DevicesSplatImport parentRoute: typeof rootRoute } + '/password/change': { + preLoaderRoute: typeof PasswordChangeImport + parentRoute: typeof rootRoute + } '/_account/': { preLoaderRoute: typeof AccountIndexImport parentRoute: typeof AccountImport @@ -123,6 +133,7 @@ export const routeTree = rootRoute.addChildren([ ResetCrossSigningRoute, ClientsIdRoute, DevicesSplatRoute, + PasswordChangeRoute, EmailsIdVerifyRoute, ]) diff --git a/frontend/src/routes/password.change.tsx b/frontend/src/routes/password.change.tsx new file mode 100644 index 000000000..b2d96f8ce --- /dev/null +++ b/frontend/src/routes/password.change.tsx @@ -0,0 +1,293 @@ +// 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 IconCheckCircleSolid from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg?react"; +import IconLockSolid from "@vector-im/compound-design-tokens/icons/lock-solid.svg?react"; +import { Alert, Form, Separator } from "@vector-im/compound-web"; +import { FormEvent, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import { useMutation, useQuery } from "urql"; + +import BlockList from "../components/BlockList"; +import { ButtonLink } from "../components/ButtonLink"; +import Layout from "../components/Layout"; +import LoadingSpinner from "../components/LoadingSpinner"; +import PageHeading from "../components/PageHeading"; +import { graphql } from "../gql"; +import { SetPasswordStatus } from "../gql/graphql"; + +const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ ` + query CurrentViewerQuery { + viewer { + __typename + ... on Node { + id + } + } + } +`); + +const CHANGE_PASSWORD_MUTATION = graphql(/* GraphQL */ ` + mutation ChangePassword( + $userId: ID! + $oldPassword: String! + $newPassword: String! + ) { + setPassword( + input: { + userId: $userId + currentPassword: $oldPassword + newPassword: $newPassword + } + ) { + status + } + } +`); + +export const Route = createFileRoute("/password/change")({ + 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(); + }, + + component: ChangePassword, +}); + +function ChangePassword(): React.ReactNode { + 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 currentPasswordRef = useRef(null); + const newPasswordRef = useRef(null); + const newPasswordAgainRef = useRef(null); + + const [result, changePassword] = useMutation(CHANGE_PASSWORD_MUTATION); + + const onSubmit = (event: FormEvent): void => { + event.preventDefault(); + + const formData = new FormData(event.currentTarget); + + const oldPassword = formData.get("current_password"); + const newPassword = formData.get("new_password"); + const newPasswordAgain = formData.get("new_password_again"); + + if (!oldPassword || !newPassword || !newPasswordAgain) { + throw new Error("not all passwords set"); + } + + // Assert that these are strings — according to the type system they could be Files + if (typeof oldPassword !== "string") { + throw new Error("oldPassword not string"); + } + if (typeof newPassword !== "string") { + throw new Error("newPassword not string"); + } + + if (newPassword !== newPasswordAgain) { + throw new Error("passwords mismatch; this should be checked by the form"); + } + + changePassword({ userId, oldPassword, newPassword }); + }; + + const success = + result.data && result.data.setPassword.status == SetPasswordStatus.Allowed; + const handleableError = + result.data && result.data.setPassword.status != SetPasswordStatus.Allowed; + const unhandleableError = result.error !== undefined; + + const errorMsg: string | undefined = ((): string | undefined => { + switch (result.data?.setPassword.status) { + case SetPasswordStatus.InvalidNewPassword: + return t( + "frontend.password_change.failure.description_INVALID_NEW_PASSWORD", + ); + case SetPasswordStatus.NoCurrentPassword: + return t( + "frontend.password_change.failure.description_NO_CURRENT_PASSWORD", + ); + case SetPasswordStatus.NotAllowed: + return t("frontend.password_change.failure.description_NOT_ALLOWED"); + case SetPasswordStatus.NotFound: + return t("frontend.password_change.failure.description_NOT_FOUND"); + case SetPasswordStatus.PasswordChangesDisabled: + return t( + "frontend.password_change.failure.description_PASSWORD_CHANGES_DISABLED", + ); + case SetPasswordStatus.WrongPassword: + return t("frontend.password_change.failure.description_WRONG_PASSWORD"); + default: + return undefined; + } + })(); + + return ( + + + + + + {/* + In normal operation, the submit event should be `preventDefault()`ed. + method = POST just prevents sending passwords in the query string, + which could be logged, if for some reason the event handler fails. + */} + + {!success && ( + <> + + + {t("frontend.password_change.current_password_label")} + + + + + + {t("frontend.errors.field_required")} + + + {result.data && + result.data.setPassword.status == + SetPasswordStatus.WrongPassword && ( + + {t( + "frontend.password_change.failure.description_WRONG_PASSWORD", + )} + + )} + + + + + + + {t("frontend.password_change.new_password_label")} + + + newPasswordAgainRef.current!.reportValidity()} + /> + + {/* TODO Show a password bar. https://github.com/matrix-org/matrix-authentication-service/issues/2854 */} + + + {t("frontend.errors.field_required")} + + + + + {/* + TODO This field has validation defects, + some caused by Radix-UI upstream bugs. + https://github.com/matrix-org/matrix-authentication-service/issues/2855 + */} + + {t("frontend.password_change.new_password_again_label")} + + + + + + {t("frontend.errors.field_required")} + + + v !== form.get("new_password")} + > + {t("frontend.password_change.passwords_no_match")} + + + + {/* TODO Use SuccessMessage once ready. https://github.com/matrix-org/matrix-authentication-service/issues/2856 */} + + {t("frontend.password_change.passwords_match")} + + + + )} + + {unhandleableError && ( + + {t("frontend.password_change.failure.description")} + + )} + {!success && ( + <> + + {!!result.fetching && } + {t("action.save")} + + + )} + {success && ( + + {t("frontend.password_change.success.description")} + + )} + {handleableError && ( + + {errorMsg} + + )} + + + {(result.data && t("action.back")) || t("action.cancel")} + + + + + ); +} From 65d1a38ec1ddb6cd724f699027d188a5e7a652e4 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Wed, 19 Jun 2024 14:50:23 +0100 Subject: [PATCH 03/15] en locale update for password change form --- frontend/locales/en.json | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index aad058c02..24e0e3547 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -31,6 +31,11 @@ "title": "Edit profile", "username_label": "Username" }, + "password": { + "change": "Change password", + "change_disabled": "Password changes are disabled by the administrator.", + "label": "Password" + }, "title": "Your account" }, "add_email_form": { @@ -79,6 +84,9 @@ "subtitle": "An unexpected error occurred. Please try again.", "title": "Something went wrong" }, + "errors": { + "field_required": "This field is required" + }, "last_active": { "active_date": "Active {{relativeDate}}", "active_now": "Active now", @@ -103,6 +111,29 @@ "pagination_controls": { "total": "Total: {{totalCount}}" }, + "password_change": { + "current_password_label": "Current password", + "failure": { + "description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "description_INVALID_NEW_PASSWORD": "The new password you chose is invalid; it may not meet the configured security policy.", + "description_NO_CURRENT_PASSWORD": "You don't have a current password.", + "description_NOT_ALLOWED": "You aren't allowed to set the password for that user.", + "description_NOT_FOUND": "The user was not found.", + "description_PASSWORD_CHANGES_DISABLED": "Password changes are disabled.", + "description_WRONG_PASSWORD": "The password you supplied as your current password is incorrect. Please try again.", + "title": "Failed to update password" + }, + "new_password_again_label": "Enter new password again", + "new_password_label": "New password", + "passwords_match": "Passwords match!", + "passwords_no_match": "Passwords don't match", + "subtitle": "Choose a new password for your account.", + "success": { + "description": "Your password has been updated successfully.", + "title": "Password updated" + }, + "title": "Change your password" + }, "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.", From fd6aadf8d2f100412c9dfb54331ad81a74556e96 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Wed, 19 Jun 2024 14:50:32 +0100 Subject: [PATCH 04/15] fr locale update (partial) for password change form --- frontend/locales/fr.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index a5b477b7b..9784ae46c 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -33,7 +33,11 @@ "title": "Editer le profil", "username_label": "Nom d’utilisateur" }, - "title": "Votre compte" + "title": "Votre compte", + "password": { + "label": "Mot de passe", + "change": "Changer le mot de passe" + } }, "add_email_form": { "email_denied_alert": { @@ -88,6 +92,9 @@ "subtitle": "Une erreur inattendue s'est produite. Veuillez réessayer", "title": "Un problème est survenu" }, + "errors": { + "field_required": "Ce champ est requis" + }, "error_boundary_title": "Un problème est survenu", "last_active": { "active_date": "Actif {{relativeDate}}", @@ -117,6 +124,13 @@ "pagination_controls": { "total": "Total : {{totalCount}}" }, + "password_change": { + "title": "Changer le mot de passe", + "subtitle": "Cela modifiera le mot de passe de votre compte.", + "current_password_label": "Mot de passe actuel", + "new_password_label": "Nouveau mot de passe", + "new_password_again_label": "Confirmer le mot de passe" + }, "reset_cross_signing": { "button": "Autoriser le remplacement de l'identité cryptographique", "description": "Si vous n'êtes connecté nulle part ailleurs et que vous avez oublié ou perdu toutes vos options de récupération, vous devez réinitialiser votre identité cryptographique. Cela signifie que vous perdrez votre historique de message, que les autres utilisateurs verront que vous avez réinitialisé votre identité et que vous devrez à nouveau vérifier vos appareils existants.", @@ -229,4 +243,4 @@ "view_profile": "Voir les informations de votre profil et vos coordonnées" } } -} \ No newline at end of file +} From 60363eae69d095866b5c76b30df09d172d24c4cd Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Thu, 20 Jun 2024 16:00:20 +0100 Subject: [PATCH 05/15] Don't show password change link if password changes are disabled --- .../AccountManagementPasswordPreview.tsx | 27 ++++++++++++++++--- frontend/src/gql/gql.ts | 9 +++++-- frontend/src/gql/graphql.ts | 7 +++-- frontend/src/routes/_account.index.tsx | 3 ++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx index 7bae25113..8c4356f13 100644 --- a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx +++ b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx @@ -16,10 +16,24 @@ import { Link } from "@tanstack/react-router"; import { Form } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; +import { FragmentType, graphql, useFragment } from "../../gql"; + import styles from "./AccountManagementPasswordPreview.module.css"; -export default function AccountManagementPasswordPreview(): React.ReactElement { +const CONFIG_FRAGMENT = graphql(/* GraphQL */ ` + fragment PasswordChange_siteConfig on SiteConfig { + id + passwordChangeAllowed + } +`); + +export default function AccountManagementPasswordPreview({ + siteConfig, +}: { + siteConfig: FragmentType; +}): React.ReactElement { const { t } = useTranslation(); + const { passwordChangeAllowed } = useFragment(CONFIG_FRAGMENT, siteConfig); return ( @@ -36,9 +50,14 @@ export default function AccountManagementPasswordPreview(): React.ReactElement { - - {t("frontend.account.password.change")} - + {passwordChangeAllowed && ( + + {t("frontend.account.password.change")} + + )} + + {!passwordChangeAllowed && + t("frontend.account.password.change_disabled")} diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 3e4ca3558..a484e0cfd 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -13,6 +13,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { + "\n fragment PasswordChange_siteConfig on SiteConfig {\n id\n passwordChangeAllowed\n }\n": types.PasswordChange_SiteConfigFragmentDoc, "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n": types.BrowserSession_SessionFragmentDoc, "\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n": types.EndBrowserSessionDocument, "\n fragment OAuth2Client_detail on Oauth2Client {\n id\n clientId\n clientName\n clientUri\n logoUri\n tosUri\n policyUri\n redirectUris\n }\n": types.OAuth2Client_DetailFragmentDoc, @@ -41,7 +42,7 @@ const documents = { "\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n": types.UserEmail_VerifyEmailFragmentDoc, "\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.VerifyEmailDocument, "\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.ResendVerificationEmailDocument, - "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n }\n }\n": types.UserProfileQueryDocument, + "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileQueryDocument, "\n query SessionDetailQuery($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": types.SessionDetailQueryDocument, "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument, "\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewQueryDocument, @@ -69,6 +70,10 @@ const documents = { */ export function graphql(source: string): unknown; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment PasswordChange_siteConfig on SiteConfig {\n id\n passwordChangeAllowed\n }\n"): (typeof documents)["\n fragment PasswordChange_siteConfig on SiteConfig {\n id\n passwordChangeAllowed\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -184,7 +189,7 @@ export function graphql(source: "\n mutation ResendVerificationEmail($id: ID!) /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n }\n }\n"): (typeof documents)["\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n }\n }\n"]; +export function graphql(source: "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): (typeof documents)["\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 3b40b63ce..aebeb8a0d 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -1290,6 +1290,8 @@ export type Viewer = Anonymous | User; /** Represents the current viewer's session */ export type ViewerSession = Anonymous | BrowserSession | Oauth2Session; +export type PasswordChange_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, passwordChangeAllowed: boolean } & { ' $fragmentName'?: 'PasswordChange_SiteConfigFragment' }; + export type BrowserSession_SessionFragment = { __typename?: 'BrowserSession', id: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', raw: string, name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: string } | null } & { ' $fragmentName'?: 'BrowserSession_SessionFragment' }; export type EndBrowserSessionMutationVariables = Exact<{ @@ -1441,7 +1443,7 @@ export type UserProfileQueryQuery = { __typename?: 'Query', viewer: { __typename & { ' $fragmentRefs'?: { 'UserEmailList_UserFragment': UserEmailList_UserFragment } } ), siteConfig: ( { __typename?: 'SiteConfig', id: string, emailChangeAllowed: boolean } - & { ' $fragmentRefs'?: { 'UserEmailList_SiteConfigFragment': UserEmailList_SiteConfigFragment;'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment } } + & { ' $fragmentRefs'?: { 'UserEmailList_SiteConfigFragment': UserEmailList_SiteConfigFragment;'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment;'PasswordChange_SiteConfigFragment': PasswordChange_SiteConfigFragment } } ) }; export type SessionDetailQueryQueryVariables = Exact<{ @@ -1557,6 +1559,7 @@ export type AllowCrossSigningResetMutationVariables = Exact<{ export type AllowCrossSigningResetMutation = { __typename?: 'Mutation', allowUserCrossSigningReset: { __typename?: 'AllowUserCrossSigningResetPayload', user?: { __typename?: 'User', id: string } | null } }; +export const PasswordChange_SiteConfigFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordChange_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"passwordChangeAllowed"}}]}}]} as unknown as DocumentNode; export const BrowserSession_SessionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"raw"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const OAuth2Client_DetailFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Client_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Client"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"clientUri"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}},{"kind":"Field","name":{"kind":"Name","value":"tosUri"}},{"kind":"Field","name":{"kind":"Name","value":"policyUri"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUris"}}]}}]} as unknown as DocumentNode; export const CompatSession_SessionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CompatSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CompatSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deviceId"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"ssoLogin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUri"}}]}}]}}]} as unknown as DocumentNode; @@ -1585,7 +1588,7 @@ export const AddEmailDocument = {"kind":"Document","definitions":[{"kind":"Opera export const UserEmailListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserEmailListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; export const VerifyEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"VerifyEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"code"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"verifyEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"userEmailId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"code"},"value":{"kind":"Variable","name":{"kind":"Name","value":"code"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; export const ResendVerificationEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResendVerificationEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendVerificationEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"userEmailId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; -export const UserProfileQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserProfileQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_user"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}}]}}]} as unknown as DocumentNode; +export const UserProfileQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserProfileQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_user"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PasswordChange_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordChange_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"passwordChangeAllowed"}}]}}]} as unknown as DocumentNode; export const SessionDetailQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SessionDetailQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CompatSession_detail"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"OAuth2Session_detail"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_detail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CompatSession_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CompatSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deviceId"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}}]}},{"kind":"Field","name":{"kind":"Name","value":"ssoLogin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Session_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Session"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"clientUri"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"os"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]} as unknown as DocumentNode; export const BrowserSessionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BrowserSessionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_session"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"raw"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const SessionsOverviewQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SessionsOverviewQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSessionsOverview_user"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSessionsOverview_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"0"}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; diff --git a/frontend/src/routes/_account.index.tsx b/frontend/src/routes/_account.index.tsx index 92e282949..01beaa704 100644 --- a/frontend/src/routes/_account.index.tsx +++ b/frontend/src/routes/_account.index.tsx @@ -49,6 +49,7 @@ const QUERY = graphql(/* GraphQL */ ` emailChangeAllowed ...UserEmailList_siteConfig ...UserEmail_siteConfig + ...PasswordChange_siteConfig } } `); @@ -111,7 +112,7 @@ function Index(): React.ReactElement {
- +
From 32356f2097ceb5e8b2839e15d835b69acb849aba Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Thu, 20 Jun 2024 16:26:18 +0100 Subject: [PATCH 06/15] Don't show password box if passwords are disabled --- crates/handlers/src/graphql/model/site_config.rs | 5 +++++ frontend/schema.graphql | 4 ++++ frontend/src/gql/gql.ts | 8 ++++---- frontend/src/gql/graphql.ts | 12 +++++++----- frontend/src/gql/schema.ts | 11 +++++++++++ frontend/src/routes/_account.index.tsx | 9 ++++++--- 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/crates/handlers/src/graphql/model/site_config.rs b/crates/handlers/src/graphql/model/site_config.rs index 655430e6c..d82596144 100644 --- a/crates/handlers/src/graphql/model/site_config.rs +++ b/crates/handlers/src/graphql/model/site_config.rs @@ -21,6 +21,7 @@ pub const SITE_CONFIG_ID: &str = "site_config"; #[derive(SimpleObject)] #[graphql(complex)] +#[allow(clippy::struct_excessive_bools)] pub struct SiteConfig { /// The server name of the homeserver. server_name: String, @@ -40,6 +41,9 @@ pub struct SiteConfig { /// Whether users can change their display name. display_name_change_allowed: bool, + /// Whether passwords are enabled for login. + password_login_enabled: bool, + /// Whether passwords are enabled and users can change their own passwords. password_change_allowed: bool, } @@ -63,6 +67,7 @@ impl SiteConfig { imprint: data_model.imprint.clone(), email_change_allowed: data_model.email_change_allowed, display_name_change_allowed: data_model.displayname_change_allowed, + password_login_enabled: data_model.password_login_enabled, password_change_allowed: data_model.password_change_allowed, } } diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 2072cfd45..3688b671b 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -1349,6 +1349,10 @@ type SiteConfig implements Node { """ displayNameChangeAllowed: Boolean! """ + Whether passwords are enabled for login. + """ + passwordLoginEnabled: Boolean! + """ Whether passwords are enabled and users can change their own passwords. """ passwordChangeAllowed: Boolean! diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index a484e0cfd..429012968 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -42,7 +42,7 @@ const documents = { "\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n": types.UserEmail_VerifyEmailFragmentDoc, "\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.VerifyEmailDocument, "\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.ResendVerificationEmailDocument, - "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileQueryDocument, + "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileQueryDocument, "\n query SessionDetailQuery($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": types.SessionDetailQueryDocument, "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument, "\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewQueryDocument, @@ -52,7 +52,7 @@ const documents = { "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n": 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": types.DeviceRedirectQueryDocument, "\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n": types.VerifyEmailQueryDocument, - "\n mutation ChangePasswordMutation($userId: ID!, $oldPassword: String!, $newPassword: String!) {\n setPassword(input: { userId: $userId, currentPassword: $oldPassword, newPassword: $newPassword }) {\n status\n }\n }\n": types.ChangePasswordMutationDocument, + "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n": types.ChangePasswordDocument, "\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n": types.AllowCrossSigningResetDocument, }; @@ -189,7 +189,7 @@ export function graphql(source: "\n mutation ResendVerificationEmail($id: ID!) /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): (typeof documents)["\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"]; +export function graphql(source: "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): (typeof documents)["\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n ...UserEmailList_user\n }\n }\n\n siteConfig {\n id\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -229,7 +229,7 @@ export function graphql(source: "\n query VerifyEmailQuery($id: ID!) {\n use /** * 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 ChangePasswordMutation($userId: ID!, $oldPassword: String!, $newPassword: String!) {\n setPassword(input: { userId: $userId, currentPassword: $oldPassword, newPassword: $newPassword }) {\n status\n }\n }\n"): (typeof documents)["\n mutation ChangePasswordMutation($userId: ID!, $oldPassword: String!, $newPassword: String!) {\n setPassword(input: { userId: $userId, currentPassword: $oldPassword, newPassword: $newPassword }) {\n status\n }\n }\n"]; +export function graphql(source: "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n"): (typeof documents)["\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index aebeb8a0d..232c6a10e 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -1007,6 +1007,8 @@ export type SiteConfig = Node & { imprint?: Maybe; /** Whether passwords are enabled and users can change their own passwords. */ passwordChangeAllowed: Scalars['Boolean']['output']; + /** Whether passwords are enabled for login. */ + passwordLoginEnabled: Scalars['Boolean']['output']; /** The URL to the privacy policy. */ policyUri?: Maybe; /** The server name of the homeserver. */ @@ -1442,7 +1444,7 @@ export type UserProfileQueryQuery = { __typename?: 'Query', viewer: { __typename ) | null } & { ' $fragmentRefs'?: { 'UserEmailList_UserFragment': UserEmailList_UserFragment } } ), siteConfig: ( - { __typename?: 'SiteConfig', id: string, emailChangeAllowed: boolean } + { __typename?: 'SiteConfig', id: string, emailChangeAllowed: boolean, passwordLoginEnabled: boolean } & { ' $fragmentRefs'?: { 'UserEmailList_SiteConfigFragment': UserEmailList_SiteConfigFragment;'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment;'PasswordChange_SiteConfigFragment': PasswordChange_SiteConfigFragment } } ) }; @@ -1543,14 +1545,14 @@ export type VerifyEmailQueryQuery = { __typename?: 'Query', userEmail?: ( & { ' $fragmentRefs'?: { 'UserEmail_VerifyEmailFragment': UserEmail_VerifyEmailFragment } } ) | null }; -export type ChangePasswordMutationMutationVariables = Exact<{ +export type ChangePasswordMutationVariables = Exact<{ userId: Scalars['ID']['input']; oldPassword: Scalars['String']['input']; newPassword: Scalars['String']['input']; }>; -export type ChangePasswordMutationMutation = { __typename?: 'Mutation', setPassword: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } }; +export type ChangePasswordMutation = { __typename?: 'Mutation', setPassword: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } }; export type AllowCrossSigningResetMutationVariables = Exact<{ userId: Scalars['ID']['input']; @@ -1588,7 +1590,7 @@ export const AddEmailDocument = {"kind":"Document","definitions":[{"kind":"Opera export const UserEmailListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserEmailListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; export const VerifyEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"VerifyEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"code"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"verifyEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"userEmailId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"code"},"value":{"kind":"Variable","name":{"kind":"Name","value":"code"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; export const ResendVerificationEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResendVerificationEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendVerificationEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"userEmailId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; -export const UserProfileQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserProfileQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_user"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PasswordChange_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordChange_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"passwordChangeAllowed"}}]}}]} as unknown as DocumentNode; +export const UserProfileQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserProfileQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_user"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}},{"kind":"Field","name":{"kind":"Name","value":"passwordLoginEnabled"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PasswordChange_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordChange_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"passwordChangeAllowed"}}]}}]} as unknown as DocumentNode; export const SessionDetailQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SessionDetailQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CompatSession_detail"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"OAuth2Session_detail"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_detail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CompatSession_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CompatSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deviceId"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}}]}},{"kind":"Field","name":{"kind":"Name","value":"ssoLogin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Session_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Session"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"clientUri"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"os"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]} as unknown as DocumentNode; export const BrowserSessionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BrowserSessionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_session"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"raw"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const SessionsOverviewQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SessionsOverviewQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSessionsOverview_user"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSessionsOverview_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"0"}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; @@ -1598,5 +1600,5 @@ export const OAuth2ClientQueryDocument = {"kind":"Document","definitions":[{"kin export const CurrentViewerQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CurrentViewerQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const DeviceRedirectQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeviceRedirectQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"deviceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"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":"session"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"deviceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"deviceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const VerifyEmailQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"VerifyEmailQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_verifyEmail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_verifyEmail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]} as unknown as DocumentNode; -export const ChangePasswordMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ChangePasswordMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oldPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setPassword"},"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"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"currentPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oldPassword"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"newPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; +export const ChangePasswordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ChangePassword"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"oldPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setPassword"},"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"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"currentPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"oldPassword"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"newPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; 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; \ No newline at end of file diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index 95ce981b7..5d65fdb58 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -2510,6 +2510,17 @@ export default { }, "args": [] }, + { + "name": "passwordLoginEnabled", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Any" + } + }, + "args": [] + }, { "name": "policyUri", "type": { diff --git a/frontend/src/routes/_account.index.tsx b/frontend/src/routes/_account.index.tsx index 01beaa704..bb6a12fb3 100644 --- a/frontend/src/routes/_account.index.tsx +++ b/frontend/src/routes/_account.index.tsx @@ -47,6 +47,7 @@ const QUERY = graphql(/* GraphQL */ ` siteConfig { id emailChangeAllowed + passwordLoginEnabled ...UserEmailList_siteConfig ...UserEmail_siteConfig ...PasswordChange_siteConfig @@ -111,9 +112,11 @@ function Index(): React.ReactElement { -
- -
+ {siteConfig.passwordLoginEnabled && ( +
+ +
+ )} From 241c803eefec4f76f83358f3318269250c174c41 Mon Sep 17 00:00:00 2001 From: reivilibre Date: Mon, 24 Jun 2024 12:44:26 +0100 Subject: [PATCH 07/15] Apply suggestions from code review Co-authored-by: Quentin Gliech --- frontend/src/routes/password.change.tsx | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/frontend/src/routes/password.change.tsx b/frontend/src/routes/password.change.tsx index b2d96f8ce..9cc2a1356 100644 --- a/frontend/src/routes/password.change.tsx +++ b/frontend/src/routes/password.change.tsx @@ -89,21 +89,9 @@ function ChangePassword(): React.ReactNode { const formData = new FormData(event.currentTarget); - const oldPassword = formData.get("current_password"); - const newPassword = formData.get("new_password"); - const newPasswordAgain = formData.get("new_password_again"); - - if (!oldPassword || !newPassword || !newPasswordAgain) { - throw new Error("not all passwords set"); - } - - // Assert that these are strings — according to the type system they could be Files - if (typeof oldPassword !== "string") { - throw new Error("oldPassword not string"); - } - if (typeof newPassword !== "string") { - throw new Error("newPassword not string"); - } + const oldPassword = formData.get("current_password") as string; + const newPassword = formData.get("new_password") as string; + const newPasswordAgain = formData.get("new_password_again") as string; if (newPassword !== newPasswordAgain) { throw new Error("passwords mismatch; this should be checked by the form"); @@ -164,8 +152,7 @@ function ChangePassword(): React.ReactNode { @@ -205,7 +192,7 @@ function ChangePassword(): React.ReactNode { required autoComplete="new-password" ref={newPasswordRef} - onBlur={() => newPasswordAgainRef.current!.reportValidity()} + onBlur={() => newPasswordAgainRef.current!.value && newPasswordAgainRef.current!.reportValidity()} /> {/* TODO Show a password bar. https://github.com/matrix-org/matrix-authentication-service/issues/2854 */} From 415d2dcbc2a2efe541429c4935a7585a7810333b Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 24 Jun 2024 14:00:39 +0100 Subject: [PATCH 08/15] Remove unnecessary wrapper for AccountManagementPasswordPreview --- frontend/src/routes/_account.index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/routes/_account.index.tsx b/frontend/src/routes/_account.index.tsx index bb6a12fb3..71a93d458 100644 --- a/frontend/src/routes/_account.index.tsx +++ b/frontend/src/routes/_account.index.tsx @@ -113,9 +113,7 @@ function Index(): React.ReactElement { {siteConfig.passwordLoginEnabled && ( -
- -
+ )} From 53b227695a2af7725edcf17541be7254f05182db Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 24 Jun 2024 16:16:58 +0100 Subject: [PATCH 09/15] Tweak error messages --- frontend/locales/en.json | 14 ++++----- frontend/src/routes/password.change.tsx | 40 +++++++++++++++++-------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 24e0e3547..1eb026cd5 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -114,13 +114,13 @@ "password_change": { "current_password_label": "Current password", "failure": { - "description": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", - "description_INVALID_NEW_PASSWORD": "The new password you chose is invalid; it may not meet the configured security policy.", - "description_NO_CURRENT_PASSWORD": "You don't have a current password.", - "description_NOT_ALLOWED": "You aren't allowed to set the password for that user.", - "description_NOT_FOUND": "The user was not found.", - "description_PASSWORD_CHANGES_DISABLED": "Password changes are disabled.", - "description_WRONG_PASSWORD": "The password you supplied as your current password is incorrect. Please try again.", + "description": { + "invalid_new_password": "The new password you chose is invalid; it may not meet the configured security policy.", + "no_current_password": "You don't have a current password.", + "password_changes_disabled": "Password changes are disabled.", + "unspecified": "This might be a temporary problem, so please try again later. If the problem persists, please contact your server administrator.", + "wrong_password": "The password you supplied as your current password is incorrect. Please try again." + }, "title": "Failed to update password" }, "new_password_again_label": "Enter new password again", diff --git a/frontend/src/routes/password.change.tsx b/frontend/src/routes/password.change.tsx index 9cc2a1356..4fb6d243f 100644 --- a/frontend/src/routes/password.change.tsx +++ b/frontend/src/routes/password.change.tsx @@ -110,24 +110,25 @@ function ChangePassword(): React.ReactNode { switch (result.data?.setPassword.status) { case SetPasswordStatus.InvalidNewPassword: return t( - "frontend.password_change.failure.description_INVALID_NEW_PASSWORD", + "frontend.password_change.failure.description.invalid_new_password", ); case SetPasswordStatus.NoCurrentPassword: return t( - "frontend.password_change.failure.description_NO_CURRENT_PASSWORD", + "frontend.password_change.failure.description.no_current_password", ); - case SetPasswordStatus.NotAllowed: - return t("frontend.password_change.failure.description_NOT_ALLOWED"); - case SetPasswordStatus.NotFound: - return t("frontend.password_change.failure.description_NOT_FOUND"); case SetPasswordStatus.PasswordChangesDisabled: return t( - "frontend.password_change.failure.description_PASSWORD_CHANGES_DISABLED", + "frontend.password_change.failure.description.password_changes_disabled", ); case SetPasswordStatus.WrongPassword: - return t("frontend.password_change.failure.description_WRONG_PASSWORD"); - default: + return t("frontend.password_change.failure.description.wrong_password"); + case SetPasswordStatus.Allowed: + case undefined: return undefined; + default: + throw new Error( + `unexpected error when changing password: ${result.data!.setPassword.status}`, + ); } })(); @@ -153,7 +154,7 @@ function ChangePassword(): React.ReactNode { name="current_password" serverInvalid={ result.data?.setPassword.status === - SetPasswordStatus.WrongPassword + SetPasswordStatus.WrongPassword } > @@ -175,7 +176,7 @@ function ChangePassword(): React.ReactNode { SetPasswordStatus.WrongPassword && ( {t( - "frontend.password_change.failure.description_WRONG_PASSWORD", + "frontend.password_change.failure.description.wrong_password", )} )} @@ -192,7 +193,10 @@ function ChangePassword(): React.ReactNode { required autoComplete="new-password" ref={newPasswordRef} - onBlur={() => newPasswordAgainRef.current!.value && newPasswordAgainRef.current!.reportValidity()} + onBlur={() => + newPasswordAgainRef.current!.value && + newPasswordAgainRef.current!.reportValidity() + } /> {/* TODO Show a password bar. https://github.com/matrix-org/matrix-authentication-service/issues/2854 */} @@ -200,6 +204,16 @@ function ChangePassword(): React.ReactNode { {t("frontend.errors.field_required")} + + {result.data && + result.data.setPassword.status == + SetPasswordStatus.InvalidNewPassword && ( + + {t( + "frontend.password_change.failure.description.invalid_new_password", + )} + + )}
@@ -242,7 +256,7 @@ function ChangePassword(): React.ReactNode { type="critical" title={t("frontend.password_change.failure.title")} > - {t("frontend.password_change.failure.description")} + {t("frontend.password_change.failure.description.unspecified")} )} {!success && ( From a626ae24caaf048740dc36b9df537dc4b0beca8d Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 24 Jun 2024 16:19:22 +0100 Subject: [PATCH 10/15] Remove needless style from AccountManagementPasswordPreview --- .../AccountManagementPasswordPreview.module.css | 4 ---- .../AccountManagementPasswordPreview.tsx | 13 +++++-------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css index bb6216288..08667e460 100644 --- a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css +++ b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.module.css @@ -13,10 +13,6 @@ * limitations under the License. */ -.password-preview-field { - flex: 1; -} - .link { display: inline-block; text-decoration: underline; diff --git a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx index 8c4356f13..f6b185441 100644 --- a/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx +++ b/frontend/src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview.tsx @@ -40,14 +40,11 @@ export default function AccountManagementPasswordPreview({ {t("frontend.account.password.label")} -
- -
+ {passwordChangeAllowed && ( From 05ab6bd3d4a376501bf8f53bbc9561470df06a2c Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 24 Jun 2024 17:38:40 +0100 Subject: [PATCH 11/15] Split out password change success to its own route --- frontend/src/routeTree.gen.ts | 11 + frontend/src/routes/password.change.tsx | 229 +++++++++--------- .../src/routes/password.change_success.tsx | 81 +++++++ 3 files changed, 201 insertions(+), 120 deletions(-) create mode 100644 frontend/src/routes/password.change_success.tsx diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 02a68acff..308c40430 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -14,6 +14,7 @@ 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 AccountIndexImport } from './routes/_account.index' +import { Route as PasswordChangesuccessImport } from './routes/password.change_success' import { Route as PasswordChangeImport } from './routes/password.change' import { Route as DevicesSplatImport } from './routes/devices.$' import { Route as ClientsIdImport } from './routes/clients.$id' @@ -39,6 +40,11 @@ const AccountIndexRoute = AccountIndexImport.update({ getParentRoute: () => AccountRoute, } as any) +const PasswordChangesuccessRoute = PasswordChangesuccessImport.update({ + path: '/password/change_success', + getParentRoute: () => rootRoute, +} as any) + const PasswordChangeRoute = PasswordChangeImport.update({ path: '/password/change', getParentRoute: () => rootRoute, @@ -98,6 +104,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PasswordChangeImport parentRoute: typeof rootRoute } + '/password/change_success': { + preLoaderRoute: typeof PasswordChangesuccessImport + parentRoute: typeof rootRoute + } '/_account/': { preLoaderRoute: typeof AccountIndexImport parentRoute: typeof AccountImport @@ -134,6 +144,7 @@ export const routeTree = rootRoute.addChildren([ ClientsIdRoute, DevicesSplatRoute, PasswordChangeRoute, + PasswordChangesuccessRoute, EmailsIdVerifyRoute, ]) diff --git a/frontend/src/routes/password.change.tsx b/frontend/src/routes/password.change.tsx index 4fb6d243f..7a8693129 100644 --- a/frontend/src/routes/password.change.tsx +++ b/frontend/src/routes/password.change.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { createFileRoute, notFound } from "@tanstack/react-router"; +import { createFileRoute, notFound, useRouter } from "@tanstack/react-router"; import IconCheckCircleSolid from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg?react"; import IconLockSolid from "@vector-im/compound-design-tokens/icons/lock-solid.svg?react"; import { Alert, Form, Separator } from "@vector-im/compound-web"; @@ -74,6 +74,7 @@ export const Route = createFileRoute("/password/change")({ function ChangePassword(): React.ReactNode { const { t } = useTranslation(); const [viewer] = useQuery({ query: CURRENT_VIEWER_QUERY }); + const router = useRouter(); if (viewer.error) throw viewer.error; if (viewer.data?.viewer.__typename !== "User") throw notFound(); const userId = viewer.data.viewer.id; @@ -84,7 +85,7 @@ function ChangePassword(): React.ReactNode { const [result, changePassword] = useMutation(CHANGE_PASSWORD_MUTATION); - const onSubmit = (event: FormEvent): void => { + const onSubmit = async (event: FormEvent): Promise => { event.preventDefault(); const formData = new FormData(event.currentTarget); @@ -97,13 +98,15 @@ function ChangePassword(): React.ReactNode { throw new Error("passwords mismatch; this should be checked by the form"); } - changePassword({ userId, oldPassword, newPassword }); + const response = await changePassword({ userId, oldPassword, newPassword }); + + if (response.data?.setPassword.status === SetPasswordStatus.Allowed) { + router.navigate({ to: "/password/change_success" }); + } }; - const success = - result.data && result.data.setPassword.status == SetPasswordStatus.Allowed; const handleableError = - result.data && result.data.setPassword.status != SetPasswordStatus.Allowed; + result.data && result.data.setPassword.status !== SetPasswordStatus.Allowed; const unhandleableError = result.error !== undefined; const errorMsg: string | undefined = ((): string | undefined => { @@ -148,108 +151,104 @@ function ChangePassword(): React.ReactNode { which could be logged, if for some reason the event handler fails. */} - {!success && ( - <> - - - {t("frontend.password_change.current_password_label")} - - - - - - {t("frontend.errors.field_required")} - - - {result.data && - result.data.setPassword.status == - SetPasswordStatus.WrongPassword && ( - - {t( - "frontend.password_change.failure.description.wrong_password", - )} - + + + {t("frontend.password_change.current_password_label")} + + + + + + {t("frontend.errors.field_required")} + + + {result.data && + result.data.setPassword.status === + SetPasswordStatus.WrongPassword && ( + + {t( + "frontend.password_change.failure.description.wrong_password", )} - - - - - - - {t("frontend.password_change.new_password_label")} - - - - newPasswordAgainRef.current!.value && - newPasswordAgainRef.current!.reportValidity() - } - /> - - {/* TODO Show a password bar. https://github.com/matrix-org/matrix-authentication-service/issues/2854 */} - - - {t("frontend.errors.field_required")} - - {result.data && - result.data.setPassword.status == - SetPasswordStatus.InvalidNewPassword && ( - - {t( - "frontend.password_change.failure.description.invalid_new_password", - )} - + )} + + + + + + + {t("frontend.password_change.new_password_label")} + + + + newPasswordAgainRef.current!.value && + newPasswordAgainRef.current!.reportValidity() + } + /> + + {/* TODO Show a password bar. https://github.com/matrix-org/matrix-authentication-service/issues/2854 */} + + + {t("frontend.errors.field_required")} + + + {result.data && + result.data.setPassword.status === + SetPasswordStatus.InvalidNewPassword && ( + + {t( + "frontend.password_change.failure.description.invalid_new_password", )} - - - - {/* - TODO This field has validation defects, - some caused by Radix-UI upstream bugs. - https://github.com/matrix-org/matrix-authentication-service/issues/2855 - */} - - {t("frontend.password_change.new_password_again_label")} - - - - - - {t("frontend.errors.field_required")} - - - v !== form.get("new_password")} - > - {t("frontend.password_change.passwords_no_match")} + )} + + + + {/* + TODO This field has validation defects, + some caused by Radix-UI upstream bugs. + https://github.com/matrix-org/matrix-authentication-service/issues/2855 + */} + + {t("frontend.password_change.new_password_again_label")} + + + + + + {t("frontend.errors.field_required")} + + + v !== form.get("new_password")} + > + {t("frontend.password_change.passwords_no_match")} + - - {/* TODO Use SuccessMessage once ready. https://github.com/matrix-org/matrix-authentication-service/issues/2856 */} - - {t("frontend.password_change.passwords_match")} - - - - )} + + {/* TODO Use SuccessMessage once ready. https://github.com/matrix-org/matrix-authentication-service/issues/2856 */} + + {t("frontend.password_change.passwords_match")} + + {unhandleableError && ( )} - {!success && ( - <> - - {!!result.fetching && } - {t("action.save")} - - - )} - {success && ( - - {t("frontend.password_change.success.description")} - - )} + + + {!!result.fetching && } + {t("action.save")} + + {handleableError && ( - {(result.data && t("action.back")) || t("action.cancel")} + {t("action.cancel")}
diff --git a/frontend/src/routes/password.change_success.tsx b/frontend/src/routes/password.change_success.tsx new file mode 100644 index 000000000..70ae9b77c --- /dev/null +++ b/frontend/src/routes/password.change_success.tsx @@ -0,0 +1,81 @@ +// 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 IconLockSolid from "@vector-im/compound-design-tokens/icons/lock-solid.svg?react"; +import { Alert } from "@vector-im/compound-web"; +import { useTranslation } from "react-i18next"; +import { useQuery } from "urql"; + +import BlockList from "../components/BlockList"; +import { ButtonLink } from "../components/ButtonLink"; +import Layout from "../components/Layout"; +import PageHeading from "../components/PageHeading"; +import { graphql } from "../gql"; + +const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ ` + query CurrentViewerQuery { + viewer { + __typename + ... on Node { + id + } + } + } +`); + +export const Route = createFileRoute("/password/change_success")({ + 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(); + }, + + component: ChangePasswordSuccess, +}); + +function ChangePasswordSuccess(): React.ReactNode { + const { t } = useTranslation(); + const [viewer] = useQuery({ query: CURRENT_VIEWER_QUERY }); + if (viewer.error) throw viewer.error; + if (viewer.data?.viewer.__typename !== "User") throw notFound(); + + return ( + + + + + + {t("frontend.password_change.success.description")} + + + + {t("action.back")} + + + + ); +} From befdeea5ebce0283ea9aa91bfca660265ba471a6 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Jun 2024 11:25:28 +0100 Subject: [PATCH 12/15] _ -> / --- frontend/src/routeTree.gen.ts | 44 +++++++++---------- ...d.change.tsx => password.change.index.tsx} | 4 +- ...uccess.tsx => password.change.success.tsx} | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) rename frontend/src/routes/{password.change.tsx => password.change.index.tsx} (98%) rename frontend/src/routes/{password.change_success.tsx => password.change.success.tsx} (97%) diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 308c40430..bcbb84f6f 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -14,11 +14,11 @@ 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 AccountIndexImport } from './routes/_account.index' -import { Route as PasswordChangesuccessImport } from './routes/password.change_success' -import { Route as PasswordChangeImport } from './routes/password.change' import { Route as DevicesSplatImport } from './routes/devices.$' import { Route as ClientsIdImport } from './routes/clients.$id' +import { Route as PasswordChangeIndexImport } from './routes/password.change.index' import { Route as AccountSessionsIndexImport } from './routes/_account.sessions.index' +import { Route as PasswordChangeSuccessImport } from './routes/password.change.success' import { Route as EmailsIdVerifyImport } from './routes/emails.$id.verify' import { Route as AccountSessionsBrowsersImport } from './routes/_account.sessions.browsers' import { Route as AccountSessionsIdImport } from './routes/_account.sessions.$id' @@ -40,16 +40,6 @@ const AccountIndexRoute = AccountIndexImport.update({ getParentRoute: () => AccountRoute, } as any) -const PasswordChangesuccessRoute = PasswordChangesuccessImport.update({ - path: '/password/change_success', - getParentRoute: () => rootRoute, -} as any) - -const PasswordChangeRoute = PasswordChangeImport.update({ - path: '/password/change', - getParentRoute: () => rootRoute, -} as any) - const DevicesSplatRoute = DevicesSplatImport.update({ path: '/devices/$', getParentRoute: () => rootRoute, @@ -60,11 +50,21 @@ const ClientsIdRoute = ClientsIdImport.update({ getParentRoute: () => rootRoute, } as any) +const PasswordChangeIndexRoute = PasswordChangeIndexImport.update({ + path: '/password/change/', + getParentRoute: () => rootRoute, +} as any) + const AccountSessionsIndexRoute = AccountSessionsIndexImport.update({ path: '/sessions/', getParentRoute: () => AccountRoute, } as any) +const PasswordChangeSuccessRoute = PasswordChangeSuccessImport.update({ + path: '/password/change/success', + getParentRoute: () => rootRoute, +} as any) + const EmailsIdVerifyRoute = EmailsIdVerifyImport.update({ path: '/emails/$id/verify', getParentRoute: () => rootRoute, @@ -100,14 +100,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DevicesSplatImport parentRoute: typeof rootRoute } - '/password/change': { - preLoaderRoute: typeof PasswordChangeImport - parentRoute: typeof rootRoute - } - '/password/change_success': { - preLoaderRoute: typeof PasswordChangesuccessImport - parentRoute: typeof rootRoute - } '/_account/': { preLoaderRoute: typeof AccountIndexImport parentRoute: typeof AccountImport @@ -124,10 +116,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof EmailsIdVerifyImport parentRoute: typeof rootRoute } + '/password/change/success': { + preLoaderRoute: typeof PasswordChangeSuccessImport + parentRoute: typeof rootRoute + } '/_account/sessions/': { preLoaderRoute: typeof AccountSessionsIndexImport parentRoute: typeof AccountImport } + '/password/change/': { + preLoaderRoute: typeof PasswordChangeIndexImport + parentRoute: typeof rootRoute + } } } @@ -143,9 +143,9 @@ export const routeTree = rootRoute.addChildren([ ResetCrossSigningRoute, ClientsIdRoute, DevicesSplatRoute, - PasswordChangeRoute, - PasswordChangesuccessRoute, EmailsIdVerifyRoute, + PasswordChangeSuccessRoute, + PasswordChangeIndexRoute, ]) /* prettier-ignore-end */ diff --git a/frontend/src/routes/password.change.tsx b/frontend/src/routes/password.change.index.tsx similarity index 98% rename from frontend/src/routes/password.change.tsx rename to frontend/src/routes/password.change.index.tsx index 7a8693129..b4993efaf 100644 --- a/frontend/src/routes/password.change.tsx +++ b/frontend/src/routes/password.change.index.tsx @@ -57,7 +57,7 @@ const CHANGE_PASSWORD_MUTATION = graphql(/* GraphQL */ ` } `); -export const Route = createFileRoute("/password/change")({ +export const Route = createFileRoute("/password/change/")({ async loader({ context, abortController: { signal } }) { const viewer = await context.client.query( CURRENT_VIEWER_QUERY, @@ -101,7 +101,7 @@ function ChangePassword(): React.ReactNode { const response = await changePassword({ userId, oldPassword, newPassword }); if (response.data?.setPassword.status === SetPasswordStatus.Allowed) { - router.navigate({ to: "/password/change_success" }); + router.navigate({ to: "/password/change/success" }); } }; diff --git a/frontend/src/routes/password.change_success.tsx b/frontend/src/routes/password.change.success.tsx similarity index 97% rename from frontend/src/routes/password.change_success.tsx rename to frontend/src/routes/password.change.success.tsx index 70ae9b77c..c00f6a8be 100644 --- a/frontend/src/routes/password.change_success.tsx +++ b/frontend/src/routes/password.change.success.tsx @@ -35,7 +35,7 @@ const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ ` } `); -export const Route = createFileRoute("/password/change_success")({ +export const Route = createFileRoute("/password/change/success")({ async loader({ context, abortController: { signal } }) { const viewer = await context.client.query( CURRENT_VIEWER_QUERY, From de2859e61bc575d02ae95a04cdf6a8df67b58c44 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Jun 2024 12:00:29 +0100 Subject: [PATCH 13/15] Change style of success page --- frontend/src/routes/password.change.success.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/frontend/src/routes/password.change.success.tsx b/frontend/src/routes/password.change.success.tsx index c00f6a8be..dae6edf16 100644 --- a/frontend/src/routes/password.change.success.tsx +++ b/frontend/src/routes/password.change.success.tsx @@ -13,8 +13,7 @@ // limitations under the License. import { createFileRoute, notFound } from "@tanstack/react-router"; -import IconLockSolid from "@vector-im/compound-design-tokens/icons/lock-solid.svg?react"; -import { Alert } from "@vector-im/compound-web"; +import IconCheckCircle from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg?react"; import { useTranslation } from "react-i18next"; import { useQuery } from "urql"; @@ -59,19 +58,12 @@ function ChangePasswordSuccess(): React.ReactNode { - - {t("frontend.password_change.success.description")} - - {t("action.back")} From e5a21a672eaac2009ac1f4ba8eb73e032f94bb86 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Jun 2024 12:04:08 +0100 Subject: [PATCH 14/15] Move alerts to top of form --- frontend/src/routes/password.change.index.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/frontend/src/routes/password.change.index.tsx b/frontend/src/routes/password.change.index.tsx index b4993efaf..e681fe1c0 100644 --- a/frontend/src/routes/password.change.index.tsx +++ b/frontend/src/routes/password.change.index.tsx @@ -150,6 +150,23 @@ function ChangePassword(): React.ReactNode { method = POST just prevents sending passwords in the query string, which could be logged, if for some reason the event handler fails. */} + {unhandleableError && ( + + {t("frontend.password_change.failure.description.unspecified")} + + )} + + {handleableError && ( + + {errorMsg} + + )} - {unhandleableError && ( - - {t("frontend.password_change.failure.description.unspecified")} - - )} - {!!result.fetching && } {t("action.save")} - {handleableError && ( - - {errorMsg} - - )} - {t("action.cancel")} From 2c8ed615d52b6625d4c2f74b0ec7273077948e58 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Tue, 25 Jun 2024 13:53:30 +0100 Subject: [PATCH 15/15] Don't show inline errors twice --- frontend/src/routes/password.change.index.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/routes/password.change.index.tsx b/frontend/src/routes/password.change.index.tsx index e681fe1c0..509f14ac9 100644 --- a/frontend/src/routes/password.change.index.tsx +++ b/frontend/src/routes/password.change.index.tsx @@ -105,16 +105,10 @@ function ChangePassword(): React.ReactNode { } }; - const handleableError = - result.data && result.data.setPassword.status !== SetPasswordStatus.Allowed; const unhandleableError = result.error !== undefined; const errorMsg: string | undefined = ((): string | undefined => { switch (result.data?.setPassword.status) { - case SetPasswordStatus.InvalidNewPassword: - return t( - "frontend.password_change.failure.description.invalid_new_password", - ); case SetPasswordStatus.NoCurrentPassword: return t( "frontend.password_change.failure.description.no_current_password", @@ -123,11 +117,16 @@ function ChangePassword(): React.ReactNode { return t( "frontend.password_change.failure.description.password_changes_disabled", ); + case SetPasswordStatus.WrongPassword: - return t("frontend.password_change.failure.description.wrong_password"); + case SetPasswordStatus.InvalidNewPassword: + // These cases are shown as inline errors in the form itself. + return undefined; + case SetPasswordStatus.Allowed: case undefined: return undefined; + default: throw new Error( `unexpected error when changing password: ${result.data!.setPassword.status}`, @@ -159,7 +158,7 @@ function ChangePassword(): React.ReactNode { )} - {handleableError && ( + {errorMsg !== undefined && (