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
- ?
-
-
- :
- }
-
- );
-}
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
index 9ac57d8aa..65a12ebe4 100644
--- a/src/pages/Home.jsx
+++ b/src/pages/Home.jsx
@@ -1,16 +1,11 @@
import React from 'react';
-import SignInButton from '../components/SignInButton';
import { ReadMore } from '../components/ReadMore';
import homeHeaderBackground from '../images/home_header_background.png';
import duosLogoImg from '../images/duos_logo.svg';
import duosDiagram from '../images/DUOS_Homepage_diagram.svg';
import { Link } from 'react-router-dom';
-import { Storage } from '../libs/storage';
const Home = (props) => {
- const { onSignIn, history } = props;
- const isLogged = Storage.userIsLogged();
-
const homeTitle = {
color: '#FFFFFF',
fontFamily: 'Montserrat',
@@ -82,22 +77,11 @@ const Home = (props) => {
display: 'block'
};
- const signInPositionStyle = {
- padding: '1em 1em 0 0',
- alignItems: 'center',
- position: 'absolute',
- top: '0',
- right: '1rem',
- };
-
return (
- {!isLogged &&
-
-
}
Data Use Oversight System
diff --git a/src/pages/TermsOfService.jsx b/src/pages/TermsOfService.jsx
index 1cbb5c0a7..d196df457 100644
--- a/src/pages/TermsOfService.jsx
+++ b/src/pages/TermsOfService.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import { useEffect, useState } from 'react';
import { Storage } from '../libs/storage';
+import { Auth } from '../libs/auth/auth';
import { TosService } from '../libs/tosService';
import SimpleButton from '../components/SimpleButton';
@@ -22,8 +23,7 @@ export default function TermsOfService(props) {
await TosService.rejectTos();
// log user out and send them back home.
- await Storage.setUserIsLogged(false);
- await Storage.clearStorage();
+ await Auth.signOut();
history.push('/');
};
diff --git a/src/pages/TermsOfServiceAcceptance.jsx b/src/pages/TermsOfServiceAcceptance.jsx
index 670ede162..7244e2a67 100644
--- a/src/pages/TermsOfServiceAcceptance.jsx
+++ b/src/pages/TermsOfServiceAcceptance.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import { useCallback, useEffect, useState } from 'react';
+import { Auth } from '../libs/auth/auth';
import { TosService } from '../libs/tosService';
-import { Storage } from '../libs/storage';
import SimpleButton from '../components/SimpleButton';
import { Theme } from '../libs/theme';
@@ -19,7 +19,6 @@ export default function TermsOfServiceAcceptance(props) {
const acceptToS = useCallback(async () => {
await TosService.acceptTos();
- await Storage.setUserIsLogged(true);
// if there is a redirectTo, we should go to that. otherwise, just go to the appropriate
// user's profile.
@@ -44,8 +43,7 @@ export default function TermsOfServiceAcceptance(props) {
/>;
const signOut = async () => {
- await Storage.setUserIsLogged(false);
- await Storage.clearStorage();
+ await Auth.signOut();
history.push('/');
};