Implement authentication with SIWE (#99)

* Create web3 modal provider with SIWE

* Add auth router to handle SIWE authentication

* Use axios instance to make request

* Add button for SIWE authentication

* Add changes to access session in web-app GQL requests

* Add auth check in GQL context and load/create user

* Use authenticated user from context

* Redirect to sign in page if unauthenticated and logout button

* Change sign-in route to login

* Get project domain from config file

* Set user ethAddress column as unique

* Use formatted user name

* Get session secret and origin url from config file

* Add unique constraint for eth address

* Get secure and samesite from origin url

* Get wallet connect id and backend url from env file

* Format user email in member tab panel

* Add backend config isProduction to set trust proxy

* Use only one server url config

* Add tool tip for displaying email

* Add trustProxy and domain in server.session config

* Add SERVER_GQL_PATH constant in frontend

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
2024-02-22 17:26:26 +05:30
committed by GitHub
co-authored by neeraj
parent a846531e43
commit ef0eac8293
33 changed files with 2970 additions and 177 deletions
@@ -0,0 +1,125 @@
import React, { ReactNode } from 'react';
import { SiweMessage } from 'siwe';
import { WagmiProvider } from 'wagmi';
import { arbitrum, mainnet } from 'wagmi/chains';
import axios from 'axios';
import { createWeb3Modal } from '@web3modal/wagmi/react';
import { defaultWagmiConfig } from '@web3modal/wagmi/react/config';
import { createSIWEConfig } from '@web3modal/siwe';
import type {
SIWECreateMessageArgs,
SIWEVerifyMessageArgs,
} from '@web3modal/core';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
withCredentials: true,
});
const metadata = {
name: 'Web3Modal',
description: 'Snowball Web3Modal',
url: window.location.origin,
icons: ['https://avatars.githubusercontent.com/u/37784886'],
};
const chains = [mainnet, arbitrum] as const;
const config = defaultWagmiConfig({
chains,
projectId: process.env.REACT_APP_WALLET_CONNECT_ID,
metadata,
});
const siweConfig = createSIWEConfig({
createMessage: ({ nonce, address, chainId }: SIWECreateMessageArgs) =>
new SiweMessage({
version: '1',
domain: window.location.host,
uri: window.location.origin,
address,
chainId,
nonce,
// Human-readable ASCII assertion that the user will sign, and it must not contain `\n`.
statement: 'Sign in With Ethereum.',
}).prepareMessage(),
getNonce: async () => {
const nonce = (await axiosInstance.get('/auth/nonce')).data;
if (!nonce) {
throw new Error('Failed to get nonce!');
}
return nonce;
},
getSession: async () => {
try {
const session = (await axiosInstance.get('/auth/session')).data;
const { address, chainId } = session;
return { address, chainId };
} catch (err) {
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
throw new Error('Failed to get session!');
}
},
verifyMessage: async ({ message, signature }: SIWEVerifyMessageArgs) => {
try {
const { success } = (
await axiosInstance.post('/auth/validate', {
message,
signature,
})
).data;
return success;
} catch (error) {
return false;
}
},
signOut: async () => {
try {
const { success } = (await axiosInstance.post('/auth/logout')).data;
return success;
} catch (error) {
return false;
}
},
onSignOut: () => {
window.location.href = '/login';
},
onSignIn: () => {
window.location.href = '/';
},
});
if (!process.env.REACT_APP_WALLET_CONNECT_ID) {
throw new Error('Error: REACT_APP_WALLET_CONNECT_ID env config is not set');
}
createWeb3Modal({
siweConfig,
wagmiConfig: config,
projectId: process.env.REACT_APP_WALLET_CONNECT_ID,
});
export default function Web3ModalProvider({
children,
}: {
children: ReactNode;
}) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</WagmiProvider>
);
}