Skip to content

Commit

Permalink
Feat/close #43 (#44)
Browse files Browse the repository at this point in the history
* define auth api and use in attestation

* add callback

* fix callback page issue

* update else state

* setup attestation

* add AuthenticationWrapper

* fix custom auth with wallet

* add attest integration

* remove connectButton in app bar

* update import

* update import

* update import

* add protect route

* update constant.d.ts

* update ts.config

* upd
  • Loading branch information
mehdi-torabiv authored Aug 14, 2024
1 parent 11613f2 commit 1076eba
Show file tree
Hide file tree
Showing 17 changed files with 1,542 additions and 143 deletions.
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';
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) : [];
};

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

0 comments on commit 1076eba

Please sign in to comment.