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

Feat/close #43 #44

Merged
merged 16 commits into from
Aug 14, 2024
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ dist-ssr

coverage

.env
.env

/src/contracts/*
26 changes: 18 additions & 8 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
"@mui/material": "^5.16.0",
"@rainbow-me/rainbowkit": "^2.1.3",
"@react-icons/all-files": "^4.1.0",
"@tanstack/react-query": "^5.51.16",
"@tanstack/react-query": "^5.51.21",
"@tanstack/react-query-devtools": "^5.50.1",
"axios": "^1.7.2",
"jwt-decode": "^4.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.1",
Expand Down
136 changes: 90 additions & 46 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import './App.css';
import '@rainbow-me/rainbowkit/styles.css';
import { RouterProvider } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
import { WagmiProvider } from 'wagmi';
import {
AuthenticationStatus,
createAuthenticationAdapter,
getDefaultConfig,
RainbowKitProvider,
RainbowKitAuthenticationProvider,
createAuthenticationAdapter,
AuthenticationStatus,
RainbowKitProvider,
} from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { sepolia } from 'viem/chains';
import { sepolia } from 'wagmi/chains';
import { getAddress } from 'viem';
import { createSiweMessage } from 'viem/siwe';
import Login from './pages/Auth/Login';
import theme from './libs/theme';
import { router } from './router';
import { api } from './api';
import { AuthProvider, useAuth } from './context/authContext';
import { api } from './services/api';

import DefaultLayout from './layouts/DefaultLayout';

import Dashboard from './pages/Dashboard';
import Identifiers from './pages/Identifiers';
import Permissions from './pages/Permissions';
import Attestation from './pages/Identifiers/Attestation';
import Callback from './pages/Callback';
import ProtectedRoute from './ProtectedRoute';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -31,8 +39,14 @@ const queryClient = new QueryClient({
},
});

const AuthenticationWrapper: React.FC = () => {
const { setAuthInfo, signOut } = useAuth();
const config = getDefaultConfig({
appName: 'RainbowKit demo',
projectId: '1cf030f3b91e339bc4e6ecf71a694a88',
chains: [sepolia],
ssr: false,
});

const App: React.FC = () => {
const [authStatus, setAuthStatus] =
useState<AuthenticationStatus>('unauthenticated');

Expand Down Expand Up @@ -65,51 +79,81 @@ const AuthenticationWrapper: React.FC = () => {
}

if (data?.jwt) {
localStorage.setItem('OCI_TOKEN', data.jwt);
setAuthStatus('authenticated');
setAuthInfo(data.jwt);
window.location.replace('/');
} else {
setAuthStatus('unauthenticated');
return true;
}

return data;
return false;
},
signOut: async () => {
setAuthStatus('unauthenticated');
signOut();
localStorage.removeItem('OCI_TOKEN');
},
});

const config = getDefaultConfig({
appName: 'RainbowKit demo',
projectId: '1cf030f3b91e339bc4e6ecf71a694a88',
chains: [sepolia],
});
useEffect(() => {
const checkStoredToken = () => {
const OCI_TOKEN = localStorage.getItem('OCI_TOKEN');
if (OCI_TOKEN) {
setAuthStatus('authenticated');
} else {
setAuthStatus('unauthenticated');
}
};

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

useEffect(() => {
console.log('authStatus', authStatus);
}, [authStatus]);

const App: React.FC = () => {
return (
<AuthProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<CssBaseline />
<AuthenticationWrapper />
</ThemeProvider>
</QueryClientProvider>
</AuthProvider>
<BrowserRouter>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitAuthenticationProvider
adapter={authenticationAdapter}
status={authStatus}
>
<RainbowKitProvider initialChain={sepolia}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Routes>
<Route
path="/auth/login"
element={
authStatus === 'authenticated' ? (
<Navigate to="/" replace />
) : (
<Login />
)
}
/>
<Route
element={
<ProtectedRoute>
<DefaultLayout />
</ProtectedRoute>
}
>
<Route path="/" element={<Dashboard />} />
<Route path="/identifiers" element={<Identifiers />} />
<Route
path="identifiers/:provider/attestation"
element={<Attestation />}
/>
<Route path="/permissions" element={<Permissions />} />
</Route>
<Route path="/callback" element={<Callback />} />
<Route path="*" element={<div>Not found</div>} />
</Routes>
</ThemeProvider>
</RainbowKitProvider>
</RainbowKitAuthenticationProvider>
</QueryClientProvider>
</WagmiProvider>
</BrowserRouter>
);
};

Expand Down
24 changes: 14 additions & 10 deletions src/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { Navigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { Navigate } from 'react-router-dom';
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);
interface ProtectedRouteProps {
children: JSX.Element;
}

const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const [loading, setLoading] = useState<boolean>(true);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);

useEffect(() => {
const checkAuthStatus = async () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 1000);
const token = localStorage.getItem('OCI_TOKEN');
if (token) {
setIsAuthenticated(true);
}
setLoading(false);
};

checkAuthStatus();
}, [isAuthenticated]);
}, []);

if (loading) {
return (
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ export interface MenuItem {
};
children?: MenuItem[];
}

export interface PlatformAuthenticationParams {
platformType: 'DISCORD' | 'GOOGLE';
}
80 changes: 80 additions & 0 deletions src/pages/Callback/Callback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { jwtDecode } from 'jwt-decode';
mehdi-torabiv marked this conversation as resolved.
Show resolved Hide resolved
import { Backdrop, CircularProgress } from '@mui/material';

interface DecodedJwt {
exp: number;
iat: number;
provider: string;
sub: string;
}

const useQueryParams = () => {
const { search } = useLocation();
return new URLSearchParams(search);
};

const isJwt = (token: string): boolean => {
const jwtRegex = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/;
return jwtRegex.test(token);
};

const getStoredTokens = (): Array<{
token: string;
exp: number;
provider: string;
}> => {
const storedTokens = localStorage.getItem('OCI_PROVIDER_TOKENS');
return storedTokens ? JSON.parse(storedTokens) : [];
};
mehdi-torabiv marked this conversation as resolved.
Show resolved Hide resolved

const storeTokens = (
tokens: Array<{ token: string; exp: number; provider: string }>
) => {
localStorage.setItem('OCI_PROVIDER_TOKENS', JSON.stringify(tokens));
};

export function Callback() {
const queryParams = useQueryParams();
const jwt = queryParams.get('jwt');
const navigate = useNavigate();

useEffect(() => {
if (jwt && isJwt(jwt)) {
try {
const decodedJwt: DecodedJwt = jwtDecode(jwt);
const { provider, exp } = decodedJwt;

// Retrieve existing tokens from localStorage
const storedTokens = getStoredTokens();

// Remove any existing token for the current provider
const updatedTokens = storedTokens.filter(
(token) => token.provider !== provider
);

// Add the new token
updatedTokens.push({ token: jwt, exp, provider });

// Store updated tokens back to localStorage
storeTokens(updatedTokens);

// Redirect to the current JWT provider route
navigate(`/identifiers/${provider}/attestation?jwt=${jwt}`);
} catch (error) {
console.error('Invalid JWT:', error);
}
} else {
navigate('/identifiers');
}
}, [jwt, navigate]);

return (
<Backdrop open>
<CircularProgress color="inherit" />
</Backdrop>
);
}

export default Callback;
3 changes: 3 additions & 0 deletions src/pages/Callback/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Callback } from './Callback';

export default Callback;
Loading
Loading