Skip to content

Commit

Permalink
implement auth
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-torabiv committed Aug 6, 2024
1 parent 20a8db3 commit 7cfc293
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 29 deletions.
96 changes: 76 additions & 20 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useState } from 'react';
import './App.css';
import '@rainbow-me/rainbowkit/styles.css';
import { RouterProvider } from 'react-router-dom';
Expand All @@ -9,11 +9,16 @@ import {
getDefaultConfig,
RainbowKitProvider,
RainbowKitAuthenticationProvider,
createAuthenticationAdapter,
AuthenticationStatus,
} from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { sepolia } from 'viem/chains';
import { createSiweMessage } from 'viem/siwe';
import theme from './libs/theme';
import { router } from './router';
import { api } from './api';
import { AuthProvider, useAuth } from './context/authContext';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -25,35 +30,86 @@ const queryClient = new QueryClient({
},
});

const config = getDefaultConfig({
appName: 'RainbowKit demo',
projectId: '1cf030f3b91e339bc4e6ecf71a694a88',
chains: [sepolia],
});
const AuthenticationWrapper: React.FC = () => {
const { setAuthInfo, signOut } = useAuth();
const [authStatus, setAuthStatus] =
useState<AuthenticationStatus>('unauthenticated');

const App: React.FC = () => {
const authStatus = useAuthStatus();
const authenticationAdapter = createAuthenticationAdapter({
getNonce: async () => {
const { data } = await api.get('auth/siwe/nonce');
return data.nonce;
},
createMessage: ({ nonce, address, chainId }) => {
return createSiweMessage({
address,

Check failure on line 45 in src/App.tsx

View workflow job for this annotation

GitHub Actions / test/node 18/ubuntu-latest

Type 'string' is not assignable to type '`0x${string}`'.

Check failure on line 45 in src/App.tsx

View workflow job for this annotation

GitHub Actions / test/node 17/ubuntu-latest

Type 'string' is not assignable to type '`0x${string}`'.

Check failure on line 45 in src/App.tsx

View workflow job for this annotation

GitHub Actions / test/node 18/ubuntu-latest

Type 'string' is not assignable to type '`0x${string}`'.

Check failure on line 45 in src/App.tsx

View workflow job for this annotation

GitHub Actions / test/node 17/ubuntu-latest

Type 'string' is not assignable to type '`0x${string}`'.
chainId,
domain: window.location.host,
nonce,
uri: window.location.origin,
version: '1',
statement: 'Sign in with Ethereum to the app.',
});
},
getMessageBody: ({ message }) => message,
verify: async ({ message, signature }) => {
const { data } = await api.post('auth/siwe/verify', {
message,
signature,
chainId: 11155111,
});

if (!data) {
throw new Error('Verification response data is empty');
}

if (data?.jwt) {
setAuthStatus('authenticated');
setAuthInfo(data.jwt);
window.location.replace('/');
} else {
setAuthStatus('unauthenticated');
}

return data;
},
signOut: async () => {
await api.post('auth/logout');
setAuthStatus('unauthenticated');
signOut();
},
});

useEffect(() => {
console.log('Auth status changed:', authStatus);
}, [authStatus]);
const config = getDefaultConfig({
appName: 'RainbowKit demo',
projectId: '1cf030f3b91e339bc4e6ecf71a694a88',
chains: [sepolia],
});

return (
<WagmiProvider config={config}>
<RainbowKitAuthenticationProvider
adapter={authenticationAdapter}
status={authStatus}
>
<RainbowKitProvider>
<RouterProvider router={router} />
</RainbowKitProvider>
</RainbowKitAuthenticationProvider>
</WagmiProvider>
);
};

const App: React.FC = () => {
return (
<AuthProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<CssBaseline />
<RainbowKitAuthenticationProvider
adapter={authAdapter}
status={authStatus}
>
<RainbowKitProvider>
<RouterProvider router={router} />
</RainbowKitProvider>
</RainbowKitAuthenticationProvider>
<AuthenticationWrapper />
</ThemeProvider>
</QueryClientProvider>
</WagmiProvider>
</AuthProvider>
);
};

Expand Down
37 changes: 37 additions & 0 deletions src/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Navigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Backdrop from '@mui/material/Backdrop';
import { useAuth } from './context/authContext';

const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
const { isAuthenticated } = useAuth();
const [loading, setLoading] = useState(true);

useEffect(() => {
const checkAuthStatus = async () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 1000);
};

checkAuthStatus();
}, [isAuthenticated]);

if (loading) {
return (
<Backdrop open={loading} style={{ zIndex: 1201 }}>
<CircularProgress color="inherit" />
</Backdrop>
);
}

if (!isAuthenticated) {
return <Navigate to="/auth/login" replace />;
}

return children;
};

export default ProtectedRoute;
23 changes: 22 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/api/index.ts
import axios from 'axios';

const baseURL = import.meta.env.VITE_API_BASE_URL;
Expand All @@ -15,3 +14,25 @@ export const api = axios.create({
'Content-Type': 'application/json',
},
});

api.interceptors.request.use(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(config: any) => {
const token = localStorage.getItem('OCI_TOKEN');
if (token) {
return {
...config,
headers: {
...config.headers,
Authorization: `Bearer ${token}`,
},
};
}
return config;
},
(error) => {
return Promise.reject(error);
}
);

export default api;
62 changes: 62 additions & 0 deletions src/context/authContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
createContext,
useState,
useContext,
ReactNode,
useEffect,
useMemo,
} from 'react';

interface AuthContextType {
isAuthenticated: boolean;
jwt: string | null;
setAuthInfo: (jwt: string) => void;
signOut: () => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [jwt, setJwt] = useState<string | null>(null);

useEffect(() => {
const token = localStorage.getItem('OCI_TOKEN');
if (token) {
setJwt(token);
setIsAuthenticated(true);
}
}, []);

const setAuthInfo = (token: string) => {
setJwt(token);
setIsAuthenticated(true);
localStorage.setItem('OCI_TOKEN', token);
};

const signOut = () => {
setJwt(null);
setIsAuthenticated(false);
localStorage.removeItem('OCI_TOKEN');
};

const value = useMemo(
() => ({
isAuthenticated,
jwt,
setAuthInfo,
signOut,
}),
[isAuthenticated, jwt]
);

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
5 changes: 1 addition & 4 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { AuthProvider } from './context/authContext';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
<App />
</React.StrictMode>
);
25 changes: 21 additions & 4 deletions src/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Permissions from '../pages/Permissions';
import Attestation from '../pages/Identifiers/Attestation';

import DefaultLayout from '../layouts/DefaultLayout';
import ProtectedRoute from '../ProtectedRoute';

export const router = createBrowserRouter([
{
Expand All @@ -19,20 +20,36 @@ export const router = createBrowserRouter([
children: [
{
path: '/',
element: <Dashboard />,
element: (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
),
index: true,
},
{
path: '/identifiers',
element: <Identifiers />,
element: (
<ProtectedRoute>
<Identifiers />
</ProtectedRoute>
),
},
{
path: '/attestation',
element: <Attestation />,
element: (
<ProtectedRoute>
<Attestation />
</ProtectedRoute>
),
},
{
path: '/permissions',
element: <Permissions />,
element: (
<ProtectedRoute>
<Permissions />
</ProtectedRoute>
),
},
],
},
Expand Down

0 comments on commit 7cfc293

Please sign in to comment.