Skip to content

Commit 1d1fbf5

Browse files
authored
Merge pull request #18 from valorem-labs-inc/nickadamson/val-1884-nonce-mismatch-preventing-sign-in
handle failed SIWE verification with nonce refetch
2 parents d94bae4 + 52c202e commit 1d1fbf5

File tree

5 files changed

+85
-59
lines changed

5 files changed

+85
-59
lines changed

.changeset/many-cobras-approve.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@valorem-labs-inc/react-hooks": patch
3+
---
4+
5+
use wagmi's QueryClient to force ConnectKit to refetch nonce after a `verify` post fails

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
"@connectrpc/connect-query": "0.5.3",
8989
"@connectrpc/connect-web": "^1.2.0",
9090
"@tanstack/react-query": "^4.36.1",
91-
"@valorem-labs-inc/sdk": "^0.0.11",
91+
"@valorem-labs-inc/sdk": "^0.0.12-alpha.2",
9292
"@wagmi/core": "^1.4.13",
9393
"abitype": "0.8.7",
9494
"connectkit": "^1.5.3",

pnpm-lock.yaml

Lines changed: 4 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/context/SIWEProvider.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { SIWEProvider as Provider, type SIWESession } from 'connectkit';
22
import { type PropsWithChildren, useMemo } from 'react';
3-
import { useAccount } from 'wagmi';
4-
import { useQuery, useQueryClient } from '@tanstack/react-query';
3+
import { useAccount, useQueryClient } from 'wagmi';
4+
import { useQuery } from '@tanstack/react-query';
55
import { getSIWEConfig } from '../utils/siwe';
66
import { usePromiseClient } from '../hooks/usePromiseClient';
77
import {
@@ -43,7 +43,7 @@ export function SIWEProvider({ onSignIn, onSignOut, children }: SIWEProps) {
4343
const { address } = useAccount();
4444
const logger = useLogger();
4545
const authClient = usePromiseClient(Auth);
46-
const queryClient = useQueryClient();
46+
const wagmiQueryClient = useQueryClient();
4747

4848
// Queries for authentication, nonce, session, and sign-out.
4949
const authenticateQuery = useQuery({
@@ -72,7 +72,7 @@ export function SIWEProvider({ onSignIn, onSignOut, children }: SIWEProps) {
7272
const SIWEConfig = useMemo(() => {
7373
return getSIWEConfig({
7474
authClient,
75-
queryClient,
75+
wagmiQueryClient,
7676
nonceQuery,
7777
authenticateQuery,
7878
sessionQuery,
@@ -83,7 +83,7 @@ export function SIWEProvider({ onSignIn, onSignOut, children }: SIWEProps) {
8383
// eslint-disable-next-line react-hooks/exhaustive-deps -- don't want to recompute when logger changes
8484
}, [
8585
authClient,
86-
queryClient,
86+
wagmiQueryClient,
8787
nonceQuery,
8888
authenticateQuery,
8989
sessionQuery,

src/utils/siwe.ts

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const createSIWEMessage: SIWEConfig['createMessage'] = ({
3232
*/
3333
interface GetSIWEConfigProps {
3434
authClient: PromiseClient<typeof Auth>;
35-
queryClient: QueryClient;
35+
wagmiQueryClient: QueryClient;
3636
nonceQuery: UseQueryResult<NonceText>;
3737
authenticateQuery: UseQueryResult<H160>;
3838
sessionQuery: UseQueryResult<SiweSession>;
@@ -51,7 +51,7 @@ interface GetSIWEConfigProps {
5151
*/
5252
export const getSIWEConfig = ({
5353
authClient,
54-
queryClient,
54+
wagmiQueryClient,
5555
nonceQuery,
5656
authenticateQuery,
5757
sessionQuery,
@@ -65,76 +65,100 @@ export const getSIWEConfig = ({
6565

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

7575
// Returns a promise which, upon resolution, verifies the contents of the SIWE message.
7676
async verifyMessage({ message, signature }) {
77-
logger.debug('Verifying message...');
78-
const res = await authClient.verify({
79-
body: JSON.stringify({ message, signature }),
80-
});
81-
// verify address returned by Trade API matches current address
82-
const verifiedAddress = fromH160ToAddress(res).toLowerCase();
83-
logger.debug('Message verified successfully');
84-
return verifiedAddress === address?.toLowerCase();
77+
logger.debug('SIWE: Verifying message...');
78+
79+
let verified = false;
80+
try {
81+
const res = await authClient.verify({
82+
body: JSON.stringify({ message, signature }),
83+
});
84+
// verify address returned by Trade API matches current address
85+
const verifiedAddress = fromH160ToAddress(res).toLowerCase();
86+
logger.info('SIWE: Signed in');
87+
verified = verifiedAddress === address?.toLowerCase();
88+
} catch (error) {
89+
logger.error('SIWE: Error verifying message', { error });
90+
}
91+
92+
if (!verified) {
93+
logger.warn('SIWE: Fetching new nonce after failed verification...');
94+
await wagmiQueryClient.refetchQueries(['ckSiweNonce']);
95+
}
96+
97+
return verified;
8598
},
8699

87100
// Returns a promise which, upon resolution and disconnect/reconnect of the
88101
// client terminates the SIWE session.
89102
async signOut() {
90-
logger.debug('Signing out...');
103+
logger.debug('SIWE: Signing out...');
91104
try {
92105
await signOutQuery.refetch();
93-
logger.info('Signed out');
106+
logger.info('SIWE: Signed out');
94107
return true;
95108
} catch (error) {
96-
logger.error('Error signing out');
109+
logger.error('SIWE: Error signing out', { error });
97110
return false;
98111
}
99112
},
100113

101114
// Returns a promise which, upon await, gets details about the current session.
102115
async getSession() {
103-
logger.debug('Getting session...');
116+
logger.debug('SIWE: Getting session...');
117+
try {
118+
// check auth endpoint to ensure session is valid
119+
const { data: authData, error: authError } =
120+
await authenticateQuery.refetch({});
121+
if (authData === undefined || authError !== null) {
122+
logger.debug('SIWE: Could not get auth data', { authError });
123+
return null;
124+
}
125+
const authorizedAddress = fromH160ToAddress(authData);
126+
if (authorizedAddress.toLowerCase() !== address?.toLowerCase()) {
127+
logger.error(
128+
'SIWE: Authorized address does not match connected address',
129+
);
130+
return null;
131+
}
132+
logger.debug(
133+
'SIWE: Authorized address matches connected address. Now checking /session endpoint.',
134+
);
104135

105-
// check auth endpoint to ensure session is valid
106-
const { data: authData } = await authenticateQuery.refetch({});
107-
if (authData === undefined) {
108-
logger.warn('Could not get auth data');
109-
return null;
110-
}
111-
const authorizedAddress = fromH160ToAddress(authData);
112-
if (authorizedAddress.toLowerCase() !== address?.toLowerCase()) {
113-
logger.error('Authorized address does not match connected address');
114-
return null;
115-
}
136+
// get session data
137+
const { data: sessionData, error: sessionError } =
138+
await sessionQuery.refetch();
139+
if (
140+
!sessionData?.address ||
141+
!sessionData.chainId ||
142+
sessionError !== null
143+
) {
144+
logger.debug('SIWE: No session data found', { sessionError });
145+
return null;
146+
}
147+
const sessionAddress = fromH160ToAddress(sessionData.address);
148+
if (sessionAddress.toLowerCase() === address.toLowerCase()) {
149+
logger.debug('SIWE: Session is valid');
150+
return {
151+
address: sessionAddress,
152+
chainId: Number(fromH256(sessionData.chainId).toString()),
153+
};
154+
}
116155

117-
// get session data
118-
const { data: sessionData } = await sessionQuery.refetch();
119-
if (!sessionData?.address || !sessionData.chainId) {
120-
logger.warn('No session data found');
156+
logger.error('SIWE: Auth route does not match session data');
157+
return null;
158+
} catch (error) {
159+
logger.error('SIWE: Error getting session', { error });
121160
return null;
122161
}
123-
const sessionAddress = fromH160ToAddress(sessionData.address);
124-
if (sessionAddress.toLowerCase() === address.toLowerCase()) {
125-
logger.debug('Session is valid');
126-
queryClient.setQueryData(
127-
['valorem.trade.v1.Auth', 'signed-out'],
128-
false,
129-
);
130-
return {
131-
address: sessionAddress,
132-
chainId: Number(fromH256(sessionData.chainId).toString()),
133-
};
134-
}
135-
136-
logger.error('Auth route does not match session data');
137-
return null;
138162
},
139163
};
140164
return config;

0 commit comments

Comments
 (0)