You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
frontend: password recovery
This commit is contained in:
committed by
reivilibre
parent
6613f4547a
commit
a6d46d90ca
@ -6,7 +6,8 @@
|
||||
"close": "Close",
|
||||
"continue": "Continue",
|
||||
"edit": "Edit",
|
||||
"save": "Save"
|
||||
"save": "Save",
|
||||
"save_and_continue": "Save and continue"
|
||||
},
|
||||
"branding": {
|
||||
"privacy_policy": {
|
||||
@ -135,6 +136,9 @@
|
||||
},
|
||||
"title": "Change your password"
|
||||
},
|
||||
"password_reset": {
|
||||
"title": "Reset your password"
|
||||
},
|
||||
"password_strength": {
|
||||
"placeholder": "Password strength",
|
||||
"score": {
|
||||
|
@ -55,6 +55,8 @@ const documents = {
|
||||
"\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n": types.VerifyEmailQueryDocument,
|
||||
"\n query PasswordChangeQuery {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": types.PasswordChangeQueryDocument,
|
||||
"\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 query PasswordRecoveryQuery {\n siteConfig {\n id\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": types.PasswordRecoveryQueryDocument,
|
||||
"\n mutation RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\n ) {\n status\n }\n }\n": types.RecoverPasswordDocument,
|
||||
"\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n": types.AllowCrossSigningResetDocument,
|
||||
};
|
||||
|
||||
@ -240,6 +242,14 @@ export function graphql(source: "\n query PasswordChangeQuery {\n viewer {\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 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.
|
||||
*/
|
||||
export function graphql(source: "\n query PasswordRecoveryQuery {\n siteConfig {\n id\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n"): (typeof documents)["\n query PasswordRecoveryQuery {\n siteConfig {\n id\n ...PasswordCreationDoubleInput_siteConfig\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 RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\n ) {\n status\n }\n }\n"): (typeof documents)["\n mutation RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\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.
|
||||
*/
|
||||
|
@ -1694,6 +1694,22 @@ export type ChangePasswordMutationVariables = Exact<{
|
||||
|
||||
export type ChangePasswordMutation = { __typename?: 'Mutation', setPassword: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } };
|
||||
|
||||
export type PasswordRecoveryQueryQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type PasswordRecoveryQueryQuery = { __typename?: 'Query', siteConfig: (
|
||||
{ __typename?: 'SiteConfig', id: string }
|
||||
& { ' $fragmentRefs'?: { 'PasswordCreationDoubleInput_SiteConfigFragment': PasswordCreationDoubleInput_SiteConfigFragment } }
|
||||
) };
|
||||
|
||||
export type RecoverPasswordMutationVariables = Exact<{
|
||||
ticket: Scalars['String']['input'];
|
||||
newPassword: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type RecoverPasswordMutation = { __typename?: 'Mutation', setPasswordByRecovery: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } };
|
||||
|
||||
export type AllowCrossSigningResetMutationVariables = Exact<{
|
||||
userId: Scalars['ID']['input'];
|
||||
}>;
|
||||
@ -1743,4 +1759,6 @@ export const DeviceRedirectQueryDocument = {"kind":"Document","definitions":[{"k
|
||||
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<VerifyEmailQueryQuery, VerifyEmailQueryQueryVariables>;
|
||||
export const PasswordChangeQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PasswordChangeQuery"},"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"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PasswordCreationDoubleInput_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordCreationDoubleInput_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":"minimumPasswordComplexity"}}]}}]} as unknown as DocumentNode<PasswordChangeQueryQuery, PasswordChangeQueryQueryVariables>;
|
||||
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<ChangePasswordMutation, ChangePasswordMutationVariables>;
|
||||
export const PasswordRecoveryQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PasswordRecoveryQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PasswordCreationDoubleInput_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordCreationDoubleInput_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":"minimumPasswordComplexity"}}]}}]} as unknown as DocumentNode<PasswordRecoveryQueryQuery, PasswordRecoveryQueryQueryVariables>;
|
||||
export const RecoverPasswordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RecoverPassword"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ticket"}},"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":"setPasswordByRecovery"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"ticket"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ticket"}}},{"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<RecoverPasswordMutation, RecoverPasswordMutationVariables>;
|
||||
export const AllowCrossSigningResetDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AllowCrossSigningReset"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allowUserCrossSigningReset"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<AllowCrossSigningResetMutation, AllowCrossSigningResetMutationVariables>;
|
@ -16,6 +16,7 @@ import { Route as AccountImport } from './routes/_account'
|
||||
import { Route as AccountIndexImport } from './routes/_account.index'
|
||||
import { Route as DevicesSplatImport } from './routes/devices.$'
|
||||
import { Route as ClientsIdImport } from './routes/clients.$id'
|
||||
import { Route as PasswordRecoveryIndexImport } from './routes/password.recovery.index'
|
||||
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'
|
||||
@ -50,6 +51,13 @@ const ClientsIdRoute = ClientsIdImport.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const PasswordRecoveryIndexRoute = PasswordRecoveryIndexImport.update({
|
||||
path: '/password/recovery/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/password.recovery.index.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const PasswordChangeIndexRoute = PasswordChangeIndexImport.update({
|
||||
path: '/password/change/',
|
||||
getParentRoute: () => rootRoute,
|
||||
@ -163,6 +171,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof PasswordChangeIndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/password/recovery/': {
|
||||
id: '/password/recovery/'
|
||||
path: '/password/recovery'
|
||||
fullPath: '/password/recovery'
|
||||
preLoaderRoute: typeof PasswordRecoveryIndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +196,7 @@ export const routeTree = rootRoute.addChildren({
|
||||
EmailsIdVerifyRoute,
|
||||
PasswordChangeSuccessRoute,
|
||||
PasswordChangeIndexRoute,
|
||||
PasswordRecoveryIndexRoute,
|
||||
})
|
||||
|
||||
/* prettier-ignore-end */
|
||||
@ -197,7 +213,8 @@ export const routeTree = rootRoute.addChildren({
|
||||
"/devices/$",
|
||||
"/emails/$id/verify",
|
||||
"/password/change/success",
|
||||
"/password/change/"
|
||||
"/password/change/",
|
||||
"/password/recovery/"
|
||||
]
|
||||
},
|
||||
"/_account": {
|
||||
@ -242,6 +259,9 @@ export const routeTree = rootRoute.addChildren({
|
||||
},
|
||||
"/password/change/": {
|
||||
"filePath": "password.change.index.tsx"
|
||||
},
|
||||
"/password/recovery/": {
|
||||
"filePath": "password.recovery.index.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
155
frontend/src/routes/password.recovery.index.lazy.tsx
Normal file
155
frontend/src/routes/password.recovery.index.lazy.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
// 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 {
|
||||
createLazyFileRoute,
|
||||
useRouter,
|
||||
useSearch,
|
||||
} from "@tanstack/react-router";
|
||||
import IconLockSolid from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
|
||||
import { Alert, Form } from "@vector-im/compound-web";
|
||||
import { FormEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation, useQuery } from "urql";
|
||||
|
||||
import BlockList from "../components/BlockList";
|
||||
import Layout from "../components/Layout";
|
||||
import LoadingSpinner from "../components/LoadingSpinner";
|
||||
import PageHeading from "../components/PageHeading";
|
||||
import PasswordCreationDoubleInput from "../components/PasswordCreationDoubleInput";
|
||||
import { graphql } from "../gql";
|
||||
import { SetPasswordStatus } from "../gql/graphql";
|
||||
import { translateSetPasswordError } from "../i18n/password_changes";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query PasswordRecoveryQuery {
|
||||
siteConfig {
|
||||
id
|
||||
...PasswordCreationDoubleInput_siteConfig
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const RECOVER_PASSWORD_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation RecoverPassword($ticket: String!, $newPassword: String!) {
|
||||
setPasswordByRecovery(
|
||||
input: { ticket: $ticket, newPassword: $newPassword }
|
||||
) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const Route = createLazyFileRoute("/password/recovery/")({
|
||||
component: RecoverPassword,
|
||||
});
|
||||
|
||||
function RecoverPassword(): React.ReactNode {
|
||||
const { t } = useTranslation();
|
||||
const { ticket } = useSearch({
|
||||
from: "/password/recovery/",
|
||||
});
|
||||
const [queryResult] = useQuery({ query: QUERY });
|
||||
const router = useRouter();
|
||||
if (queryResult.error) throw queryResult.error;
|
||||
const siteConfig = queryResult.data?.siteConfig;
|
||||
if (!siteConfig) throw Error(); // This should never happen
|
||||
|
||||
const [result, changePassword] = useMutation(RECOVER_PASSWORD_MUTATION);
|
||||
|
||||
const onSubmit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
const response = await changePassword({ ticket, newPassword });
|
||||
|
||||
if (
|
||||
response.data?.setPasswordByRecovery.status === SetPasswordStatus.Allowed
|
||||
) {
|
||||
// Redirect to the application root using a full page load
|
||||
// The MAS backend will then redirect to the login page
|
||||
// Unfortunately this won't work in dev mode (`npm run dev`)
|
||||
// as the backend isn't involved there.
|
||||
const location = router.buildLocation({ to: "/" });
|
||||
window.location.href = location.href;
|
||||
}
|
||||
};
|
||||
|
||||
const unhandleableError = result.error !== undefined;
|
||||
|
||||
const errorMsg: string | undefined = translateSetPasswordError(
|
||||
t,
|
||||
result.data?.setPasswordByRecovery.status,
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<BlockList>
|
||||
<PageHeading
|
||||
Icon={IconLockSolid}
|
||||
title={t("frontend.password_reset.title")}
|
||||
subtitle={t("frontend.password_change.subtitle")}
|
||||
/>
|
||||
|
||||
<Form.Root onSubmit={onSubmit} method="POST">
|
||||
{/*
|
||||
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.
|
||||
*/}
|
||||
{unhandleableError && (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={t("frontend.password_change.failure.title")}
|
||||
>
|
||||
{t("frontend.password_change.failure.description.unspecified")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{errorMsg !== undefined && (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={t("frontend.password_change.failure.title")}
|
||||
>
|
||||
{errorMsg}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<PasswordCreationDoubleInput
|
||||
siteConfig={siteConfig}
|
||||
forceShowNewPasswordInvalid={
|
||||
(result.data &&
|
||||
result.data.setPasswordByRecovery.status ==
|
||||
SetPasswordStatus.InvalidNewPassword) ||
|
||||
false
|
||||
}
|
||||
/>
|
||||
|
||||
<Form.Submit kind="primary" disabled={result.fetching}>
|
||||
{!!result.fetching && <LoadingSpinner inline />}
|
||||
{t("action.save_and_continue")}
|
||||
</Form.Submit>
|
||||
</Form.Root>
|
||||
</BlockList>
|
||||
</Layout>
|
||||
);
|
||||
}
|
41
frontend/src/routes/password.recovery.index.tsx
Normal file
41
frontend/src/routes/password.recovery.index.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 } from "@tanstack/react-router";
|
||||
|
||||
import { graphql } from "../gql";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query PasswordRecoveryQuery {
|
||||
siteConfig {
|
||||
id
|
||||
...PasswordCreationDoubleInput_siteConfig
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const Route = createFileRoute("/password/recovery/")({
|
||||
validateSearch: (search) =>
|
||||
search as {
|
||||
ticket: string;
|
||||
},
|
||||
async loader({ context, abortController: { signal } }) {
|
||||
const queryResult = await context.client.query(
|
||||
QUERY,
|
||||
{},
|
||||
{ fetchOptions: { signal } },
|
||||
);
|
||||
if (queryResult.error) throw queryResult.error;
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user