diff --git a/cypress/component/SignIn/google_is.spec.js b/cypress/component/SignIn/google_is.spec.js deleted file mode 100644 index a6d589ea4..000000000 --- a/cypress/component/SignIn/google_is.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint-disable no-undef */ - -import {GoogleIS} from '../../../src/libs/googleIS'; - -describe('Google IS Utility', function () { - it('GoogleIS.initTokenClient works with a valid client id', function () { - cy.viewport(600, 300); - cy.readFile('config/alpha.json').then((config) => { - const clientId = config.clientId; - GoogleIS.initTokenClient( - clientId, - () => {}, - () => {}) - .then(() => { - expect(GoogleIS.client).to.not.equal(null); - }); - }); - }); - - it('GoogleIS.requestAccessToken: Cannot be tested as it would trigger an OAuth flow', function () { - expect(true).to.equal(true); - }); - - it('GoogleIS.revokeAccessToken does not fail with empty client or access token', function () { - cy.viewport(600, 300); - cy.readFile('config/alpha.json').then((config) => { - const clientId = config.clientId; - GoogleIS.initTokenClient( - clientId, - () => {}, - () => {}) - .then(() => { - expect(GoogleIS.client).to.not.equal(null); - GoogleIS.revokeAccessToken(clientId).then(() => { - expect(GoogleIS.client).to.equal(null); - expect(GoogleIS.accessToken).to.equal(null); - }); - }); - }); - }); - - it('GoogleIS.signInButton renders a button', function() { - cy.viewport(600, 300); - const button = GoogleIS.signInButton('', () => {}, () => {}); - expect(button).to.not.equal(null); - expect(button.type).to.equal('button'); - }); -}); diff --git a/cypress/component/TermsOfService/tos_acceptance.spec.js b/cypress/component/TermsOfService/tos_acceptance.spec.js index a5776d7ef..fb6242446 100644 --- a/cypress/component/TermsOfService/tos_acceptance.spec.js +++ b/cypress/component/TermsOfService/tos_acceptance.spec.js @@ -3,6 +3,7 @@ import React from 'react'; import { mount } from 'cypress/react'; import TermsOfServiceAcceptance from '../../../src/pages/TermsOfServiceAcceptance'; +import { OidcBroker } from '../../../src/libs/auth/oidcBroker'; import { ToS } from '../../../src/libs/ajax/ToS'; import {Storage} from '../../../src/libs/storage'; import {Navigation} from '../../../src/libs/utils'; @@ -17,6 +18,8 @@ describe('Terms of Service Acceptance Page', function () { cy.viewport(600, 300); cy.stub(ToS, 'getDUOSText').returns(text); cy.stub(ToS, 'acceptToS').returns(true); + cy.stub(OidcBroker, 'signIn').returns({}); + cy.stub(OidcBroker, 'signOut'); cy.stub(Storage, 'getCurrentUser').returns({}); cy.stub(Navigation, 'back').returns(true); const setUserIsLoggedSpy = cy.spy(Storage, 'setUserIsLogged'); diff --git a/cypress/e2e/home.cy.js b/cypress/e2e/home.cy.js index 654f237b5..9932636fc 100644 --- a/cypress/e2e/home.cy.js +++ b/cypress/e2e/home.cy.js @@ -5,7 +5,7 @@ describe('Home', function() { it('Home page loads correctly', function() { cy.visit(Cypress.env('baseUrl')); cy.contains('DUOS'); - cy.contains('Sign-in/Register'); + cy.contains('Sign in'); cy.contains('What is DUOS and how does it work?'); cy.contains('DUOS for DACs'); cy.contains('Institutional Oversight'); diff --git a/package-lock.json b/package-lock.json index 7c698b73b..24d90fe4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,7 @@ "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/preset-react": "7.24.7", "@types/dompurify": "3.0.5", - "@types/history": "5.0.0", + "@types/history": "4.7.11", "@types/lodash": "4.17.5", "@types/react": "18.3.3", "@types/react-modal": "3.16.3", @@ -70,7 +70,7 @@ "eslint-plugin-flowtype": "8.0.3", "eslint-plugin-react": "7.34.3", "google-auth-library": "9.11.0", - "history": "5.3.0", + "history": "4.10.1", "html-webpack-plugin": "5.6.0", "prop-types": "15.8.1", "react-error-overlay": "6.0.11", @@ -4487,14 +4487,10 @@ } }, "node_modules/@types/history": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/history/-/history-5.0.0.tgz", - "integrity": "sha512-hy8b7Y1J8OGe6LbAjj3xniQrj3v6lsivCcrmf4TzSgPzLkhIeKgc5IZnT7ReIqmEuodjfO8EYAuoFvIrHi/+jQ==", - "deprecated": "This is a stub types definition. history provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "history": "*" - } + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", @@ -4660,18 +4656,6 @@ "@types/react-router": "*" } }, - "node_modules/@types/react-router-dom/node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, - "node_modules/@types/react-router/node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, "node_modules/@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -11253,12 +11237,16 @@ } }, "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dev": true, + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "dependencies": { - "@babel/runtime": "^7.7.6" + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" } }, "node_modules/hoist-non-react-statics": { @@ -18704,32 +18692,6 @@ "react": ">=15" } }, - "node_modules/react-router-dom/node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/react-router/node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, "node_modules/react-router/node_modules/mini-create-react-context": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", @@ -26136,13 +26098,10 @@ } }, "@types/history": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/history/-/history-5.0.0.tgz", - "integrity": "sha512-hy8b7Y1J8OGe6LbAjj3xniQrj3v6lsivCcrmf4TzSgPzLkhIeKgc5IZnT7ReIqmEuodjfO8EYAuoFvIrHi/+jQ==", - "dev": true, - "requires": { - "history": "*" - } + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true }, "@types/html-minifier-terser": { "version": "6.1.0", @@ -26295,14 +26254,6 @@ "requires": { "@types/history": "^4.7.11", "@types/react": "*" - }, - "dependencies": { - "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - } } }, "@types/react-router-dom": { @@ -26314,14 +26265,6 @@ "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router": "*" - }, - "dependencies": { - "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - } } }, "@types/react-transition-group": { @@ -31186,12 +31129,16 @@ "dev": true }, "history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dev": true, + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { - "@babel/runtime": "^7.7.6" + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" } }, "hoist-non-react-statics": { @@ -36373,19 +36320,6 @@ "tiny-warning": "^1.0.0" }, "dependencies": { - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, "mini-create-react-context": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", @@ -36414,21 +36348,6 @@ "react-router": "5.2.1", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" - }, - "dependencies": { - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - } } }, "react-scripts": { diff --git a/package.json b/package.json index 924687e64..ac786b361 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/preset-react": "7.24.7", "@types/dompurify": "3.0.5", - "@types/history": "5.0.0", + "@types/history": "4.7.11", "@types/lodash": "4.17.5", "@types/react": "18.3.3", "@types/react-modal": "3.16.3", @@ -79,7 +79,7 @@ "eslint-plugin-flowtype": "8.0.3", "eslint-plugin-react": "7.34.3", "google-auth-library": "9.11.0", - "history": "5.3.0", + "history": "4.10.1", "html-webpack-plugin": "5.6.0", "prop-types": "15.8.1", "react-error-overlay": "6.0.11", diff --git a/src/App.jsx b/src/App.jsx index 908ee2737..4e2add895 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,15 +10,15 @@ import loadingImage from './images/loading-indicator.svg'; import {SpinnerComponent as Spinner} from './components/SpinnerComponent'; import {StackdriverReporter} from './libs/stackdriverReporter'; -import {Storage} from './libs/storage'; + import Routes from './Routes'; -import {GoogleIS} from './libs/googleIS'; +import { Storage } from './libs/storage'; function App() { - const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(Storage.userIsLogged()); const [env, setEnv] = useState(''); - let history = useHistory(); - let location = useLocation(); + const history = useHistory(); + const location = useLocation(); const trackPageView = (location) => { ReactGA.send({ hitType: 'pageview', page: location.pathname+location.search }); @@ -66,26 +66,13 @@ function App() { setUserIsLogged(); }); - const signOut = async () => { - const clientId = await Config.getGoogleClientId(); - await GoogleIS.revokeAccessToken(clientId); - await Storage.setUserIsLogged(false); - await Storage.clearStorage(); - await setIsLoggedIn(false); - }; - - const signIn = async () => { - await Storage.setUserIsLogged(true); - await setIsLoggedIn(true); - }; - return (
- + - +
diff --git a/src/Routes.jsx b/src/Routes.jsx index a3817ef39..578d38355 100644 --- a/src/Routes.jsx +++ b/src/Routes.jsx @@ -23,7 +23,6 @@ import SigningOfficialDataSubmitters from './pages/signing_official_console/Sign import Translator from './pages/Translator'; import NIHPilotInfo from './pages/NIHPilotInfo'; import Status from './pages/Status'; -import BackgroundSignIn from './pages/BackgroundSignIn'; import ConsentTextGenerator from './pages/ConsentTextGenerator'; import AdminManageInstitutions from './pages/AdminManageInstitutions'; import AdminManageLC from './pages/AdminManageLC'; @@ -52,12 +51,6 @@ const Routes = (props) => ( } /> } /> - - checkEnv(envGroups.NON_STAGING) - ? - : - } /> diff --git a/src/appLoader.tsx b/src/appLoader.tsx new file mode 100644 index 000000000..d8cb346a4 --- /dev/null +++ b/src/appLoader.tsx @@ -0,0 +1,12 @@ +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import { createRoot } from 'react-dom/client'; +import React from 'react'; + +const container = document.getElementById('root'); +const root = createRoot(container!); +root.render( + + + +); diff --git a/src/components/DuosHeader.jsx b/src/components/DuosHeader.jsx index 0e8054cef..f9a9b6bab 100644 --- a/src/components/DuosHeader.jsx +++ b/src/components/DuosHeader.jsx @@ -18,6 +18,8 @@ import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; import {checkEnv, envGroups} from '../utils/EnvironmentUtils'; import {isFunction, isNil} from 'lodash/fp'; +import SignInButton from './SignInButton'; +import { Auth } from '../libs/auth/auth'; const styles = { drawerPaper: { @@ -227,6 +229,13 @@ const NavigationTabsComponent = (props) => { };
{/* Navbar right side */} + {!isLogged && ( +
+ +
+ )} {isLogged && (
{ const signOut = () => { props.history.push('/home'); toggleDrawer(false); - props.onSignOut(); + Auth.signOut(); }; const supportRequestModal = () => { @@ -381,12 +390,8 @@ const DuosHeader = (props) => { toggleDrawer(false); }; - let isLogged = Storage.userIsLogged(); - let currentUser = {}; - - if (isLogged) { - currentUser = Storage.getCurrentUser(); - } + const isLogged = Storage.userIsLogged(); + const currentUser = isLogged ? Storage.getCurrentUser() : {}; const contactUsSource = state.hover ? contactUsHover : contactUsStandard; const contactUsIcon = isLogged ? '' : ; diff --git a/src/components/SignInButton.tsx b/src/components/SignInButton.tsx index 6d1b89d62..017da569c 100644 --- a/src/components/SignInButton.tsx +++ b/src/components/SignInButton.tsx @@ -1,229 +1,140 @@ -import React, { useEffect, useState } from 'react'; -import { isEmpty, isNil } from 'lodash/fp'; +import React, { useState } from 'react'; import { Alert } from './Alert'; -import { ToS } from '../libs/ajax/ToS'; -import { User } from '../libs/ajax/User'; -import { Metrics } from '../libs/ajax/Metrics'; -import { Config } from '../libs/config'; import { Storage } from '../libs/storage'; -import { Navigation, setUserRoleStatuses } from '../libs/utils'; -import loadingIndicator from '../images/loading-indicator.svg'; -import { Spinner } from './Spinner'; -import ReactTooltip from 'react-tooltip'; -import { GoogleIS } from '../libs/googleIS'; -import eventList from '../libs/events'; -import { StackdriverReporter } from '../libs/stackdriverReporter'; -import { History } from 'history'; +import loadingImage from '../images/loading-indicator.svg'; +import { Auth } from '../libs/auth/auth'; import CSS from 'csstype'; +import { useHistory } from 'react-router'; +import { OidcUser } from '../libs/auth/oidcBroker'; +import { + attemptSignInCheckToSAndRedirect, + getRedirectTo, + handleConflictError, + registerAndRedirectNewUser, + shouldRedirectTo, +} from '../libs/signIn'; interface SignInButtonProps { - customStyle: CSS.Properties | undefined; - history: History; - onSignIn: () => Promise; + style: CSS.Properties | undefined; } interface ErrorInfo { title?: string; description?: string; - show?: boolean; - msg?: string; } -type ErrorDisplay = ErrorInfo | JSX.Element; - interface HttpError extends Error { status?: number; } -interface GoogleSuccessPayload { - accessToken: string; -} - -declare global { - interface Window { google: any; } -} - export const SignInButton = (props: SignInButtonProps) => { - const [clientId, setClientId] = useState(''); - const [errorDisplay, setErrorDisplay] = useState({}); - const { onSignIn, history, customStyle } = props; - - useEffect(() => { - // Using `isSubscribed` resolves the - // "To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function." warning - let isSubscribed = true; - const init = async () => { - if (isSubscribed) { - const googleClientId = await Config.getGoogleClientId(); - setClientId(googleClientId); - if (window.google !== undefined && GoogleIS.client === null) { - await GoogleIS.initTokenClient(googleClientId, onSuccess, onFailure); - } - } - ReactTooltip.rebuild(); - }; - init(); - return () => { - isSubscribed = false; - }; - }); + const [errorInfo, setErrorInfo] = useState({}); + const [isLoading, setIsLoading] = useState(false); + const [showError, setShowError] = useState(false); + const { style } = props; - // Utility function called in the normal success case and in the undocumented 409 case - // Check for ToS Acceptance - redirect user if not set. - const checkToSAndRedirect = async (redirectPath: string | null) => { - // Check if the user has accepted ToS yet or not: - const user = await User.getMe(); - if (!user.roles) { - await StackdriverReporter.report('roles not found for user: ' + user.email); - } - setUserRoleStatuses(user, Storage); - await onSignIn(); - const userStatus = await ToS.getStatus(); - const { tosAccepted } = userStatus; - if (!isEmpty(userStatus) && !tosAccepted) { - await Storage.setUserIsLogged(false); - if (isNil(redirectPath)) { - history.push(`/tos_acceptance`); - } else { - history.push(`/tos_acceptance?redirectTo=${redirectPath}`); - } - } else { - if (isNil(redirectPath)) { - Navigation.back(user, history); - } else { - history.push(redirectPath); - } - } - }; + const history = useHistory(); - const onSuccess = async (response: GoogleSuccessPayload) => { - Storage.setGoogleData(response); + // eslint-disable-next-line no-unused-vars + const onSuccess = async (_: OidcUser) => { const redirectTo = getRedirectTo(); const shouldRedirect = shouldRedirectTo(redirectTo); Storage.setAnonymousId(); - try { - await attemptSignInCheckToSAndRedirect(redirectTo, shouldRedirect); - } catch (error) { - await handleRegistration(redirectTo, shouldRedirect); - } - }; - - const getRedirectTo = (): string => { - const queryParams = new URLSearchParams(window.location.search); - return queryParams.get('redirectTo') || window.location.pathname; - }; - - const shouldRedirectTo = (page: string): boolean => page !== '/' && page !== '/home'; - - const attemptSignInCheckToSAndRedirect = async (redirectTo:string, shouldRedirect: boolean) => { - await checkToSAndRedirect(shouldRedirect ? redirectTo : null); - Metrics.identify(Storage.getAnonymousId()); - Metrics.syncProfile(); - Metrics.captureEvent(eventList.userSignIn); - }; - - const handleRegistration = async (redirectTo: string, shouldRedirect: boolean) => { - try { - await registerAndRedirectNewUser(redirectTo, shouldRedirect); - } catch (error) { - await handleErrors(error as HttpError, redirectTo, shouldRedirect); + await attemptSignInCheckToSAndRedirect( + redirectTo, + shouldRedirect, + history + ); + } catch (error: unknown) { + await registerAndRedirectNewUser( + redirectTo, + shouldRedirect, + history + ).catch((reason) => handleErrors(reason, redirectTo, shouldRedirect)); } }; - const registerAndRedirectNewUser = async (redirectTo: string, shouldRedirect: boolean) => { - const registeredUser = await User.registerUser(); - setUserRoleStatuses(registeredUser, Storage); - await onSignIn(); - Metrics.identify(Storage.getAnonymousId()); - Metrics.syncProfile(); - Metrics.captureEvent(eventList.userRegister); - history.push(`/tos_acceptance${shouldRedirect ? `?redirectTo=${redirectTo}` : ''}`); - }; - - const handleErrors = async (error: HttpError, redirectTo: string, shouldRedirect: boolean) => { + const handleErrors = async ( + error: HttpError, + redirectTo: string, + shouldRedirect: boolean + ) => { const status = error.status; switch (status) { case 400: - setErrorDisplay({ show: true, title: 'Error', msg: JSON.stringify(error) }); + setShowError(true); + setErrorInfo({ + title: 'Error', + description: JSON.stringify(error), + }); break; case 409: - handleConflictError(redirectTo, shouldRedirect); + handleConflictError(redirectTo, shouldRedirect, history); break; default: - setErrorDisplay({ show: true, title: 'Error', msg: 'Unexpected error, please try again' }); + setShowError(true); + setErrorInfo({ + title: 'Error', + description: 'Unexpected error, please try again', + }); break; } }; - const handleConflictError = async (redirectTo: string, shouldRedirect: boolean) => { - try { - await checkToSAndRedirect(shouldRedirect ? redirectTo : null); - } catch (error) { - Storage.clearStorage(); + const onFailure = (error: Error) => { + Auth.signOut(); + setIsLoading(false); + if (!error.message.includes('Popup closed by user')) { + setShowError(true); + setErrorInfo({ + title: 'Error', + description: error.message, + }); } }; - const onFailure = (response: any) => { - Storage.clearStorage(); - if (response.error === 'popup_closed_by_user') { - setErrorDisplay( - - Sign-in cancelled ... - - - ); - setTimeout(() => { - setErrorDisplay({}); - }, 2000); - } else { - setErrorDisplay({ title: response.error, description: response.details }); - } + const loadingIndicator = (): JSX.Element => { + return ( + + + + ); + }; + + const signInButton = (): JSX.Element => { + return ( + + ); }; - const spinnerOrSignInButton = () => { - return (clientId === '' - ? Spinner() - : (
- {isNil(customStyle) - ? GoogleIS.signInButton(clientId, onSuccess, onFailure) - : } - {isNil(customStyle) && - { + return ( +
+ - } - -
)); +
+ ); }; - return ( -
- {isEmpty(errorDisplay) - ?
- {spinnerOrSignInButton()} -
- :
- -
- } -
- ); + return
{showError ? errorAlert(errorInfo) : signInButton()}
; }; export default SignInButton; diff --git a/src/custom.d.ts b/src/custom.d.ts index 51c83b3a5..957f456d8 100644 --- a/src/custom.d.ts +++ b/src/custom.d.ts @@ -1,8 +1,8 @@ declare module '*.svg' { - export const ReactComponent: React.FunctionComponent< - React.SVGAttributes - >; + export const ReactComponent: React.FunctionComponent< + React.SVGAttributes + >; - const src: string; - export default src; + const src: string; + export default src; } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..0057e38ef --- /dev/null +++ b/src/index.ts @@ -0,0 +1,20 @@ +import 'bootstrap/dist/css/bootstrap.min.css'; + +// jquery is needed for bootstrap +import 'jquery/src/jquery'; +import 'bootstrap/dist/js/bootstrap.min'; + +import './index.css'; +import { unregister } from './registerServiceWorker'; +import { Auth } from './libs/auth/auth'; + +const load = async (): Promise => { + unregister(); + await Auth.initialize(); + window.location.pathname.startsWith('/redirect-from-oauth') + ? import('./libs/auth/RedirectFromOAuth') + : import('./appLoader'); +}; + +load(); + diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 5079ac2bd..000000000 --- a/src/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import 'bootstrap/dist/css/bootstrap.min.css'; - -// jquery is needed for bootstrap -import 'jquery/src/jquery'; -import 'bootstrap/dist/js/bootstrap.min'; - -import React from 'react'; -import {createRoot} from 'react-dom/client'; -import './index.css'; -import App from './App'; -import {unregister} from './registerServiceWorker'; -import {BrowserRouter} from 'react-router-dom'; - -const load = async () => { - unregister(); - const container = document.getElementById('root'); - const root = createRoot(container!); - root.render(); -}; - -await load(); diff --git a/src/libs/ajax.js b/src/libs/ajax.js index ba9ee02d0..3ee9f7bfa 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -1,15 +1,15 @@ import {getOr, isNil} from 'lodash/fp'; +import {Auth} from './auth/auth'; import {Config} from './config'; import {spinnerService} from './spinner-service'; import {StackdriverReporter} from './stackdriverReporter'; -import {Storage} from './storage'; import axios from 'axios'; //define axios interceptor //to log out user and redirect to home when response has 401 status //return responses with statuses in the 200s and reject the rest const redirectOnLogout = () => { - Storage.clearStorage(); + Auth.signOut(); window.location.href = `/home?redirectTo=${window.location.pathname}`; }; diff --git a/src/libs/ajax/Metrics.js b/src/libs/ajax/Metrics.js index 678b09446..f6e0312a5 100644 --- a/src/libs/ajax/Metrics.js +++ b/src/libs/ajax/Metrics.js @@ -1,6 +1,7 @@ import axios from 'axios'; import { getDefaultProperties } from '@databiosphere/bard-client'; +import { Auth } from '../auth/auth'; import { Storage } from '../storage'; import { getBardApiUrl } from '../ajax'; @@ -42,7 +43,7 @@ const captureEventFn = async (event, details = {}, signal) => { method: 'POST', url: `${await getBardApiUrl()}/api/event`, data: body, - headers: isRegistered ? { Authorization: `Bearer ${Storage.getGoogleData()?.accessToken}` } : undefined, + headers: isRegistered ? { Authorization: `Bearer ${Auth.getToken()}` } : undefined, signal, }; @@ -59,7 +60,7 @@ const syncProfile = async (signal) => { const config = { method: 'POST', url: `${await getBardApiUrl()}/api/syncProfile`, - headers: { Authorization: `Bearer ${Storage.getGoogleData()?.accessToken}` }, + headers: { Authorization: `Bearer ${Auth.getToken()}` }, signal, }; @@ -75,15 +76,13 @@ const syncProfile = async (signal) => { */ const identify = async (anonId, signal) => { const body = { anonId }; - const config = { method: 'POST', url: `${await getBardApiUrl()}/api/identify`, data: body, - headers: { Authorization: `Bearer ${Storage.getGoogleData()?.accessToken}` }, + headers: { Authorization: `Bearer ${Auth.getToken()}` }, signal, }; - return axios(config).catch(() => { }); }; diff --git a/src/libs/ajax/OAuth2.ts b/src/libs/ajax/OAuth2.ts new file mode 100644 index 000000000..23c84c504 --- /dev/null +++ b/src/libs/ajax/OAuth2.ts @@ -0,0 +1,17 @@ +import axios from 'axios'; +import { Config } from '../config'; + +export interface OAuthConfig { + clientId: string; + authorityEndpoint: string; +} + +export const OAuth2 = { + getConfig: async (): Promise => getConfig(), +}; + +const getConfig = async (): Promise => { + const configUrl = `${await Config.getApiUrl()}/oauth2/configuration`; + const res: OAuthConfig = (await axios.get(configUrl)).data; + return res; +}; diff --git a/src/libs/ajax/User.js b/src/libs/ajax/User.ts similarity index 83% rename from src/libs/ajax/User.js rename to src/libs/ajax/User.ts index 9e4960269..a4f53fa06 100644 --- a/src/libs/ajax/User.js +++ b/src/libs/ajax/User.ts @@ -4,26 +4,55 @@ import { Config } from '../config'; import axios from 'axios'; import { getApiUrl, fetchOk, fetchAny } from '../ajax'; +export type UserRoleName = + 'Admin' | 'Chairperson' | 'Member' | 'Researcher' | + 'Alumni' | 'SigningOfficial' | 'DataSubmitter' | 'All'; + +export interface UserRole { + roleId: number, + name: UserRoleName, + userId: number, + userRoleId: number, +} + +export interface DuosUser { + createDate: Date, + displayName: string, + email: string, + emailPreference: boolean, + isAdmin: boolean, + isAlumni: boolean, + isChairPerson: boolean, + isDataSubmitter: boolean, + isMember: boolean, + isResearcher: boolean, + isSigningOfficial: boolean, + roles: UserRole[], + userId: number, +} export const User = { - getMe: async () => { + getMe: async (): Promise => { const url = `${await getApiUrl()}/api/user/me`; const res = await axios.get(url, Config.authOpts()); return res.data; }, + // @ts-ignore getById: async (id) => { const url = `${await getApiUrl()}/api/user/${id}`; const res = await axios.get(url, Config.authOpts()); return res.data; }, + // @ts-ignore list: async (roleName) => { const url = `${await getApiUrl()}/api/user/role/${roleName}`; const res = await fetchOk(url, Config.authOpts()); return res.json(); }, + // @ts-ignore create: async (user) => { const url = `${await getApiUrl()}/api/dacuser`; try { @@ -36,6 +65,7 @@ export const User = { } }, + // @ts-ignore updateSelf: async (payload) => { const url = `${await getApiUrl()}/api/user`; // We should not be updating the user's create date, associated institution, or library cards @@ -49,10 +79,11 @@ export const User = { } }, + // @ts-ignore update: async (user, userId) => { const url = `${await getApiUrl()}/api/user/${userId}`; // We should not be updating the user's create date, associated institution, or library cards - let filteredUser = flow( + const filteredUser = flow( cloneDeep, unset('updatedUser.createDate'), unset('updatedUser.institution'), @@ -86,12 +117,14 @@ export const User = { return res.data; }, + // @ts-ignore addRoleToUser: async (userId, roleId) => { const url = `${await getApiUrl()}/api/user/${userId}/${roleId}`; const res = await fetchAny(url, fp.mergeAll([Config.authOpts(), { method: 'PUT' }])); return res.json(); }, + // @ts-ignore deleteRoleFromUser: async (userId, roleId) => { const url = `${await getApiUrl()}/api/user/${userId}/${roleId}`; const res = await fetchAny(url, fp.mergeAll([Config.authOpts(), { method: 'DELETE' }])); @@ -107,6 +140,7 @@ export const User = { const res = await axios.get(url, Config.authOpts()); return res.data; }, + // @ts-ignore acceptAcknowledgments: async (...keys) => { if (keys.length === 0) { return {}; diff --git a/src/libs/auth/RedirectFromOAuth.tsx b/src/libs/auth/RedirectFromOAuth.tsx new file mode 100644 index 000000000..6f5b689a7 --- /dev/null +++ b/src/libs/auth/RedirectFromOAuth.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { OidcBroker } from './oidcBroker'; +import { Spinner } from '../../components/Spinner'; +import { UserManager } from 'oidc-client-ts'; + +const userManager: UserManager = new UserManager( + OidcBroker.getUserManagerSettings() +); +const url = window.location.href; +const isSilent = window.location.pathname.startsWith( + '/redirect-from-oauth-silent' +); + +if (isSilent) { + userManager.signinSilentCallback(url); +} else { + userManager.signinPopupCallback(url); +} + +const rootElement = document.getElementById('root'); +const root = createRoot(rootElement!); +root.render(); diff --git a/src/libs/auth/auth.ts b/src/libs/auth/auth.ts new file mode 100644 index 000000000..fbe813150 --- /dev/null +++ b/src/libs/auth/auth.ts @@ -0,0 +1,56 @@ +/* + This file should abstract out the oidcBroker actions + and implement DUOS specific auth login (signIn, signOut, etc.) +*/ + +import { OidcBroker, OidcUser } from './oidcBroker'; +import { Storage } from './../storage'; +import { UserManager } from 'oidc-client-ts'; + +export const Auth = { + getToken: (): string => { + // In a future ticket, it would be better to make get Token an async function and call + // the UserManager.getUser to get the token. Since authOpts depends on getToken being synchonous + // it would be alot of places to change the uses of authOpts. + const oidcUser: OidcUser | null = OidcBroker.getUserSync(); + return oidcUser !== null ? oidcUser.access_token : 'token'; + }, + initialize: async (): Promise => { + await OidcBroker.initialize(); + const oidcUser: OidcUser | null = await OidcBroker.getUser(); + const um: UserManager = OidcBroker.getUserManager(); + // UserManager events. + // For details of each event, see https://authts.github.io/oidc-client-ts/classes/UserManagerEvents.html + // eslint-disable-next-line no-unused-vars + um.events.addUserLoaded((_: OidcUser) => { + //TODO: DUOS-3072 Add metrics for user loaded + }); + um.events.addAccessTokenExpiring((): void => { + //TODO: DUOS-3082 Add an alert that session will expire soon + }); + um.events.addAccessTokenExpired((): void => { + Auth.signOut(); + //TODO: DUOS-3082 Add an alert that session has expired + }); + if (oidcUser !== null) { + Storage.setUserIsLogged(true); + } else { + await Auth.signOut(); + } + }, + + signIn: async (popup: boolean): Promise => { + const user: OidcUser | null = await OidcBroker.signIn(popup); + if (user === null) { + throw new Error('signInSilent called before signInPopup'); + } + Storage.setUserIsLogged(true); + return user; + }, + + signOut: async () => { + Storage.clearStorage(); + Storage.setUserIsLogged(false); + await OidcBroker.signOut(); + }, +}; diff --git a/src/libs/auth/oidcBroker.ts b/src/libs/auth/oidcBroker.ts new file mode 100644 index 000000000..324d3fed8 --- /dev/null +++ b/src/libs/auth/oidcBroker.ts @@ -0,0 +1,95 @@ +import { IdTokenClaims, OidcMetadata, SigninPopupArgs, SigninSilentArgs, User, UserManager, UserManagerSettings, WebStorageStateStore } from 'oidc-client-ts'; +import { Config } from '../config'; +import { OAuth2, OAuthConfig } from '../ajax/OAuth2'; + +// Our config for b2C claims are defined here: https://github.com/broadinstitute/terraform-ap-deployments/tree/master/azure/b2c/policies +// The standard b2C claims are defined here: https://learn.microsoft.com/en-us/azure/active-directory/develop/id-token-claims-reference +export interface B2cIdTokenClaims extends IdTokenClaims { + email_verified?: boolean; + idp?: string; + idp_access_token?: string; + tid?: string; + ver?: string; +} + +export interface OidcUser extends User { + profile: B2cIdTokenClaims; +} + +type OidcUserManager = UserManager; + +let config: OAuthConfig | null = null; +let userManagerSettings: UserManagerSettings | null = null; +let userManager: UserManager | null = null; + +const generateOidcUserManagerSettings = async ( + config: OAuthConfig +): Promise => { + const metadata: Partial = { + authorization_endpoint: `${await Config.getApiUrl()}/oauth2/authorize`, + token_endpoint: `${await Config.getApiUrl()}/oauth2/token`, + }; + return { + authority: config.authorityEndpoint, + client_id: config.clientId, + popup_redirect_uri: `${window.origin}/redirect-from-oauth`, + silent_redirect_uri: `${window.origin}/redirect-from-oauth-silent`, + metadata, + prompt: 'consent login', + scope: 'openid email profile', + stateStore: new WebStorageStateStore({ store: window.localStorage }), + userStore: new WebStorageStateStore({ store: window.localStorage }), + automaticSilentRenew: true, + // Time before access token expires when access token expiring event is fired + accessTokenExpiringNotificationTimeInSeconds: 330, + includeIdTokenInSilentRenew: true, + extraQueryParams: { access_type: 'offline' }, + redirect_uri: '', // this field is not being used currently, but is expected from UserManager + }; +}; + +export const OidcBroker = { + initialize: async (): Promise => { + config = await OAuth2.getConfig(); + const ums: UserManagerSettings = await generateOidcUserManagerSettings( + config + ); + userManagerSettings = ums; + userManager = new UserManager(userManagerSettings); + }, + getUserManager: (): UserManager => { + if (userManager === null) { + throw new Error('Cannot retrieve userManager before OidcBroker is initialized'); + } + return userManager; + }, + getUserManagerSettings: (): UserManagerSettings => { + if (userManagerSettings === null) { + throw new Error('Cannot retrieve userManagerSettings before OidcBroker is initialized'); + } + return userManagerSettings; + }, + getUser: async (): Promise => { + const userManager: OidcUserManager = new UserManager(OidcBroker.getUserManagerSettings()); + return await userManager.getUser(); + }, + getUserSync: (): OidcUser | null => { + const settings: UserManagerSettings = + OidcBroker.getUserManagerSettings(); + const oidcStorage: string | null = localStorage.getItem( + `oidc.user:${settings.authority}:${settings.client_id}` + ); + return oidcStorage !== null ? User.fromStorageString(oidcStorage) : null; + }, + + signIn: async (popup: boolean, args?: SigninPopupArgs | SigninSilentArgs): Promise => { + const um: UserManager = OidcBroker.getUserManager(); + return popup ? await um.signinPopup(args) : await um.signinSilent(args); + }, + + signOut: async (): Promise => { + const um: UserManager = OidcBroker.getUserManager(); + await um.removeUser(); + await um.clearStaleState(); + } +}; diff --git a/src/libs/config.js b/src/libs/config.js index 995dc1f7c..e9f0c0a73 100644 --- a/src/libs/config.js +++ b/src/libs/config.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import {Storage} from './storage'; +import { Auth } from './auth/auth'; export const Config = { @@ -42,7 +42,7 @@ export const Config = { } }, - authOpts: (token = Token.getToken()) => ({ + authOpts: (token = Auth.getToken()) => ({ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json', @@ -50,7 +50,7 @@ export const Config = { }, }), - multiPartOpts: (token = Token.getToken()) => ({ + multiPartOpts: (token = Auth.getToken()) => ({ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'multipart/form-data', @@ -65,7 +65,7 @@ export const Config = { }, }), - fileOpts: (token = Token.getToken()) => ({ + fileOpts: (token = Auth.getToken()) => ({ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json', @@ -82,7 +82,7 @@ export const Config = { headers: {'Content-Type': 'application/binary'} }), - fileBody: (token = Token.getToken()) => ({ + fileBody: (token = Auth.getToken()) => ({ headers: { Authorization: `Bearer ${token}`, Accept: '*/*', @@ -90,14 +90,6 @@ export const Config = { }), }; -const Token = { - getToken: () => { - return Storage.getGoogleData() !== null ? - Storage.getGoogleData().accessToken : - 'token'; - }, -}; - const loadConfig = _.memoize(async () => { const res = await fetch('/config.json'); return res.json(); diff --git a/src/libs/googleIS.js b/src/libs/googleIS.js deleted file mode 100644 index 44208759c..000000000 --- a/src/libs/googleIS.js +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; - -/** - * This utility is a wrapper around Google's Identity Services API - * https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#gis-only - * For the purposes of DUOS authentication, our primary need is for an - * Access Token that we can use to communicate with any back end system. - */ - -const SCOPES = ['openid', - 'https://www.googleapis.com/auth/userinfo.profile', - 'https://www.googleapis.com/auth/userinfo.email'].join(' '); - -export const GoogleIS = { - - client: null, - - accessToken: null, - - initTokenClient: async (clientId, onSuccess, onFailure) => { - GoogleIS.client = await window.google.accounts.oauth2.initTokenClient({ - client_id: clientId, - scope: SCOPES, - callback: (tokenResponse) => { - try { - GoogleIS.accessToken = tokenResponse.access_token; - onSuccess({accessToken: GoogleIS.accessToken}); - } catch (e) { - onFailure(e); - } - }, - }); - }, - - requestAccessToken: async (clientId, onSuccess, onFailure) => { - try { - if (GoogleIS.client === null) { - await GoogleIS.initTokenClient(clientId, onSuccess, onFailure); - } - GoogleIS.accessToken = await GoogleIS.client.requestAccessToken(); - } catch (e) { - onFailure(e); - } - }, - - revokeAccessToken: async (clientId) => { - if (GoogleIS.accessToken !== null) { - if (GoogleIS.client === null) { - // We're initializing a client here purely for logout purposes. - await GoogleIS.initTokenClient(clientId, () => {}, () => {}); - } - await window.google.accounts.oauth2.revoke(GoogleIS.accessToken, () => { - // Reset internal state - GoogleIS.client = null; - GoogleIS.accessToken = null; - }); - } - }, - - signInButton: (clientId, onSuccess, onFailure) => - -}; diff --git a/src/libs/signIn.ts b/src/libs/signIn.ts new file mode 100644 index 000000000..194143a84 --- /dev/null +++ b/src/libs/signIn.ts @@ -0,0 +1,86 @@ +import { isEmpty, isNil } from 'lodash/fp'; +import { ToS } from '../libs/ajax/ToS'; +import { DuosUser, User } from '../libs/ajax/User'; +import { Metrics } from '../libs/ajax/Metrics'; +import { Navigation, setUserRoleStatuses } from '../libs/utils'; +import eventList from '../libs/events'; +import { StackdriverReporter } from '../libs/stackdriverReporter'; +import { Storage } from '../libs/storage'; +import { History } from 'history'; +import { Auth } from './auth/auth'; + +// Utility function called in the normal success case and in the undocumented 409 case +// Check for ToS Acceptance - redirect user if not set. +export const checkToSAndRedirect = async ( + redirectPath: string | null, + history: History +) => { + // Check if the user has accepted ToS yet or not: + const user: DuosUser = await User.getMe(); + if (!user.roles) { + await StackdriverReporter.report('roles not found for user: ' + user.email); + } + setUserRoleStatuses(user, Storage); + const userStatus = await ToS.getStatus(); + const { tosAccepted } = userStatus; + if (!isEmpty(userStatus) && !tosAccepted) { + Auth.signOut(); + if (isNil(redirectPath)) { + history.push(`/tos_acceptance`); + } else { + history.push(`/tos_acceptance?redirectTo=${redirectPath}`); + } + } else { + if (isNil(redirectPath)) { + Navigation.back(user, history); + } else { + history.push(redirectPath); + } + } +}; + +export const getRedirectTo = (): string => { + const queryParams = new URLSearchParams(window.location.search); + return queryParams.get('redirectTo') || window.location.pathname; +}; + +export const shouldRedirectTo = (page: string): boolean => + page !== '/' && page !== '/home'; + +export const attemptSignInCheckToSAndRedirect = async ( + redirectTo: string, + shouldRedirect: boolean, + history: History +) => { + await checkToSAndRedirect(shouldRedirect ? redirectTo : null, history); + Metrics.identify(Storage.getAnonymousId()); + Metrics.syncProfile(); + Metrics.captureEvent(eventList.userSignIn); +}; + +export const registerAndRedirectNewUser = async ( + redirectTo: string, + shouldRedirect: boolean, + history: History +) => { + const registeredUser = await User.registerUser(); + setUserRoleStatuses(registeredUser, Storage); + Metrics.identify(Storage.getAnonymousId()); + Metrics.syncProfile(); + Metrics.captureEvent(eventList.userRegister); + history.push( + `/tos_acceptance${shouldRedirect ? `?redirectTo=${redirectTo}` : ''}` + ); +}; + +export const handleConflictError = async ( + redirectTo: string, + shouldRedirect: boolean, + history: History +) => { + try { + await checkToSAndRedirect(shouldRedirect ? redirectTo : null, history); + } catch (error) { + Auth.signOut(); + } +}; diff --git a/src/libs/storage.js b/src/libs/storage.js index 86129781e..c3bec380a 100644 --- a/src/libs/storage.js +++ b/src/libs/storage.js @@ -3,7 +3,6 @@ import { v4 as uuid } from 'uuid'; // Storage Variables const CurrentUser = 'CurrentUser'; // System user -const GoogleUser = 'Gapi'; // Google user info, including token const UserIsLogged = 'isLogged'; // User log status flag const UserSettings = 'UserSettings'; // Different user settings for saving statuses in the app const anonymousId = 'anonymousId'; @@ -50,14 +49,6 @@ export const Storage = { sessionStorage.setItem(UserSettings, JSON.stringify(userSettings)); }, - setGoogleData: data => { - sessionStorage.setItem(GoogleUser, JSON.stringify(data)); - }, - - getGoogleData: () => { - return sessionStorage.getItem(GoogleUser) ? JSON.parse(sessionStorage.getItem(GoogleUser)) : null; - }, - userIsLogged: () => { return sessionStorage.getItem(UserIsLogged) === 'true'; }, diff --git a/src/pages/BackgroundSignIn.jsx b/src/pages/BackgroundSignIn.jsx deleted file mode 100644 index ba835ead7..000000000 --- a/src/pages/BackgroundSignIn.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import { User } from '../libs/ajax/User'; -import { Storage } from '../libs/storage'; -import { Navigation, setUserRoleStatuses } from '../libs/utils'; -import { useState, useEffect } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; -import { SpinnerComponent } from '../components/SpinnerComponent'; -import loadingImage from '../images/loading-indicator.svg'; - -export default function BackgroundSignIn(props) { - const location = useLocation(); - const history = useHistory(); - const queryParams = new URLSearchParams(location.search); - let token = queryParams.get('token'); - let { onSignIn, onError, bearerToken } = props; - token = bearerToken || (token || ''); - let [loading, setLoading] = useState(token && token !== ''); - let [accessToken, setAccessToken] = useState(token); - let [formToken, setFormToken] = useState(token); - let [invalidToken, setInvalidToken] = useState(false); - - useEffect(() => { - const getUser = async () => { - return await User.getMe(); - }; - - const redirect = (user) => { - Navigation.back(user, history); - if (onSignIn) - onSignIn(); - }; - - const setIsLogged = () => { - Storage.setUserIsLogged(true); - }; - - const performLogin = () => { - setLoading(true); - Storage.setGoogleData({ accessToken: accessToken }); - getUser().then( - user => { - user = Object.assign(user, setUserRoleStatuses(user, Storage)); - setIsLogged(); - setLoading(false); - redirect(user); - }, - error => { - const status = error.status; - switch (status) { - case 400: - if (onError) - onError(error); - setLoading(false); - break; - case 409: - // If the user exists, just log them in. - getUser().then( - user => { - user = Object.assign(user, setUserRoleStatuses(user, Storage)); - setIsLogged(); - redirect(user); - setLoading(false); - }, - () => { - Storage.clearStorage(); - setLoading(false); - }); - break; - case 401: - setInvalidToken(true); - setLoading(false); - break; - default: - setInvalidToken(true); - setLoading(false); - break; - } - }); - }; - - if (accessToken) - performLogin(); - return () => { }; - }, [accessToken, history, onError, onSignIn]); - - return ( -
- {loading - ?
- -
- :
{ - e.preventDefault(); - setAccessToken(formToken); - }} - > -
-
- {invalidToken && -
- The provided token is invalid. -
- } -
- -
-