Skip to content

Commit 001cee1

Browse files
OskarDamkjaernielsdejongBennuFire
authored
Handle token expiration in SSO (#611)
* handles token expiration * self review * selfreview2 * thanks sonar cloud --------- Co-authored-by: Niels de Jong <[email protected]> Co-authored-by: Harold Agudelo <[email protected]>
1 parent 6b1379b commit 001cee1

File tree

7 files changed

+108
-13
lines changed

7 files changed

+108
-13
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"mui-color": "^2.0.0-beta.2",
7373
"mui-nested-menu": "^3.2.1",
7474
"neo4j-client-sso": "^1.2.2",
75+
"neo4j-driver": "^5.12.0",
7576
"openai": "^3.3.0",
7677
"postcss": "^8.4.21",
7778
"postcss-loader": "^7.2.4",

src/application/ApplicationActions.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* This file contains all state-changing actions relevant for the main application.
33
*/
44

5+
import { SSOProviderOriginal } from 'neo4j-client-sso';
6+
57
export const CLEAR_NOTIFICATION = 'APPLICATION/CLEAR_NOTIFICATION';
68
export const clearNotification = () => ({
79
type: CLEAR_NOTIFICATION,
@@ -56,10 +58,11 @@ export const setConnectionProperties = (
5658
port: string,
5759
database: string,
5860
username: string,
59-
password: string
61+
password: string,
62+
ssoProviders?: SSOProviderOriginal[]
6063
) => ({
6164
type: SET_CONNECTION_PROPERTIES,
62-
payload: { protocol, url, port, database, username, password },
65+
payload: { protocol, url, port, database, username, password, ssoProviders },
6366
});
6467

6568
export const SET_BASIC_CONNECTION_PROPERTIES = 'APPLICATION/SET_BASIC_CONNECTION_PROPERTIES';

src/application/ApplicationReducer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const initialState = {
6464
database: '',
6565
username: 'neo4j',
6666
password: '',
67+
ssoProviders: [],
6768
},
6869
shareDetails: undefined,
6970
desktopConnection: null,
@@ -246,7 +247,7 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
246247
return state;
247248
}
248249
case SET_CONNECTION_PROPERTIES: {
249-
const { protocol, url, port, database, username, password } = payload;
250+
const { protocol, url, port, database, username, password, ssoProviders } = payload;
250251
state = update(state, {
251252
connection: {
252253
protocol: protocol,
@@ -255,6 +256,7 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
255256
database: database,
256257
username: username,
257258
password: password,
259+
ssoProviders,
258260
},
259261
});
260262
return state;

src/application/ApplicationThunks.ts

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { createDriver } from 'use-neo4j';
21
import { initializeSSO } from '../component/sso/SSOUtils';
32
import { DEFAULT_SCREEN, Screens } from '../config/ApplicationConfig';
43
import { setDashboard } from '../dashboard/DashboardActions';
@@ -44,6 +43,9 @@ import {
4443
} from './ApplicationActions';
4544
import { setLoggingMode, setLoggingDatabase, setLogErrorNotification } from './logging/LoggingActions';
4645
import { version } from '../modal/AboutModal';
46+
import neo4j, { auth, authTokenManagers } from 'neo4j-driver';
47+
import type { Neo4jScheme } from 'use-neo4j/dist/neo4j-config.interface';
48+
import { SSOProviderOriginal, handleRefreshingToken } from 'neo4j-client-sso';
4749
import { applicationIsStandalone } from './ApplicationSelectors';
4850
import { applicationGetLoggingSettings } from './logging/LoggingSelectors';
4951
import { createLogThunk } from './logging/LoggingThunk';
@@ -54,6 +56,47 @@ import { createUUID } from '../utils/uuid';
5456
* Several actions/other thunks may be dispatched from here.
5557
*/
5658

59+
export const createDriver = (
60+
scheme: Neo4jScheme,
61+
host: string,
62+
port: string | number,
63+
username?: string,
64+
password?: string,
65+
config?: { userAgent?: string },
66+
ssoProviders: SSOProviderOriginal[] = []
67+
) => {
68+
if (ssoProviders.length > 0) {
69+
const authTokenMgr = authTokenManagers.bearer({
70+
tokenProvider: async () => {
71+
const credentials = await handleRefreshingToken(ssoProviders);
72+
const token = auth.bearer(credentials.password);
73+
// Get the expiration from the JWT's payload, which is a JSON string encoded
74+
// using base64. You could also use a JWT parsing lib
75+
const [, payloadBase64] = credentials.password.split('.');
76+
const payload: unknown = JSON.parse(window.atob(payloadBase64 ?? ''));
77+
let expiration: Date;
78+
if (typeof payload === 'object' && payload !== null && 'exp' in payload) {
79+
expiration = new Date(Number(payload.exp) * 1000);
80+
} else {
81+
expiration = new Date();
82+
}
83+
84+
return {
85+
expiration,
86+
token,
87+
};
88+
},
89+
});
90+
return neo4j.driver(`${scheme}://${host}:${port}`, authTokenMgr, config);
91+
}
92+
93+
if (!username || !password) {
94+
return neo4j.driver(`${scheme}://${host}:${port}`);
95+
}
96+
97+
return neo4j.driver(`${scheme}://${host}:${port}`, neo4j.auth.basic(username, password), config);
98+
};
99+
57100
/**
58101
* Establish a connection to Neo4j with the specified credentials. Open/close the relevant windows when connection is made (un)successfully.
59102
* @param protocol - the neo4j protocol (e.g. bolt, bolt+s, neo4j+s, ...)
@@ -62,14 +105,24 @@ import { createUUID } from '../utils/uuid';
62105
* @param database - the Neo4j database to connect to.
63106
* @param username - Neo4j username.
64107
* @param password - Neo4j password.
108+
* @param SSOProviders - List of available SSO providers
65109
*/
66110
export const createConnectionThunk =
67-
(protocol, url, port, database, username, password) => (dispatch: any, getState: any) => {
111+
(protocol, url, port, database, username, password, SSOProviders = []) =>
112+
(dispatch: any, getState: any) => {
68113
const loggingState = getState();
69114
const loggingSettings = applicationGetLoggingSettings(loggingState);
70115
const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor';
71116
try {
72-
const driver = createDriver(protocol, url, port, username, password, { userAgent: `neodash/v${version}` });
117+
const driver = createDriver(
118+
protocol,
119+
url,
120+
port,
121+
username,
122+
password,
123+
{ userAgent: `neodash/v${version}` },
124+
SSOProviders
125+
);
73126
// eslint-disable-next-line no-console
74127
console.log('Attempting to connect...');
75128
const validateConnection = (records) => {
@@ -508,7 +561,7 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
508561
dispatch(setAboutModalOpen(false));
509562
dispatch(setConnected(false));
510563
dispatch(setWelcomeScreenOpen(false));
511-
const success = await initializeSSO(state.application.cachedSSODiscoveryUrl, (credentials) => {
564+
const success = await initializeSSO(state.application.cachedSSODiscoveryUrl, (credentials, ssoProviders) => {
512565
if (standalone) {
513566
// Redirected from SSO and running in viewer mode, merge retrieved config with hardcoded credentials.
514567
dispatch(
@@ -518,7 +571,8 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
518571
config.standalonePort,
519572
config.standaloneDatabase,
520573
credentials.username,
521-
credentials.password
574+
credentials.password,
575+
ssoProviders
522576
)
523577
);
524578
dispatch(
@@ -528,7 +582,8 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
528582
config.standalonePort,
529583
config.standaloneDatabase,
530584
credentials.username,
531-
credentials.password
585+
credentials.password,
586+
ssoProviders
532587
)
533588
);
534589
} else {
@@ -540,7 +595,8 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
540595
state.application.connection.port,
541596
state.application.connection.database,
542597
credentials.username,
543-
credentials.password
598+
credentials.password,
599+
ssoProviders
544600
)
545601
);
546602
dispatch(setConnected(true));

src/component/sso/SSOUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const initializeSSO = async (cachedSSODiscoveryUrl, _setCredentials) => {
118118
// Successful credentials retrieval.
119119
// Log in at the Neo4j dbms now using the Neo4j (js) driver.
120120
//
121-
_setCredentials(credentials);
121+
_setCredentials(credentials, mergedSSOProviders);
122122

123123
// Exemplifying retrieval of stored URL paramenters
124124
_retrieveAdditionalURLParameters();

src/dashboard/Dashboard.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import NeoPage from '../page/Page';
33
import NeoDashboardHeader from './header/DashboardHeader';
44
import NeoDashboardTitle from './header/DashboardTitle';
55
import NeoDashboardHeaderPageList from './header/DashboardHeaderPageList';
6-
import { createDriver, Neo4jProvider } from 'use-neo4j';
6+
import { Neo4jProvider } from 'use-neo4j';
77
import { applicationGetConnection, applicationGetStandaloneSettings } from '../application/ApplicationSelectors';
88
import { connect } from 'react-redux';
99
import NeoDashboardConnectionUpdateHandler from '../component/misc/DashboardConnectionUpdateHandler';
1010
import { forceRefreshPage } from '../page/PageActions';
1111
import { getPageNumber } from '../settings/SettingsSelectors';
1212
import { createNotificationThunk } from '../page/PageThunks';
1313
import { version } from '../modal/AboutModal';
14+
import { createDriver } from '../application/ApplicationThunks';
1415
import NeoDashboardSidebar from './sidebar/DashboardSidebar';
1516

1617
const Dashboard = ({
@@ -32,8 +33,10 @@ const Dashboard = ({
3233
connection.port,
3334
connection.username,
3435
connection.password,
35-
{ userAgent: `neodash/v${version}` }
36+
{ userAgent: `neodash/v${version}` },
37+
connection.ssoProviders
3638
);
39+
// @ts-ignore wrong driver version
3740
setDriver(newDriver);
3841
}
3942
const content = (

yarn.lock

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10435,6 +10435,15 @@ neo4j-client-sso@^1.2.2:
1043510435
jwt-decode "^3.1.2"
1043610436
lodash.pick "^4.4.0"
1043710437

10438+
10439+
version "5.12.0"
10440+
resolved "https://registry.yarnpkg.com/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.12.0.tgz#aff161367d287579d7bdd3ee4179eed324398210"
10441+
integrity sha512-dlYbFsfT0HopGItitG5uDK4nAkcqSPNtRqMz318qy//7fb/7OXVLGYikj57Ve1toJiJD8IIVErt/dVuEUHVxGA==
10442+
dependencies:
10443+
buffer "^6.0.3"
10444+
neo4j-driver-core "5.12.0"
10445+
string_decoder "^1.3.0"
10446+
1043810447
neo4j-driver-bolt-connection@^4.4.10:
1043910448
version "4.4.10"
1044010449
resolved "https://registry.yarnpkg.com/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-4.4.10.tgz#a8b5b7f82b1d6f9a71a43eafcb0e21512ea24908"
@@ -10444,6 +10453,11 @@ neo4j-driver-bolt-connection@^4.4.10:
1044410453
neo4j-driver-core "^4.4.10"
1044510454
string_decoder "^1.3.0"
1044610455

10456+
10457+
version "5.12.0"
10458+
resolved "https://registry.yarnpkg.com/neo4j-driver-core/-/neo4j-driver-core-5.12.0.tgz#1f8616da7e945921574811368a68f5d2501bfd35"
10459+
integrity sha512-xBRi5oezysDUvtvBiIgBchzumkDZxvR9ol9sUtA9PBgVENeSmPH3CncitY8S979CFELS6wH7kydcjPLB4QMOzA==
10460+
1044710461
neo4j-driver-core@^4.4.10:
1044810462
version "4.4.10"
1044910463
resolved "https://registry.yarnpkg.com/neo4j-driver-core/-/neo4j-driver-core-4.4.10.tgz#6f4c1ccc1199f864b149bdcef5e50e45ff95c29e"
@@ -10459,6 +10473,15 @@ neo4j-driver@^4.4.5:
1045910473
neo4j-driver-core "^4.4.10"
1046010474
rxjs "^6.6.3"
1046110475

10476+
neo4j-driver@^5.12.0:
10477+
version "5.12.0"
10478+
resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-5.12.0.tgz#1b2d7db1672ad224f0146542efee306a0a156a11"
10479+
integrity sha512-T2Vz63XDkL9TomM16dBusuXbo7d9SIGw2g3VR/rmrWTdbl1V1LYFx/u1P7AwBsFuX08oncKHfZwHGsWrCvdMyA==
10480+
dependencies:
10481+
neo4j-driver-bolt-connection "5.12.0"
10482+
neo4j-driver-core "5.12.0"
10483+
rxjs "^7.8.1"
10484+
1046210485
next-tick@1, next-tick@^1.0.0, next-tick@^1.1.0:
1046310486
version "1.1.0"
1046410487
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@@ -12288,6 +12311,13 @@ rxjs@^7.5.1, rxjs@^7.5.5, rxjs@^7.8.0:
1228812311
dependencies:
1228912312
tslib "^2.1.0"
1229012313

12314+
rxjs@^7.8.1:
12315+
version "7.8.1"
12316+
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
12317+
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
12318+
dependencies:
12319+
tslib "^2.1.0"
12320+
1229112321
sade@^1.7.3:
1229212322
version "1.8.1"
1229312323
resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"

0 commit comments

Comments
 (0)