Skip to content

Commit

Permalink
rework App.jsx and add configuration & oidc in index.js
Browse files Browse the repository at this point in the history
  • Loading branch information
RenauxLeaInsee committed Jun 4, 2024
1 parent 2fcd562 commit b6f0546
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 2,595 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sonor",
"version": "0.5.33",
"version": "0.5.34",
"private": true,
"dependencies": {
"@tanstack/react-query": "4.0.5",
Expand Down
5 changes: 3 additions & 2 deletions public/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"PEARL_JAM_URL": "http://localhost:7777",
"QUEEN_URL_BACK_END": "http://localhost:7777",
"QUEEN_URL_FRONT_END": "http://localhost:7777",

"ISSUER_URI": "http://localhost:7777",
"AUTHENTICATION_MODE": "anonymous",
"_AUTHENTICATION_MODE_COMMENT_": "Use 'keycloak' or 'anonymous'"
"OIDC_CLIENT_ID": "clientId",
"_AUTHENTICATION_MODE_COMMENT_": "Use 'oidc' or 'anonymous'"
}

56 changes: 28 additions & 28 deletions src/Authentication/oidc.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
import { createMockReactOidc } from "oidc-spa/mock/react";
import { createReactOidc } from "oidc-spa/react";
import { useEffect, useState } from "react";

const guestUser = {
inseegroupedefaut: [],
preferred_username: "Guest",
name: "Guest",
preferred_username: "anonymous",
name: "anonymous",
};

// const publicUrl = new URL(process.env.PUBLIC_URL!, window.location.href);
// const response = await fetch(`${publicUrl.origin}/configuration.json`);
// const configuration = await response.json();

// const isOidc = configuration.AUTHENTICATION_MODE === "oidc"
const isOidc = true

