From ffce1c5765eaf0c7bf4a57942e621b824093234e Mon Sep 17 00:00:00 2001 From: Siddharth Date: Thu, 14 Dec 2023 16:15:58 +0530 Subject: [PATCH] feat(dasboard): adding totp in dashboard (#3696) * feat(dasboard): adding totp in dashboard * chore: added protected * feat: Ui improvements * chore: fix testid * chore: misc changes * chore: change nextjs version rebase * feat: fix qr and added multiple identifier * chore: lint fix --------- Co-authored-by: Siddharth --- apps/consent/package.json | 2 +- apps/dashboard/app/security/2fa/add/page.tsx | 38 ++++ .../app/security/2fa/add/verify-form.tsx | 210 ++++++++++++++++++ .../app/security/2fa/server-actions.ts | 132 +++++++++++ apps/dashboard/app/security/2fa/totp.types.ts | 12 + apps/dashboard/app/security/page.tsx | 6 +- apps/dashboard/app/url.ts | 1 + .../security/twoFactorAuthSettings/index.tsx | 144 ++++++++++++ apps/dashboard/package.json | 1 + apps/dashboard/services/graphql/generated.ts | 189 +++++++++++++--- .../services/graphql/mutations/totp.ts | 97 ++++++++ pnpm-lock.yaml | 60 +---- 12 files changed, 811 insertions(+), 81 deletions(-) create mode 100644 apps/dashboard/app/security/2fa/add/page.tsx create mode 100644 apps/dashboard/app/security/2fa/add/verify-form.tsx create mode 100644 apps/dashboard/app/security/2fa/server-actions.ts create mode 100644 apps/dashboard/app/security/2fa/totp.types.ts create mode 100644 apps/dashboard/components/security/twoFactorAuthSettings/index.tsx create mode 100644 apps/dashboard/services/graphql/mutations/totp.ts diff --git a/apps/consent/package.json b/apps/consent/package.json index 93750828ec..afba45d16c 100644 --- a/apps/consent/package.json +++ b/apps/consent/package.json @@ -54,7 +54,7 @@ "@types/react-dom": "18.2.17", "@types/react-phone-number-input": "^3.0.17", "autoprefixer": "10.4.16", - "cypress": "^13.3.0", + "cypress": "^13.6.1", "eslint": "8.55.0", "eslint-config-next": "14.0.4", "postcss": "8.4.32", diff --git a/apps/dashboard/app/security/2fa/add/page.tsx b/apps/dashboard/app/security/2fa/add/page.tsx new file mode 100644 index 0000000000..9866f4d693 --- /dev/null +++ b/apps/dashboard/app/security/2fa/add/page.tsx @@ -0,0 +1,38 @@ +import { getServerSession } from "next-auth" +import { redirect } from "next/navigation" + +import { totpRegisterInitiateServerAction } from "../server-actions" + +import VerifyTwoFactorAuth from "./verify-form" + +import { authOptions } from "@/app/api/auth/[...nextauth]/route" + +export default async function VerifyTwoFactorAuthPage() { + const session = await getServerSession(authOptions) + const token = session?.accessToken + const totpEnabled = session?.userData.data.me?.totpEnabled + const totpIdentifier = + session?.userData.data.me?.username || + session?.userData.data.me?.email?.address || + session?.userData.data.me?.phone || + session?.userData.data.me?.id || + "Blink User" + + if (!token || typeof token !== "string" || totpEnabled === true) { + redirect("/security") + } + + const { error, responsePayload, message } = await totpRegisterInitiateServerAction() + if (error || !responsePayload?.totpRegistrationId || !responsePayload?.totpSecret) { + console.error(message) + const errorMessage = message || "Something Went Wrong" + throw new Error(errorMessage) + } + return ( + + ) +} diff --git a/apps/dashboard/app/security/2fa/add/verify-form.tsx b/apps/dashboard/app/security/2fa/add/verify-form.tsx new file mode 100644 index 0000000000..31a6f8657c --- /dev/null +++ b/apps/dashboard/app/security/2fa/add/verify-form.tsx @@ -0,0 +1,210 @@ +"use client" + +import ArrowForwardIcon from "@mui/icons-material/ArrowForward" +import { useFormState } from "react-dom" +import CopyIcon from "@mui/icons-material/CopyAll" + +import { + Box, + Button, + Input, + FormControl, + FormHelperText, + Typography, + Tooltip, + Card, +} from "@mui/joy" +import InfoOutlined from "@mui/icons-material/InfoOutlined" +import Link from "next/link" + +import { QRCode } from "react-qrcode-logo" + +import { useState } from "react" + +import { totpRegisterValidateServerAction } from "../server-actions" + +import { TotpValidateResponse } from "../totp.types" + +import FormSubmitButton from "@/components/form-submit-button" + +type VerifyTwoFactorAuth = { + totpRegistrationId: string + totpSecret: string + totpIdentifier: string +} + +const AuthenticatorQRCode = ({ + account, + secret, +}: { + account: string + secret: string +}) => { + return `otpauth://totp/Blink:${account}?secret=${secret}&issuer=Blink` +} + +export default function VerifyTwoFactorAuth({ + totpRegistrationId, + totpSecret, + totpIdentifier, +}: VerifyTwoFactorAuth) { + const [copied, setCopied] = useState(false) + const [state, formAction] = useFormState( + totpRegisterValidateServerAction, + { + error: false, + message: null, + responsePayload: null, + }, + ) + + return ( +
+ + Enter 2FA Code + + + Authenticator Secret Key + + + + {totpSecret} + + { + setCopied(true) + setTimeout(() => { + setCopied(false) + }, 2000) + navigator.clipboard.writeText(totpSecret ?? "") + }} + > + + + + + + + + + +
+ + + + {state.error ? ( + + + {state.message} + + ) : null} + + + + + + + Confirm + + +
+
+ + To enable two-factor authentication (2FA), add the secret key to your + Authenticator App manually or use the app to scan the provided QR code. Once + added, enter the code generated by the Authenticator App to complete the setup. + +
+
+ ) +} diff --git a/apps/dashboard/app/security/2fa/server-actions.ts b/apps/dashboard/app/security/2fa/server-actions.ts new file mode 100644 index 0000000000..e503c8db7b --- /dev/null +++ b/apps/dashboard/app/security/2fa/server-actions.ts @@ -0,0 +1,132 @@ +"use server" +import { getServerSession } from "next-auth" +import { redirect } from "next/navigation" + +import { revalidatePath } from "next/cache" + +import { TotpRegisterResponse, TotpValidateResponse } from "./totp.types" + +import { + userTotpRegistrationInitiate, + userTotpRegistrationValidate, + userTotpDelete, +} from "@/services/graphql/mutations/totp" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" + +import { + UserTotpRegistrationInitiateMutation, + UserTotpRegistrationValidateMutation, +} from "@/services/graphql/generated" + +export const totpRegisterInitiateServerAction = + async (): Promise => { + const session = await getServerSession(authOptions) + const token = session?.accessToken + if (!token && typeof token !== "string") { + return { + error: true, + message: "Invalid Token", + responsePayload: null, + } + } + + let data: UserTotpRegistrationInitiateMutation | null | undefined + try { + data = await userTotpRegistrationInitiate(token) + } catch (err) { + console.log("error in userTotpRegistrationInitiate ", err) + return { + error: true, + message: + "Something went wrong. Please try again. If the error persists, contact support.", + responsePayload: null, + } + } + + if (data?.userTotpRegistrationInitiate.errors.length) { + return { + error: true, + message: data?.userTotpRegistrationInitiate.errors[0].message, + responsePayload: null, + } + } + + const totpRegistrationId = data?.userTotpRegistrationInitiate.totpRegistrationId + const totpSecret = data?.userTotpRegistrationInitiate.totpSecret + return { + error: false, + message: "", + responsePayload: { + totpRegistrationId, + totpSecret, + }, + } + } + +export const totpRegisterValidateServerAction = async ( + _prevState: TotpValidateResponse, + form: FormData, +): Promise => { + const totpCode = form.get("code") + const totpRegistrationId = form.get("totpRegistrationId") + if ( + !totpCode || + typeof totpCode !== "string" || + !totpRegistrationId || + typeof totpRegistrationId !== "string" + ) { + return { + error: true, + message: "Invalid values", + responsePayload: null, + } + } + + const session = await getServerSession(authOptions) + const token = session?.accessToken + + if (!token || typeof token !== "string") { + return { + error: true, + message: "Invalid Token", + responsePayload: null, + } + } + + let totpValidationResponse: UserTotpRegistrationValidateMutation | null | undefined + try { + totpValidationResponse = await userTotpRegistrationValidate( + totpCode, + totpRegistrationId, + token, + ) + } catch (err) { + console.log("error in userTotpRegistrationValidate ", err) + return { + error: true, + message: + "Something went wrong. Please try again. If the error persists, contact support.", + responsePayload: null, + } + } + + if (totpValidationResponse?.userTotpRegistrationValidate.errors.length) { + return { + error: true, + message: totpValidationResponse?.userTotpRegistrationValidate.errors[0].message, + responsePayload: null, + } + } + + redirect("/security") +} + +export const deleteTotpServerAction = async () => { + const session = await getServerSession(authOptions) + const token = session?.accessToken + if (!token && typeof token !== "string") { + return + } + await userTotpDelete(token) + revalidatePath("/security") +} diff --git a/apps/dashboard/app/security/2fa/totp.types.ts b/apps/dashboard/app/security/2fa/totp.types.ts new file mode 100644 index 0000000000..4c6b0c91da --- /dev/null +++ b/apps/dashboard/app/security/2fa/totp.types.ts @@ -0,0 +1,12 @@ +import { ServerActionResponse } from "@/app/index.types" + +type TotpRegisterBody = { + totpRegistrationId: string | undefined | null + totpSecret: string | undefined | null +} +export interface TotpRegisterResponse + extends ServerActionResponse {} + +type TotpValidateBody = null +export interface TotpValidateResponse + extends ServerActionResponse {} diff --git a/apps/dashboard/app/security/page.tsx b/apps/dashboard/app/security/page.tsx index 8b14107473..a565c3dfe1 100644 --- a/apps/dashboard/app/security/page.tsx +++ b/apps/dashboard/app/security/page.tsx @@ -4,10 +4,11 @@ import { Box } from "@mui/joy" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import ContentContainer from "@/components/content-container" import EmailSettings from "@/components/security/email/email" - +import TwoFactorAuthSettings from "@/components/security/twoFactorAuthSettings" export default async function Home() { const session = await getServerSession(authOptions) const email = session?.userData.data.me?.email + const totpEnabled = session?.userData.data.me?.totpEnabled return ( {email ? : null} + ) diff --git a/apps/dashboard/app/url.ts b/apps/dashboard/app/url.ts index 287a93374f..3e2050e451 100644 --- a/apps/dashboard/app/url.ts +++ b/apps/dashboard/app/url.ts @@ -11,4 +11,5 @@ export const URLS: Record = { "/security/email/verify": { title: "Verify Email", protected: true }, "/api-keys": { title: "API Keys", protected: true }, "/callback": { title: "Callback", protected: true }, + "/security/2fa/add": { title: "Add 2FA to Account", protected: true }, } diff --git a/apps/dashboard/components/security/twoFactorAuthSettings/index.tsx b/apps/dashboard/components/security/twoFactorAuthSettings/index.tsx new file mode 100644 index 0000000000..aff66d50d3 --- /dev/null +++ b/apps/dashboard/components/security/twoFactorAuthSettings/index.tsx @@ -0,0 +1,144 @@ +"use client" +import { Button, Typography, Box, Modal, Sheet, ModalClose } from "@mui/joy" +import DeleteIcon from "@mui/icons-material/Delete" +import Link from "next/link" +import { useState } from "react" +import ReportProblemRoundedIcon from "@mui/icons-material/ReportProblemRounded" + +import LockOpenIcon from "@mui/icons-material/LockOpen" + +import { deleteTotpServerAction } from "@/app/security/2fa/server-actions" + +type twoFactorAuthSettingsProps = { + totpEnabled: boolean | undefined +} + +function TwoFactorAuthSettings({ totpEnabled }: twoFactorAuthSettingsProps) { + const [open, setOpen] = useState(false) + const [loading, setLoading] = useState(false) + + if (totpEnabled === undefined) { + return null + } + + return ( + <> + setOpen(false)} + sx={{ display: "flex", justifyContent: "center", alignItems: "center" }} + > + + + + + + Remove 2FA Address + + + + Are you sure you want to remove 2FA from your account? + + + + + + + + + + Two Factor Authentication + + Use 2FA code to login in your Account. + + + + {totpEnabled ? ( + + + + ) : ( + + + + )} + + + + ) +} + +export default TwoFactorAuthSettings diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index e95d7b894f..dbd083667b 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -38,6 +38,7 @@ "next-auth": "^4.24.5", "react": "18.2.0", "react-dom": "18.2.0", + "react-qrcode-logo": "^2.9.0", "zod": "^3.22.4" }, "devDependencies": { diff --git a/apps/dashboard/services/graphql/generated.ts b/apps/dashboard/services/graphql/generated.ts index 7c004891d7..6df39107f1 100644 --- a/apps/dashboard/services/graphql/generated.ts +++ b/apps/dashboard/services/graphql/generated.ts @@ -533,6 +533,8 @@ export type InitiationViaIntraLedger = { export type InitiationViaLn = { readonly __typename: 'InitiationViaLn'; readonly paymentHash: Scalars['PaymentHash']['output']; + /** Bolt11 invoice */ + readonly paymentRequest: Scalars['LnPaymentRequest']['output']; }; export type InitiationViaOnChain = { @@ -1154,16 +1156,6 @@ export type MutationUserPhoneRegistrationValidateArgs = { }; -export type MutationUserTotpDeleteArgs = { - input: UserTotpDeleteInput; -}; - - -export type MutationUserTotpRegistrationInitiateArgs = { - input: UserTotpRegistrationInitiateInput; -}; - - export type MutationUserTotpRegistrationValidateArgs = { input: UserTotpRegistrationValidateInput; }; @@ -1556,6 +1548,7 @@ export type SettlementViaLn = { export type SettlementViaOnChain = { readonly __typename: 'SettlementViaOnChain'; + readonly arrivalInMempoolEstimatedAt?: Maybe; readonly transactionHash?: Maybe; readonly vout?: Maybe; }; @@ -1890,20 +1883,12 @@ export type UserQuizQuestion = { readonly question: QuizQuestion; }; -export type UserTotpDeleteInput = { - readonly authToken: Scalars['AuthToken']['input']; -}; - export type UserTotpDeletePayload = { readonly __typename: 'UserTotpDeletePayload'; readonly errors: ReadonlyArray; readonly me?: Maybe; }; -export type UserTotpRegistrationInitiateInput = { - readonly authToken: Scalars['AuthToken']['input']; -}; - export type UserTotpRegistrationInitiatePayload = { readonly __typename: 'UserTotpRegistrationInitiatePayload'; readonly errors: ReadonlyArray; @@ -1912,7 +1897,7 @@ export type UserTotpRegistrationInitiatePayload = { }; export type UserTotpRegistrationValidateInput = { - readonly authToken: Scalars['AuthToken']['input']; + readonly authToken?: InputMaybe; readonly totpCode: Scalars['TotpCode']['input']; readonly totpRegistrationId: Scalars['TotpRegistrationId']['input']; }; @@ -2088,6 +2073,23 @@ export type UserEmailDeleteMutationVariables = Exact<{ [key: string]: never; }>; export type UserEmailDeleteMutation = { readonly __typename: 'Mutation', readonly userEmailDelete: { readonly __typename: 'UserEmailDeletePayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly code?: string | null, readonly message: string }> } }; +export type UserTotpRegistrationInitiateMutationVariables = Exact<{ [key: string]: never; }>; + + +export type UserTotpRegistrationInitiateMutation = { readonly __typename: 'Mutation', readonly userTotpRegistrationInitiate: { readonly __typename: 'UserTotpRegistrationInitiatePayload', readonly totpRegistrationId?: string | null, readonly totpSecret?: string | null, readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly code?: string | null, readonly message: string, readonly path?: ReadonlyArray | null }> } }; + +export type UserTotpDeleteMutationVariables = Exact<{ [key: string]: never; }>; + + +export type UserTotpDeleteMutation = { readonly __typename: 'Mutation', readonly userTotpDelete: { readonly __typename: 'UserTotpDeletePayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly code?: string | null, readonly message: string, readonly path?: ReadonlyArray | null }> } }; + +export type UserTotpRegistrationValidateMutationVariables = Exact<{ + input: UserTotpRegistrationValidateInput; +}>; + + +export type UserTotpRegistrationValidateMutation = { readonly __typename: 'Mutation', readonly userTotpRegistrationValidate: { readonly __typename: 'UserTotpRegistrationValidatePayload', readonly errors: ReadonlyArray<{ readonly __typename: 'GraphQLApplicationError', readonly code?: string | null, readonly message: string, readonly path?: ReadonlyArray | null }>, readonly me?: { readonly __typename: 'User', readonly id: string } | null } }; + export type ApiKeysQueryVariables = Exact<{ [key: string]: never; }>; @@ -2385,6 +2387,120 @@ export function useUserEmailDeleteMutation(baseOptions?: Apollo.MutationHookOpti export type UserEmailDeleteMutationHookResult = ReturnType; export type UserEmailDeleteMutationResult = Apollo.MutationResult; export type UserEmailDeleteMutationOptions = Apollo.BaseMutationOptions; +export const UserTotpRegistrationInitiateDocument = gql` + mutation UserTotpRegistrationInitiate { + userTotpRegistrationInitiate { + totpRegistrationId + totpSecret + errors { + code + message + path + } + } +} + `; +export type UserTotpRegistrationInitiateMutationFn = Apollo.MutationFunction; + +/** + * __useUserTotpRegistrationInitiateMutation__ + * + * To run a mutation, you first call `useUserTotpRegistrationInitiateMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUserTotpRegistrationInitiateMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [userTotpRegistrationInitiateMutation, { data, loading, error }] = useUserTotpRegistrationInitiateMutation({ + * variables: { + * }, + * }); + */ +export function useUserTotpRegistrationInitiateMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UserTotpRegistrationInitiateDocument, options); + } +export type UserTotpRegistrationInitiateMutationHookResult = ReturnType; +export type UserTotpRegistrationInitiateMutationResult = Apollo.MutationResult; +export type UserTotpRegistrationInitiateMutationOptions = Apollo.BaseMutationOptions; +export const UserTotpDeleteDocument = gql` + mutation UserTotpDelete { + userTotpDelete { + errors { + code + message + path + } + } +} + `; +export type UserTotpDeleteMutationFn = Apollo.MutationFunction; + +/** + * __useUserTotpDeleteMutation__ + * + * To run a mutation, you first call `useUserTotpDeleteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUserTotpDeleteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [userTotpDeleteMutation, { data, loading, error }] = useUserTotpDeleteMutation({ + * variables: { + * }, + * }); + */ +export function useUserTotpDeleteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UserTotpDeleteDocument, options); + } +export type UserTotpDeleteMutationHookResult = ReturnType; +export type UserTotpDeleteMutationResult = Apollo.MutationResult; +export type UserTotpDeleteMutationOptions = Apollo.BaseMutationOptions; +export const UserTotpRegistrationValidateDocument = gql` + mutation UserTotpRegistrationValidate($input: UserTotpRegistrationValidateInput!) { + userTotpRegistrationValidate(input: $input) { + errors { + code + message + path + } + me { + id + } + } +} + `; +export type UserTotpRegistrationValidateMutationFn = Apollo.MutationFunction; + +/** + * __useUserTotpRegistrationValidateMutation__ + * + * To run a mutation, you first call `useUserTotpRegistrationValidateMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUserTotpRegistrationValidateMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [userTotpRegistrationValidateMutation, { data, loading, error }] = useUserTotpRegistrationValidateMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useUserTotpRegistrationValidateMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UserTotpRegistrationValidateDocument, options); + } +export type UserTotpRegistrationValidateMutationHookResult = ReturnType; +export type UserTotpRegistrationValidateMutationResult = Apollo.MutationResult; +export type UserTotpRegistrationValidateMutationOptions = Apollo.BaseMutationOptions; export const ApiKeysDocument = gql` query ApiKeys { me { @@ -2425,8 +2541,13 @@ export function useApiKeysLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(ApiKeysDocument, options); } +export function useApiKeysSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ApiKeysDocument, options); + } export type ApiKeysQueryHookResult = ReturnType; export type ApiKeysLazyQueryHookResult = ReturnType; +export type ApiKeysSuspenseQueryHookResult = ReturnType; export type ApiKeysQueryResult = Apollo.QueryResult; export const CallbackEndpointsDocument = gql` query CallbackEndpoints { @@ -2464,8 +2585,13 @@ export function useCallbackEndpointsLazyQuery(baseOptions?: Apollo.LazyQueryHook const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(CallbackEndpointsDocument, options); } +export function useCallbackEndpointsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(CallbackEndpointsDocument, options); + } export type CallbackEndpointsQueryHookResult = ReturnType; export type CallbackEndpointsLazyQueryHookResult = ReturnType; +export type CallbackEndpointsSuspenseQueryHookResult = ReturnType; export type CallbackEndpointsQueryResult = Apollo.QueryResult; export const GetPaginatedTransactionsDocument = gql` query GetPaginatedTransactions($first: Int, $after: String, $before: String) { @@ -2559,8 +2685,13 @@ export function useGetPaginatedTransactionsLazyQuery(baseOptions?: Apollo.LazyQu const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetPaginatedTransactionsDocument, options); } +export function useGetPaginatedTransactionsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetPaginatedTransactionsDocument, options); + } export type GetPaginatedTransactionsQueryHookResult = ReturnType; export type GetPaginatedTransactionsLazyQueryHookResult = ReturnType; +export type GetPaginatedTransactionsSuspenseQueryHookResult = ReturnType; export type GetPaginatedTransactionsQueryResult = Apollo.QueryResult; export const GetFirstTransactionsDocument = gql` query GetFirstTransactions($first: Int) { @@ -2652,8 +2783,13 @@ export function useGetFirstTransactionsLazyQuery(baseOptions?: Apollo.LazyQueryH const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetFirstTransactionsDocument, options); } +export function useGetFirstTransactionsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetFirstTransactionsDocument, options); + } export type GetFirstTransactionsQueryHookResult = ReturnType; export type GetFirstTransactionsLazyQueryHookResult = ReturnType; +export type GetFirstTransactionsSuspenseQueryHookResult = ReturnType; export type GetFirstTransactionsQueryResult = Apollo.QueryResult; export const MeDocument = gql` query me { @@ -2708,8 +2844,13 @@ export function useMeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(MeDocument, options); } +export function useMeSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(MeDocument, options); + } export type MeQueryHookResult = ReturnType; export type MeLazyQueryHookResult = ReturnType; +export type MeSuspenseQueryHookResult = ReturnType; export type MeQueryResult = Apollo.QueryResult; @@ -2984,9 +3125,7 @@ export type ResolversTypes = { UserPhoneRegistrationValidateInput: UserPhoneRegistrationValidateInput; UserPhoneRegistrationValidatePayload: ResolverTypeWrapper; UserQuizQuestion: ResolverTypeWrapper; - UserTotpDeleteInput: UserTotpDeleteInput; UserTotpDeletePayload: ResolverTypeWrapper; - UserTotpRegistrationInitiateInput: UserTotpRegistrationInitiateInput; UserTotpRegistrationInitiatePayload: ResolverTypeWrapper; UserTotpRegistrationValidateInput: UserTotpRegistrationValidateInput; UserTotpRegistrationValidatePayload: ResolverTypeWrapper; @@ -3178,9 +3317,7 @@ export type ResolversParentTypes = { UserPhoneRegistrationValidateInput: UserPhoneRegistrationValidateInput; UserPhoneRegistrationValidatePayload: UserPhoneRegistrationValidatePayload; UserQuizQuestion: UserQuizQuestion; - UserTotpDeleteInput: UserTotpDeleteInput; UserTotpDeletePayload: UserTotpDeletePayload; - UserTotpRegistrationInitiateInput: UserTotpRegistrationInitiateInput; UserTotpRegistrationInitiatePayload: UserTotpRegistrationInitiatePayload; UserTotpRegistrationValidateInput: UserTotpRegistrationValidateInput; UserTotpRegistrationValidatePayload: UserTotpRegistrationValidatePayload; @@ -3481,6 +3618,7 @@ export type InitiationViaIntraLedgerResolvers = { paymentHash?: Resolver; + paymentRequest?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; @@ -3657,8 +3795,8 @@ export type MutationResolvers; userPhoneRegistrationInitiate?: Resolver>; userPhoneRegistrationValidate?: Resolver>; - userTotpDelete?: Resolver>; - userTotpRegistrationInitiate?: Resolver>; + userTotpDelete?: Resolver; + userTotpRegistrationInitiate?: Resolver; userTotpRegistrationValidate?: Resolver>; userUpdateLanguage?: Resolver>; userUpdateUsername?: Resolver>; @@ -3897,6 +4035,7 @@ export type SettlementViaLnResolvers = { + arrivalInMempoolEstimatedAt?: Resolver, ParentType, ContextType>; transactionHash?: Resolver, ParentType, ContextType>; vout?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; diff --git a/apps/dashboard/services/graphql/mutations/totp.ts b/apps/dashboard/services/graphql/mutations/totp.ts new file mode 100644 index 0000000000..cb5bb926c5 --- /dev/null +++ b/apps/dashboard/services/graphql/mutations/totp.ts @@ -0,0 +1,97 @@ +import { gql } from "@apollo/client" + +import { apollo } from ".." +import { + UserTotpDeleteDocument, + UserTotpDeleteMutation, + UserTotpRegistrationInitiateDocument, + UserTotpRegistrationInitiateMutation, + UserTotpRegistrationValidateDocument, + UserTotpRegistrationValidateMutation, +} from "../generated" + +gql` + mutation UserTotpRegistrationInitiate { + userTotpRegistrationInitiate { + totpRegistrationId + totpSecret + errors { + code + message + path + } + } + } + + mutation UserTotpDelete { + userTotpDelete { + errors { + code + message + path + } + } + } + + mutation UserTotpRegistrationValidate($input: UserTotpRegistrationValidateInput!) { + userTotpRegistrationValidate(input: $input) { + errors { + code + message + path + } + me { + id + } + } + } +` + +export async function userTotpRegistrationInitiate(token: string) { + const client = apollo(token).getClient() + try { + const { data } = await client.mutate({ + mutation: UserTotpRegistrationInitiateDocument, + }) + return data + } catch (error) { + console.error("Error executing mutation: UserTotpRegistrationInitiate ==> ", error) + throw new Error("Error in UserTotpRegistrationInitiate") + } +} + +export async function userTotpRegistrationValidate( + totpCode: string, + totpRegistrationId: string, + token: string, +) { + const client = apollo(token).getClient() + try { + const { data } = await client.mutate({ + mutation: UserTotpRegistrationValidateDocument, + variables: { + input: { + totpRegistrationId, + totpCode, + }, + }, + }) + return data + } catch (error) { + console.error("Error executing mutation: UserTotpRegistrationValidate ==> ", error) + throw new Error("Error in UserTotpRegistrationValidate") + } +} + +export async function userTotpDelete(token: string) { + const client = apollo(token).getClient() + try { + const { data } = await client.mutate({ + mutation: UserTotpDeleteDocument, + }) + return data + } catch (error) { + console.error("Error executing UserTotpDelete mutation:", error) + throw new Error("Error in UserTotpDelete") + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac68a36856..fa7b6c06a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,8 +134,8 @@ importers: specifier: 10.4.16 version: 10.4.16(postcss@8.4.32) cypress: - specifier: ^13.3.0 - version: 13.3.1 + specifier: ^13.6.1 + version: 13.6.1 eslint: specifier: 8.55.0 version: 8.55.0 @@ -232,6 +232,9 @@ importers: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) + react-qrcode-logo: + specifier: ^2.9.0 + version: 2.9.0(react-dom@18.2.0)(react@18.2.0) zod: specifier: ^3.22.4 version: 3.22.4 @@ -14905,57 +14908,6 @@ packages: ally.js: 1.4.1 dev: false - /cypress@13.3.1: - resolution: {integrity: sha512-g4mJLZxYN+UAF2LMy3Znd4LBnUmS59Vynd81VES59RdW48Yt+QtR2cush3melOoVNz0PPbADpWr8DcUx6mif8Q==} - engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} - hasBin: true - requiresBuild: true - dependencies: - '@cypress/request': 3.0.1 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/node': 18.18.14 - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.4 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.4.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.3 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.10 - debug: 4.3.4(supports-color@8.1.1) - enquirer: 2.4.1 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-ci: 3.0.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.4.1) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.5.4 - supports-color: 8.1.1 - tmp: 0.2.1 - untildify: 4.0.0 - yauzl: 2.10.0 - dev: true - /cypress@13.6.1: resolution: {integrity: sha512-k1Wl5PQcA/4UoTffYKKaxA0FJKwg8yenYNYRzLt11CUR0Kln+h7Udne6mdU1cUIdXBDTVZWtmiUjzqGs7/pEpw==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} @@ -21993,7 +21945,7 @@ packages: '@opentelemetry/api': 1.7.0 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001561 + caniuse-lite: 1.0.30001568 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.2.0