Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ jobs:
echo VITE_GRAPHQL_ENDPOINT=${{ secrets.STAGING_GRAPHQL_ENDPOINT }} >> .env
echo VITE_ASL_LEXICON_ID=${{ secrets.STAGING_ASL_LEXICON_ID }} >> .env
echo VITE_NAME=${{ secrets.STAGING_ASL_LEX_NAME }} >> .env
echo VITE_AUTH_API_KEY=${{ secrets.STAGING_AUTH_API_KEY }} >> .env
echo VITE_AUTH_DOMAIN=${{ secrets.STAGING_AUTH_DOMAIN }} >> .env

- name: NPM Install
run: npm install
Expand Down
1,576 changes: 1,551 additions & 25 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.5.0/firebase-ui-auth.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
* {
Expand All @@ -18,4 +19,4 @@
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
</html>
6 changes: 3 additions & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@
"ajv-errors": "^3.0.0",
"axios": "^1.6.2",
"esbuild": "^0.19.0",
"firebaseui": "^6.1.0",
"graphql": "^16.8.0",
"injection-js": "^2.4.0",
"i18next": "^23.8.2",
"i18next-browser-languagedetector": "^7.2.0",
"i18next-http-backend": "^2.4.3",
"react-i18next": "^14.0.1",
"injection-js": "^2.4.0",
"jwt-decode": "^3.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^14.0.1",
"react-router-dom": "^6.12.1",
"styled-components": "^5.3.10"

},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.0",
Expand Down
6 changes: 0 additions & 6 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@ import { ProjectAccess } from './pages/datasets/ProjectAccess';
import { ProjectUserPermissions } from './pages/projects/ProjectUserPermissions';
import { StudyUserPermissions } from './pages/studies/UserPermissions';
import { DownloadTags } from './pages/studies/DownloadTags';
import { LoginPage } from './pages/LoginPage';
import { DatasetControls } from './pages/datasets/DatasetControls';
import { AuthCallback } from './pages/AuthCallback';
import { AuthProvider, useAuth, AUTH_TOKEN_STR } from './context/Auth.context';
import { AdminGuard } from './guards/AdminGuard';
import { LogoutPage } from './pages/LogoutPage';
import { CssBaseline, Box, styled } from '@mui/material';
import { FC, ReactNode, useState } from 'react';
import { SideBar } from './components/SideBar.component';
Expand Down Expand Up @@ -119,8 +116,6 @@ const MyRoutes: FC = () => {
return (
<Routes>
<Route path={'/'} element={<HomePage />} />
<Route path={'/callback'} element={<AuthCallback />} />
<Route path={'/loginpage'} element={<LoginPage />} />
<Route element={<AdminGuard />}>
<Route path={'/project/new'} element={<NewProject />} />
<Route path={'/project/controls'} element={<ProjectControl />} />
Expand All @@ -135,7 +130,6 @@ const MyRoutes: FC = () => {
<Route path={'/dataset/projectaccess'} element={<ProjectAccess />} />
<Route path={'/contribute/landing'} element={<ContributeLanding />} />
<Route path={'/contribute/tagging'} element={<TaggingInterface />} />
<Route path={'/logoutpage'} element={<LogoutPage />} />
</Route>
</Routes>
);
Expand Down
131 changes: 76 additions & 55 deletions packages/client/src/context/Auth.context.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import { createContext, FC, useContext, useEffect, useState, ReactNode } from 'react';
import jwt_decode from 'jwt-decode';
import { useNavigate } from 'react-router-dom';
import * as firebaseui from 'firebaseui';
import * as firebase from '@firebase/app';
import * as firebaseauth from '@firebase/auth';

const firebaseConfig = {
apiKey: import.meta.env.VITE_AUTH_API_KEY,
authDomain: import.meta.env.VITE_AUTH_DOMAIN
};

export const AUTH_TOKEN_STR = 'token';

export interface DecodedToken {
id: string;
projectId: string;
role: number;
aud: string;
auth_time: number;
email: string;
email_verified: boolean;
exp: number;
firebase: {
identities: {
email: string[];
email_verified: boolean;
};
sign_in_provider: string;
user_id: string;
};
iat: number;
iss: string;
sub: string;
user_id: string;
}

export interface AuthContextProps {
authenticated: boolean;
setAuthenticated: (authenticated: boolean) => void;
token: string | null;
decodedToken: DecodedToken | null;
setToken: (token: string) => void;
login: (token: string) => void;
logout: () => void;
}

Expand All @@ -28,83 +45,87 @@ export interface AuthProviderProps {
}

export const AuthProvider: FC<AuthProviderProps> = ({ children }) => {
const [authenticated, setAuthenticated] = useState<boolean>(false);
const [token, setToken] = useState<string | null>(null);
const [token, setToken] = useState<string | null>(localStorage.getItem(AUTH_TOKEN_STR));
const [authenticated, setAuthenticated] = useState<boolean>(true);
const [decodedToken, setDecodedToken] = useState<DecodedToken | null>(null);
const navigate = useNavigate();

const setUnautheticated = () => {
clearToken();
setAuthenticated(false);
const handleUnauthenticated = () => {
// Clear the token and authenticated state
setToken(null);
setDecodedToken(null);
setAuthenticated(false);
localStorage.removeItem(AUTH_TOKEN_STR);
};

const makeAuthenticated = (token: string, tokenPayload: DecodedToken) => {
setAuthenticated(true);
const handleAuthenticated = (token: string) => {
setToken(token);
setDecodedToken(tokenPayload);
saveToken(token);
};

const logout = () => {
setUnautheticated();
navigate('/loginpage');
};
setAuthenticated(true);
localStorage.setItem(AUTH_TOKEN_STR, token);

const login = (token: string) => {
makeAuthenticated(token, jwt_decode(token));
navigate('/');
const decodedToken = jwt_decode<DecodedToken>(token);
setDecodedToken(decodedToken);
};

// Handle loading the login UI
useEffect(() => {
const token = restoreToken();
// Check local storage for token
const token = localStorage.getItem(AUTH_TOKEN_STR);

// If not token present, redirect to login
// If no token, need to login
if (!token) {
setUnautheticated();
navigate('/loginpage');
handleUnauthenticated();
return;
}

// Decode the current token payload
const decodedToken: DecodedToken = jwt_decode(token);
const currentTime = new Date().getTime() / 1000;
// Decode the token
const decodedToken = jwt_decode<DecodedToken>(token);

// Handle expired token
if (currentTime > decodedToken.exp) {
setUnautheticated();
navigate('/loginpage');
// If token is expired, need to login
if (decodedToken.exp * 1000 < Date.now()) {
handleUnauthenticated();
return;
}

// User is authenticated with presoent token
makeAuthenticated(token, decodedToken);
// Otherwise, can set the token and authenticated state
handleAuthenticated(token);
}, []);

useEffect(() => {
if (token) {
makeAuthenticated(token, jwt_decode(token));
}
}, [token]);
const logout = () => {
handleUnauthenticated();
};

return (
<AuthContext.Provider value={{ token, decodedToken, setToken, authenticated, setAuthenticated, logout, login }}>
{children}
<AuthContext.Provider value={{ token, authenticated, decodedToken, logout }}>
{!authenticated && <FirebaseLoginWrapper setToken={handleAuthenticated} />}
{authenticated && children}
</AuthContext.Provider>
);
};

const saveToken = (token: string) => {
localStorage.setItem(AUTH_TOKEN_STR, token);
};
interface FirebaseLoginWrapperProps {
setToken: (token: string) => void;
}

const restoreToken = (): string | null => {
return localStorage.getItem(AUTH_TOKEN_STR);
};
const FirebaseLoginWrapper: FC<FirebaseLoginWrapperProps> = ({ setToken }) => {
firebase.initializeApp(firebaseConfig);
const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseauth.getAuth());

const signInSuccess = async (authResult: any) => {
setToken(await authResult.user.getIdToken());
};

useEffect(() => {
ui.start('#firebaseui-auth-container', {
callbacks: {
signInSuccessWithAuthResult: (authResult, _redirectUrl) => {
signInSuccess(authResult);
return true;
}
},
signInOptions: [firebaseauth.GoogleAuthProvider.PROVIDER_ID, firebaseauth.EmailAuthProvider.PROVIDER_ID]
});
}, []);

const clearToken = (): void => {
localStorage.removeItem(AUTH_TOKEN_STR);
return <div id="firebaseui-auth-container" />;
};

export const useAuth = () => useContext(AuthContext);
30 changes: 0 additions & 30 deletions packages/client/src/pages/AuthCallback.tsx

This file was deleted.

88 changes: 0 additions & 88 deletions packages/client/src/pages/LoginPage.tsx

This file was deleted.

Loading