const getConfiguration = async () => {
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
const response = await fetch(`${publicUrl.origin}/configuration.json`)
const configuration = await response.json();
console.log("configuration", configuration)
return configuration
export const useConfiguration = () => {
const [configuration, setConfiguration] = useState()
useEffect(() => {
const getConfiguration = async () => {
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
const response = await fetch(`${publicUrl.origin}/configuration.json`)
const configuration = await response.json();
setConfiguration(configuration)
window.localStorage.setItem(
'AUTHENTICATION_MODE',
configuration.AUTHENTICATION_MODE,
);
window.localStorage.setItem('PEARL_JAM_URL', configuration.PEARL_JAM_URL);
window.localStorage.setItem('QUEEN_URL_BACK_END', configuration.QUEEN_URL_BACK_END);
window.localStorage.setItem('QUEEN_URL_FRONT_END', configuration.QUEEN_URL_FRONT_END);
}
getConfiguration()
}, [])

return configuration
}

export const createAppOidc = () => {
// const configuration = getConfiguration()
// console.log("dans oidc",configuration)

if (isOidc) {
export const createAppOidc = (configuration) => {
if(configuration && configuration.AUTHENTICATION_MODE === "oidc"){
return createReactOidc({
// issuerUri: configuration.ISSUER_URI,
// clientId: configuration.OIDC_CLIENT_ID,

issuerUri:"https://auth.insee.test/auth/realms/agents-insee-interne",
clientId:"localhost-frontend",

publicUrl: "/",
// extraQueryParams: { kc_idp_hint: import.meta.env.VITE_IDENTITY_PROVIDER },
extraQueryParams: {kc_idp_hint :"insee-ssp"}
issuerUri: configuration.ISSUER_URI,
clientId: configuration.OIDC_CLIENT_ID,
publicUrl: "/",
});
}

Expand Down
61 changes: 35 additions & 26 deletions src/Authentication/useAuth.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import { useEffect } from "react";
import { createAppOidc } from "./oidc.js";

export const { OidcProvider, prOidc, useOidc } = createAppOidc();

export const useHasRole = (role)=> {
const { oidcTokens } = useOidc({ assertUserLoggedIn: true });
return oidcTokens.decodedIdToken.inseegroupedefaut.includes(role);
};

export const useAccessToken = () => {
return useOidc({ assertUserLoggedIn: true }).oidcTokens.accessToken;
};

export const useUser = () => {
return useOidc({ assertUserLoggedIn: true }).oidcTokens.decodedIdToken;
};

export const useMaybeUser = () => {
return useOidc({ assertUserLoggedIn: false }).oidcTokens?.decodedIdToken;
};
import { createReactOidc } from "oidc-spa/react";

/**
* By default, without initialization we use a mock as a return of "useOidc"
*
* This object will be used for testing purpose to simulate authentication status
*/
const mockOidc = { login: () => {}, isUserLoggedIn: false, oidcTokens: {} }
// Global method that will be replaced when oidc is initialized
let useOidc = () => mockOidc;

/**
* Helper method used for tests, set a fake Oidc authentication state
*/
export const mockOidcForUser = () => {
window.localStorage.setItem("AUTHENTICATION_MODE", "oidc")
mockOidc.isUserLoggedIn = true
mockOidc.oidcTokens = {accessToken: '12031203'}
}
export const mockOidcFailed = () => {
mockOidc.isUserLoggedIn = false
mockOidc.oidcTokens = {}
}

export const useLogout = () => {
return useOidc({ assertUserLoggedIn: false }).logout;
};
/**
* Initialize oidc
*/
export function initializeOidc (config) {
const oidc = createReactOidc(config)
useOidc = oidc.useOidc
return oidc;
}

/**
* Retrieve authentication status based of Oidc
*/
export function useIsAuthenticated() {
const { login, isUserLoggedIn, oidcTokens } = useOidc({ assertUserLoggedIn: false });

Expand All @@ -32,11 +43,9 @@ export function useIsAuthenticated() {
return;
}
login({
doesCurrentHrefRequiresAuth: false,
doesCurrentHrefRequiresAuth: true,
});
}, [login]);

return { isAuthenticated: isUserLoggedIn, tokens: oidcTokens };
}

export const AuthProvider = OidcProvider;
88 changes: 31 additions & 57 deletions src/components/App/App.jsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,55 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { useIsAuthenticated } from "../../Authentication/useAuth";
import D from "../../i18n";
import View from "../View/View";
import DataFormatter from "../../utils/DataFormatter";
import { OIDC, ANONYMOUS } from "../../utils/constants.json";
import initConfiguration from "../../initConfiguration";
import D from "../../i18n";

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
contactFailed: false,
initialisationFailed: false,
data: null,
};
}
export const App = () => {
const [authenticated, setAuthenticated] = useState(false);
const [contactFailed, setContactFailed] = useState(false);
const [data, setData] = useState(null);

async componentDidMount() {
try {
await initConfiguration();
} catch (e) {
this.setState({ initialisationFailed: true });
}
const { tokens } = useIsAuthenticated();

useEffect(() => {
if (window.localStorage.getItem("AUTHENTICATION_MODE") === ANONYMOUS) {
const dataRetreiver = new DataFormatter();
dataRetreiver.getUserInfo((data) => {
if (data.error) {
this.setState({ contactFailed: true });
setContactFailed(true);
} else {
this.setState({ authenticated: true, data });
setAuthenticated(true);
setData(data);
}
});
} else if (
window.localStorage.getItem("AUTHENTICATION_MODE") === OIDC &&
this.props.token
tokens?.accessToken
) {
const dataRetreiver = new DataFormatter(this.props.token);
const dataRetreiver = new DataFormatter(tokens.accessToken);
dataRetreiver.getUserInfo((data) => {
this.setState({ authenticated: data !== undefined, data });
setAuthenticated(data !== undefined);
setData(data);
});
}
}
}, [tokens]);

async componentDidUpdate(prevprops, prevstate) {
const { token } = this.props;

if (
(token && token !== prevprops.token) ||
this.state.authenticated !== prevstate.authenticated
) {
const dataRetreiver = new DataFormatter(this.props.token);
dataRetreiver.getUserInfo((data) => {
this.setState({ authenticated: data !== undefined, data });
});
}
if (!tokens?.accessToken) {
return <div>{D.initializationFailed}</div>;
}

render() {
const { authenticated, data, contactFailed, initialisationFailed } =
this.state;

if (authenticated) {
return (
<div className="App">
<View token={this.props.token} userData={data} />
</div>
);
}
if (initialisationFailed) {
return <div>{D.initializationFailed}</div>;
}
if (contactFailed) {
return <div>{D.cannotContactServer}</div>;
}
if (authenticated && tokens?.accessToken && data) {
return (
<div className="App">
<View token={tokens.accessToken} userData={data} />
</div>
);
}

return <div>{D.initializing}</div>;
if (contactFailed) {
return <div>{D.cannotContactServer}</div>;
}
}

export default App;
return <div>{D.initializing}</div>;
};
69 changes: 10 additions & 59 deletions src/components/App/App.test.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Link.react.test.js
import React from 'react';
import {
render, screen, cleanup, waitForElement,
} from '@testing-library/react';
import Keycloak from 'keycloak-js';
import { render, screen, cleanup } from "@testing-library/react";
import { NotificationManager } from 'react-notifications';
import DataFormatter from '../../utils/DataFormatter';
import App from './App';
import { App } from "./App";
import mocks from '../../tests/mocks';
import C from '../../utils/constants.json';
import { mockOidcForUser, mockOidcFailed } from "../../Authentication/useAuth";

jest.mock(
"../../../package.json",
Expand All @@ -29,7 +27,6 @@ Date.now = jest.fn(() => 1597916474000);

afterEach(cleanup);

jest.mock('keycloak-js');
jest.mock('react-notifications');
jest.mock('../../utils/DataFormatter');
jest.mock('../../initConfiguration');
Expand All @@ -53,15 +50,6 @@ const mockError = jest.fn();
NotificationManager.success = mockSuccess;
NotificationManager.error = mockError;

Keycloak.init = jest.fn(() => (Promise.resolve({ token: 'abc' })));

Keycloak.mockImplementation(() => ({
init: jest.fn(() => (Promise.resolve({ token: 'abc' }))),
updateToken: (() => ({ error: (() => {}) })),
tokenParsed: { exp: 300 },
timeSkew: 0,
}));

const updatePreferences = jest.fn((newPrefs, cb) => {
if (newPrefs.includes('simpsonkgs2020x00')) {
cb({ status: 500 });
Expand Down Expand Up @@ -104,52 +92,15 @@ DataFormatter.mockImplementation(() => ({
updatePreferences,
}));

it('Component is displayed (initializing)', async () => {
const component = render(
<App />,
);
// Should match snapshot
expect(component).toMatchSnapshot();
});

it('Component is displayed (anonymous mode)', async () => {
Object.getPrototypeOf(window.localStorage).getItem = jest.fn(() => ('anonymous'));
const component = render(
<App />,
);

await waitForElement(() => screen.getByTestId('pagination-nav'));

// Should match snapshot
it("Component is displayed ", async () => {
mockOidcForUser();
const component = render(<App />);
await screen.findByText("List of surveys");
expect(component).toMatchSnapshot();
});

it('Component is displayed (keycloak mode)', async () => {
Object.getPrototypeOf(window.localStorage).getItem = jest.fn(() => ('keycloak'));
const component = render(
<App />,
);

await waitForElement(() => screen.getByTestId('pagination-nav'));

// Should match snapshot
expect(component).toMatchSnapshot();
});

it('Could not authenticate with keycloak', async () => {
Object.getPrototypeOf(window.localStorage).getItem = jest.fn(() => ('keycloak'));

Keycloak.mockImplementation(() => ({
init: jest.fn(() => (Promise.resolve(false))),
updateToken: (() => ({ error: (() => {}) })),
tokenParsed: { exp: 300 },
timeSkew: 0,
}));

const component = render(
<App />,
);

// Should match snapshot
it("Component is not displayed ", async () => {
mockOidcFailed();
const component = render(<App />);
expect(component).toMatchSnapshot();
});
Loading

0 comments on commit b6f0546

Please sign in to comment.