Skip to content

Commit

Permalink
feat: use OidcBroker for sign-out functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
rushtong committed Aug 29, 2024
1 parent 40cd161 commit 8ab174e
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 12 deletions.
18 changes: 18 additions & 0 deletions cypress/component/Auth/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable no-undef */

import {Auth} from '../../../src/libs/auth/auth';
import {Config} from '../../../src/libs/config';
import {GoogleIS} from '../../../src/libs/googleIS';
import {Storage} from '../../../src/libs/storage';

describe('Auth', function () {
it('Sign Out Clears the session when called', async function () {
cy.stub(Config, 'getGoogleClientId').returns('12345');
cy.stub(GoogleIS, 'revokeAccessToken');
await Auth.initialize();
Storage.setUserIsLogged(true);
expect(Storage.userIsLogged()).to.be.true;
await Auth.signOut();
expect(Storage.userIsLogged()).to.be.false;
});
});
19 changes: 19 additions & 0 deletions cypress/component/Auth/oidcBroker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable no-undef */

import {Config} from '../../../src/libs/config';
import {GoogleIS} from '../../../src/libs/googleIS';
import {OidcBroker} from '../../../src/libs/auth/oidcBroker';

describe('OidcBroker', function () {
it('Sign Out calls Oidc UserManager sign-out functions', async function () {
cy.stub(Config, 'getGoogleClientId').returns('12345');
cy.stub(GoogleIS, 'revokeAccessToken');
await OidcBroker.initialize();
const um = OidcBroker.getUserManager();
cy.spy(um, 'removeUser').as('removeUser');
cy.spy(um, 'clearStaleState').as('clearStaleState');
await OidcBroker.signOut();
expect(um.removeUser).to.be.called;
expect(um.clearStaleState).to.be.called;
});
});
13 changes: 2 additions & 11 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ 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';

function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
Expand Down Expand Up @@ -66,14 +65,6 @@ 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);
Expand All @@ -83,9 +74,9 @@ function App() {
<div className="body">
<div className="wrap">
<div className="main">
<DuosHeader onSignOut={signOut} />
<DuosHeader />
<Spinner name="mainSpinner" group="duos" loadingImage={loadingImage} />
<Routes onSignOut={signOut} onSignIn={signIn} isLogged={isLoggedIn} env={env} />
<Routes onSignIn={signIn} isLogged={isLoggedIn} env={env} />
</div>
</div>
<DuosFooter />
Expand Down
3 changes: 2 additions & 1 deletion src/components/DuosHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
import {isFunction, isNil} from 'lodash/fp';
import {DAAUtils} from '../utils/DAAUtils';
import {Auth} from '../libs/auth/auth';

const styles = {
drawerPaper: {
Expand Down Expand Up @@ -340,7 +341,7 @@ const DuosHeader = (props) => {
const signOut = () => {
props.history.push('/home');
toggleDrawer(false);
props.onSignOut();
Auth.signOut();
};

const supportRequestModal = () => {
Expand Down
16 changes: 16 additions & 0 deletions src/libs/ajax/OAuth2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import axios from 'axios';
import {Config} from '../config';

export interface OAuthConfig {
clientId: string;
authorityEndpoint: string;
}

export const OAuth2 = {
getConfig: async (): Promise<OAuthConfig> => getConfig(),
};

const getConfig = async (): Promise<OAuthConfig> => {
const configUrl = `${await Config.getApiUrl()}/oauth2/configuration`;
return (await axios.get(configUrl)).data;
};
38 changes: 38 additions & 0 deletions src/libs/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
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 = {
initialize: async (): Promise<void> => {
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();
}
},
signOut: async () => {
Storage.clearStorage();
Storage.setUserIsLogged(false);
await OidcBroker.signOut();
},
};
88 changes: 88 additions & 0 deletions src/libs/auth/oidcBroker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
IdTokenClaims,
OidcMetadata,
User,
UserManager,
UserManagerSettings,
WebStorageStateStore
} from 'oidc-client-ts';

import {Config} from '../config';
import {OAuth2, OAuthConfig} from '../ajax/OAuth2';
import {GoogleIS} from '../googleIS';

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<UserManagerSettings> => {
const metadata: Partial<OidcMetadata> = {
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<void> => {
config = await OAuth2.getConfig();
userManagerSettings = await generateOidcUserManagerSettings(config);
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<OidcUser | null> => {
const userManager: OidcUserManager = new UserManager(OidcBroker.getUserManagerSettings());
return await userManager.getUser();
},
signOut: async (): Promise<void> => {
// TODO: When sign-in is migrated to OIDC, we can remove GoogleIS functionality
const clientId = await Config.getGoogleClientId();
await GoogleIS.revokeAccessToken(clientId);
const um: UserManager = OidcBroker.getUserManager();
await um.removeUser();
await um.clearStaleState();
}
};

0 comments on commit 8ab174e

Please sign in to comment.