Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle failed SIWE verification with nonce refetch #18

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/many-cobras-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@valorem-labs-inc/react-hooks": patch
---

use wagmi's QueryClient to force ConnectKit to refetch nonce after a `verify` post fails
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"@connectrpc/connect-query": "0.5.3",
"@connectrpc/connect-web": "^1.2.0",
"@tanstack/react-query": "^4.36.1",
"@valorem-labs-inc/sdk": "^0.0.11",
"@valorem-labs-inc/sdk": "^0.0.12-alpha.2",
"@wagmi/core": "^1.4.13",
"abitype": "0.8.7",
"connectkit": "^1.5.3",
Expand Down
11 changes: 4 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/context/SIWEProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SIWEProvider as Provider, type SIWESession } from 'connectkit';
import { type PropsWithChildren, useMemo } from 'react';
import { useAccount } from 'wagmi';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useAccount, useQueryClient } from 'wagmi';
import { useQuery } from '@tanstack/react-query';
import { getSIWEConfig } from '../utils/siwe';
import { usePromiseClient } from '../hooks/usePromiseClient';
import {
Expand Down Expand Up @@ -43,7 +43,7 @@ export function SIWEProvider({ onSignIn, onSignOut, children }: SIWEProps) {
const { address } = useAccount();
const logger = useLogger();
const authClient = usePromiseClient(Auth);
const queryClient = useQueryClient();
const wagmiQueryClient = useQueryClient();

// Queries for authentication, nonce, session, and sign-out.
const authenticateQuery = useQuery({
Expand Down Expand Up @@ -72,7 +72,7 @@ export function SIWEProvider({ onSignIn, onSignOut, children }: SIWEProps) {
const SIWEConfig = useMemo(() => {
return getSIWEConfig({
authClient,
queryClient,
wagmiQueryClient,
nonceQuery,
authenticateQuery,
sessionQuery,
Expand All @@ -83,7 +83,7 @@ export function SIWEProvider({ onSignIn, onSignOut, children }: SIWEProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps -- don't want to recompute when logger changes
}, [
authClient,
queryClient,
wagmiQueryClient,
nonceQuery,
authenticateQuery,
sessionQuery,
Expand Down
116 changes: 70 additions & 46 deletions src/utils/siwe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const createSIWEMessage: SIWEConfig['createMessage'] = ({
*/
interface GetSIWEConfigProps {
authClient: PromiseClient<typeof Auth>;
queryClient: QueryClient;
wagmiQueryClient: QueryClient;
nonceQuery: UseQueryResult<NonceText>;
authenticateQuery: UseQueryResult<H160>;
sessionQuery: UseQueryResult<SiweSession>;
Expand All @@ -51,7 +51,7 @@ interface GetSIWEConfigProps {
*/
export const getSIWEConfig = ({
authClient,
queryClient,
wagmiQueryClient,
nonceQuery,
authenticateQuery,
sessionQuery,
Expand All @@ -65,76 +65,100 @@ export const getSIWEConfig = ({

// Returns a promise which, upon resolution, returns the nonce.
async getNonce() {
logger.debug('Fetching nonce...');
logger.debug('SIWE: Fetching nonce...');
const { data } = await nonceQuery.refetch();
if (data?.nonce === undefined) throw new Error('Could not fetch nonce');
logger.debug(`Current nonce: ${data.nonce}`);
logger.debug(`SIWE: Current nonce: ${data.nonce}`);
return data.nonce;
},

// Returns a promise which, upon resolution, verifies the contents of the SIWE message.
async verifyMessage({ message, signature }) {
logger.debug('Verifying message...');
const res = await authClient.verify({
body: JSON.stringify({ message, signature }),
});
// verify address returned by Trade API matches current address
const verifiedAddress = fromH160ToAddress(res).toLowerCase();
logger.debug('Message verified successfully');
return verifiedAddress === address?.toLowerCase();
logger.debug('SIWE: Verifying message...');

let verified = false;
try {
const res = await authClient.verify({
body: JSON.stringify({ message, signature }),
});
// verify address returned by Trade API matches current address
const verifiedAddress = fromH160ToAddress(res).toLowerCase();
logger.info('SIWE: Signed in');
verified = verifiedAddress === address?.toLowerCase();
} catch (error) {
logger.error('SIWE: Error verifying message', { error });
}

if (!verified) {
logger.warn('SIWE: Fetching new nonce after failed verification...');
await wagmiQueryClient.refetchQueries(['ckSiweNonce']);
}

return verified;
},

// Returns a promise which, upon resolution and disconnect/reconnect of the
// client terminates the SIWE session.
async signOut() {
logger.debug('Signing out...');
logger.debug('SIWE: Signing out...');
try {
await signOutQuery.refetch();
logger.info('Signed out');
logger.info('SIWE: Signed out');
return true;
} catch (error) {
logger.error('Error signing out');
logger.error('SIWE: Error signing out', { error });
return false;
}
},

// Returns a promise which, upon await, gets details about the current session.
async getSession() {
logger.debug('Getting session...');
logger.debug('SIWE: Getting session...');
try {
// check auth endpoint to ensure session is valid
const { data: authData, error: authError } =
await authenticateQuery.refetch({});
if (authData === undefined || authError !== null) {
logger.debug('SIWE: Could not get auth data', { authError });
return null;
}
const authorizedAddress = fromH160ToAddress(authData);
if (authorizedAddress.toLowerCase() !== address?.toLowerCase()) {
logger.error(
'SIWE: Authorized address does not match connected address',
);
return null;
}
logger.debug(
'SIWE: Authorized address matches connected address. Now checking /session endpoint.',
);

// check auth endpoint to ensure session is valid
const { data: authData } = await authenticateQuery.refetch({});
if (authData === undefined) {
logger.warn('Could not get auth data');
return null;
}
const authorizedAddress = fromH160ToAddress(authData);
if (authorizedAddress.toLowerCase() !== address?.toLowerCase()) {
logger.error('Authorized address does not match connected address');
return null;
}
// get session data
const { data: sessionData, error: sessionError } =
await sessionQuery.refetch();
if (
!sessionData?.address ||
!sessionData.chainId ||
sessionError !== null
) {
logger.debug('SIWE: No session data found', { sessionError });
return null;
}
const sessionAddress = fromH160ToAddress(sessionData.address);
if (sessionAddress.toLowerCase() === address.toLowerCase()) {
logger.debug('SIWE: Session is valid');
return {
address: sessionAddress,
chainId: Number(fromH256(sessionData.chainId).toString()),
};
}

// get session data
const { data: sessionData } = await sessionQuery.refetch();
if (!sessionData?.address || !sessionData.chainId) {
logger.warn('No session data found');
logger.error('SIWE: Auth route does not match session data');
return null;
} catch (error) {
logger.error('SIWE: Error getting session', { error });
return null;
}
const sessionAddress = fromH160ToAddress(sessionData.address);
if (sessionAddress.toLowerCase() === address.toLowerCase()) {
logger.debug('Session is valid');
queryClient.setQueryData(
['valorem.trade.v1.Auth', 'signed-out'],
false,
);
return {
address: sessionAddress,
chainId: Number(fromH256(sessionData.chainId).toString()),
};
}

logger.error('Auth route does not match session data');
return null;
},
};
return config;
Expand Down