Skip to content

Commit 605bc17

Browse files
Remove oauth implicit code and old authentication flag (#479)
* Add type for settings * Change to fetcher for settings * pass functions to async * remove code mode flag * remove implicit flow hack
1 parent e43c12d commit 605bc17

File tree

3 files changed

+72
-149
lines changed

3 files changed

+72
-149
lines changed

src/components/TopBar/TopBar.test.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import { act } from 'react-dom/test-utils';
1111
import { IntlProvider } from 'react-intl';
1212

1313
import TopBar, { LANG_ENGLISH } from './TopBar';
14-
import { top_bar_en } from '../../';
14+
import { CommonMetadata, top_bar_en } from '../../';
1515

1616
import PowsyblLogo from '../images/powsybl_logo.svg?react';
1717

1818
import { red } from '@mui/material/colors';
1919
import { createTheme, ThemeProvider } from '@mui/material';
20-
import { beforeEach, afterEach, it, expect } from '@jest/globals';
20+
import { afterEach, beforeEach, expect, it } from '@jest/globals';
2121

2222
let container: Element;
2323

@@ -32,19 +32,17 @@ afterEach(() => {
3232
container?.remove();
3333
});
3434

35-
const apps = [
35+
const apps: CommonMetadata[] = [
3636
{
3737
name: 'App1',
3838
url: '/app1',
3939
appColor: 'blue',
4040
hiddenInAppsMenu: false,
41-
resources: [],
4241
},
4342
{
4443
name: 'App2',
4544
url: '/app2',
4645
appColor: 'green',
47-
resources: [],
4846
hiddenInAppsMenu: true,
4947
},
5048
];

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export {
8080
dispatchUser,
8181
getPreLoginPath,
8282
} from './utils/AuthService';
83+
export type * from './utils/AuthService';
8384

8485
export { getFileIcon } from './utils/ElementIcon';
8586

src/utils/AuthService.ts

Lines changed: 68 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,32 @@
77
import { Log, User, UserManager } from 'oidc-client';
88
import { UserManagerMock } from './UserManagerMock';
99
import {
10+
resetAuthenticationRouterError,
1011
setLoggedUser,
12+
setLogoutError,
13+
setShowAuthenticationRouterLogin,
1114
setSignInCallbackError,
1215
setUnauthorizedUserInfo,
13-
setLogoutError,
1416
setUserValidationError,
15-
resetAuthenticationRouterError,
16-
setShowAuthenticationRouterLogin,
1717
} from '../redux/actions';
1818
import { jwtDecode } from 'jwt-decode';
1919
import { Dispatch } from 'react';
2020
import { NavigateFunction } from 'react-router-dom';
2121

2222
type UserValidationFunc = (user: User) => Promise<boolean>;
23+
type IdpSettingsGetter = () => Promise<IdpSettings>;
24+
25+
export type IdpSettings = {
26+
authority: string;
27+
client_id: string;
28+
redirect_uri: string;
29+
post_logout_redirect_uri: string;
30+
silent_redirect_uri: string;
31+
scope: string;
32+
maxExpiresIn?: number;
33+
};
2334

2435
type CustomUserManager = UserManager & {
25-
authorizationCodeFlowEnabled?: boolean;
2636
idpSettings?: {
2737
maxExpiresIn?: number;
2838
};
@@ -36,7 +46,7 @@ const hackAuthorityKey = 'oidc.hack.authority';
3646
const oidcHackReloadedKey = 'gridsuite-oidc-hack-reloaded';
3747
const pathKey = 'powsybl-gridsuite-current-path';
3848

39-
function isIssuerErrorForCodeFlow(error: Error) {
49+
function isIssuerError(error: Error) {
4050
return error.message.includes('Invalid issuer in token');
4151
}
4252

@@ -60,7 +70,6 @@ function reloadTimerOnExpiresIn(
6070
userManager: UserManager,
6171
expiresIn: number
6272
) {
63-
// TODO: Can we stop doing it in the hash for implicit flow ? To make it common for both flows
6473
// Not allowed by TS because expires_in is supposed to be readonly
6574
// @ts-ignore
6675
user.expires_in = expiresIn;
@@ -77,24 +86,16 @@ function handleSigninSilent(
7786
if (user == null || getIdTokenExpiresIn(user) < 0) {
7887
return userManager.signinSilent().catch((error: Error) => {
7988
dispatch(setShowAuthenticationRouterLogin(true));
80-
const errorIssuerCodeFlow = isIssuerErrorForCodeFlow(error);
81-
const errorIssuerImplicitFlow =
82-
error.message ===
83-
'authority mismatch on settings vs. signin state';
84-
if (errorIssuerCodeFlow) {
85-
// Replacing authority for code flow only because it's done in the hash for implicit flow
86-
// TODO: Can we stop doing it in the hash for implicit flow ? To make it common here for both flows
89+
if (isIssuerError(error)) {
8790
extractIssuerToSessionStorage(error);
88-
}
89-
if (errorIssuerCodeFlow || errorIssuerImplicitFlow) {
9091
reload();
9192
}
9293
});
9394
}
9495
});
9596
}
9697

97-
function initializeAuthenticationDev(
98+
export async function initializeAuthenticationDev(
9899
dispatch: Dispatch<unknown>,
99100
isSilentRenew: boolean,
100101
validateUser: UserValidationFunc,
@@ -109,113 +110,49 @@ function initializeAuthenticationDev(
109110
handleSigninSilent(dispatch, userManager);
110111
}
111112
}
112-
return Promise.resolve(userManager);
113+
return userManager;
113114
}
114115

115116
const accessTokenExpiringNotificationTime = 60; // seconds
116117

117-
function initializeAuthenticationProd(
118+
export async function initializeAuthenticationProd(
118119
dispatch: Dispatch<unknown>,
119120
isSilentRenew: boolean,
120-
idpSettings: Promise<Response>,
121+
idpSettingsGetter: IdpSettingsGetter,
121122
validateUser: UserValidationFunc,
122-
authorizationCodeFlowEnabled: boolean,
123123
isSigninCallback: boolean
124124
) {
125-
return idpSettings
126-
.then((r) => r.json())
127-
.then((idpSettings) => {
128-
/* hack to ignore the iss check. XXX TODO to remove */
129-
const regextoken = /id_token=[^&]*/;
130-
const regexstate = /state=[^&]*/;
131-
const regexexpires = /expires_in=[^&]*/;
132-
let authority: string | undefined;
133-
if (window.location.hash) {
134-
const matched_id_token = window.location.hash.match(regextoken);
135-
const matched_state = window.location.hash.match(regexstate);
136-
if (matched_id_token != null && matched_state != null) {
137-
const id_token = matched_id_token[0].split('=')[1];
138-
const state = matched_state[0].split('=')[1];
139-
const strState = localStorage.getItem('oidc.' + state);
140-
if (strState != null) {
141-
const decoded = jwtDecode(id_token);
142-
authority = decoded.iss;
143-
const storedState = JSON.parse(strState);
144-
console.debug(
145-
'Replacing authority in storedState. Before: ',
146-
storedState.authority,
147-
'after: ',
148-
authority
149-
);
150-
storedState.authority = authority;
151-
localStorage.setItem(
152-
'oidc.' + state,
153-
JSON.stringify(storedState)
154-
);
155-
if (authority !== undefined) {
156-
sessionStorage.setItem(hackAuthorityKey, authority);
157-
}
158-
const matched_expires =
159-
window.location.hash.match(regexexpires);
160-
if (matched_expires != null) {
161-
const expires_in = parseInt(
162-
matched_expires[0].split('=')[1]
163-
);
164-
window.location.hash = window.location.hash.replace(
165-
matched_expires[0],
166-
'expires_in=' +
167-
computeMinExpiresIn(
168-
expires_in,
169-
id_token,
170-
idpSettings.maxExpiresIn
171-
)
172-
);
173-
}
174-
}
175-
}
176-
}
177-
authority =
178-
authority ||
125+
const idpSettings = await idpSettingsGetter();
126+
try {
127+
const settings = {
128+
authority:
179129
sessionStorage.getItem(hackAuthorityKey) ||
180-
idpSettings.authority;
181-
182-
const responseSettings = authorizationCodeFlowEnabled
183-
? { response_type: 'code' }
184-
: {
185-
response_type: 'id_token token',
186-
response_mode: 'fragment',
187-
};
188-
const settings = {
189-
authority,
190-
client_id: idpSettings.client_id,
191-
redirect_uri: idpSettings.redirect_uri,
192-
post_logout_redirect_uri: idpSettings.post_logout_redirect_uri,
193-
silent_redirect_uri: idpSettings.silent_redirect_uri,
194-
scope: idpSettings.scope,
195-
automaticSilentRenew: !isSilentRenew,
196-
accessTokenExpiringNotificationTime:
197-
accessTokenExpiringNotificationTime,
198-
...responseSettings,
199-
};
200-
let userManager: CustomUserManager = new UserManager(settings);
201-
// Hack to enrich UserManager object
202-
userManager.idpSettings = idpSettings; //store our settings in there as well to use it later
203-
// Hack to enrich UserManager object
204-
userManager.authorizationCodeFlowEnabled =
205-
authorizationCodeFlowEnabled;
206-
if (!isSilentRenew) {
207-
handleUser(dispatch, userManager, validateUser);
208-
if (!isSigninCallback) {
209-
handleSigninSilent(dispatch, userManager);
210-
}
130+
idpSettings.authority,
131+
client_id: idpSettings.client_id,
132+
redirect_uri: idpSettings.redirect_uri,
133+
post_logout_redirect_uri: idpSettings.post_logout_redirect_uri,
134+
silent_redirect_uri: idpSettings.silent_redirect_uri,
135+
scope: idpSettings.scope,
136+
automaticSilentRenew: !isSilentRenew,
137+
accessTokenExpiringNotificationTime:
138+
accessTokenExpiringNotificationTime,
139+
response_type: 'code',
140+
};
141+
let userManager: CustomUserManager = new UserManager(settings);
142+
// Hack to enrich UserManager object
143+
userManager.idpSettings = idpSettings; //store our settings in there as well to use it later
144+
if (!isSilentRenew) {
145+
handleUser(dispatch, userManager, validateUser);
146+
if (!isSigninCallback) {
147+
handleSigninSilent(dispatch, userManager);
211148
}
212-
return userManager;
213-
})
214-
.catch((error) => {
215-
console.debug('error when importing the idp settings', error);
216-
dispatch(setShowAuthenticationRouterLogin(true));
217-
throw error;
218-
});
149+
}
150+
return userManager;
151+
} catch (error: unknown) {
152+
console.debug('error when importing the idp settings', error);
153+
dispatch(setShowAuthenticationRouterLogin(true));
154+
throw error;
155+
}
219156
}
220157

221158
function computeMinExpiresIn(
@@ -257,14 +194,17 @@ function computeMinExpiresIn(
257194
return newExpiresIn;
258195
}
259196

260-
function login(location: Location, userManagerInstance: UserManager) {
197+
export function login(location: Location, userManagerInstance: UserManager) {
261198
sessionStorage.setItem(pathKey, location.pathname + location.search);
262199
return userManagerInstance
263200
.signinRedirect()
264201
.then(() => console.debug('login'));
265202
}
266203

267-
function logout(dispatch: Dispatch<unknown>, userManagerInstance: UserManager) {
204+
export function logout(
205+
dispatch: Dispatch<unknown>,
206+
userManagerInstance: UserManager
207+
) {
268208
sessionStorage.removeItem(hackAuthorityKey); //To remove when hack is removed
269209
return userManagerInstance.getUser().then((user) => {
270210
if (user) {
@@ -301,7 +241,7 @@ function getIdTokenExpiresIn(user: User) {
301241
return exp - now;
302242
}
303243

304-
function dispatchUser(
244+
export function dispatchUser(
305245
dispatch: Dispatch<unknown>,
306246
userManagerInstance: CustomUserManager,
307247
validateUser: UserValidationFunc
@@ -333,20 +273,17 @@ function dispatchUser(
333273
console.debug(
334274
'User has been successfully loaded from store.'
335275
);
336-
337276
// In authorization code flow we have to make the oidc-client lib re-evaluate the date of the token renewal timers
338277
// because it is not hacked at page loading on the fragment before oidc-client lib initialization
339-
if (userManagerInstance.authorizationCodeFlowEnabled) {
340-
reloadTimerOnExpiresIn(
341-
user,
342-
userManagerInstance,
343-
computeMinExpiresIn(
344-
user.expires_in,
345-
user.id_token,
346-
userManagerInstance.idpSettings?.maxExpiresIn
347-
)
348-
);
349-
}
278+
reloadTimerOnExpiresIn(
279+
user,
280+
userManagerInstance,
281+
computeMinExpiresIn(
282+
user.expires_in,
283+
user.id_token,
284+
userManagerInstance.idpSettings?.maxExpiresIn
285+
)
286+
);
350287
return dispatch(setLoggedUser(user));
351288
})
352289
.catch((e) => {
@@ -363,7 +300,7 @@ function dispatchUser(
363300
});
364301
}
365302

366-
function getPreLoginPath() {
303+
export function getPreLoginPath() {
367304
return sessionStorage.getItem(pathKey);
368305
}
369306

@@ -374,7 +311,7 @@ function navigateToPreLoginPath(navigate: NavigateFunction) {
374311
}
375312
}
376313

377-
function handleSigninCallback(
314+
export function handleSigninCallback(
378315
dispatch: Dispatch<unknown>,
379316
navigate: NavigateFunction,
380317
userManagerInstance: UserManager
@@ -383,9 +320,7 @@ function handleSigninCallback(
383320
userManagerInstance
384321
.signinRedirectCallback()
385322
.catch(function (e) {
386-
if (isIssuerErrorForCodeFlow(e)) {
387-
// Replacing authority for code flow only because it's done in the hash for implicit flow
388-
// TODO: Can we also do it here for the implicit flow ? To make it common here for both flows
323+
if (isIssuerError(e)) {
389324
extractIssuerToSessionStorage(e);
390325
// After navigate, location will be out of a redirection route (sign-in-silent or sign-in-callback) so reloading the page will attempt a silent signin
391326
// It will reload the user manager based on hacked authority at initialization with the new authority
@@ -409,7 +344,7 @@ function handleSigninCallback(
409344
});
410345
}
411346

412-
function handleSilentRenewCallback(userManagerInstance: UserManager) {
347+
export function handleSilentRenewCallback(userManagerInstance: UserManager) {
413348
userManagerInstance.signinSilentCallback();
414349
}
415350

@@ -507,14 +442,3 @@ function handleUser(
507442
console.debug('dispatch user');
508443
dispatchUser(dispatch, userManager, validateUser);
509444
}
510-
511-
export {
512-
initializeAuthenticationDev,
513-
initializeAuthenticationProd,
514-
handleSilentRenewCallback,
515-
login,
516-
logout,
517-
dispatchUser,
518-
handleSigninCallback,
519-
getPreLoginPath,
520-
};

0 commit comments

Comments
 (0)