Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions apps/model_catalog/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/model_catalog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"eslint-plugin-react-hooks": "^4.2.0",
"filesize": "^6.1.0",
"humanparser": "^1.11.0",
"keycloak-js": "^8.0.1",
"keycloak-js": "^12.0.2",
"lodash": "^4.17.20",
"material-ui-chip-input": "^2.0.0-beta.2",
"moment": "^2.29.1",
Expand Down
45 changes: 45 additions & 0 deletions apps/model_catalog/src/AuthWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import PersonIcon from '@material-ui/icons/Person';
import Tooltip from "@material-ui/core/Tooltip";
import ContextMain from "./ContextMain";


function AuthWidget(props) {

const context = React.useContext(ContextMain);
const [auth] = context.auth;

if (auth.authenticated || props.currentUser) {

if (!props.currentUser) {
auth.loadUserInfo().then((userInfo) => {
console.log(userInfo);
props.setCurrentUser(userInfo.preferred_username);
});
};

return (
<Tooltip title={props.currentUser || "anonymous"}>
<IconButton variant="outlined">
<PersonIcon />
</IconButton>
</Tooltip>
);
} else {
return (
<Button
variant="outlined"
color="primary"
disableElevation
size="small"
onClick={auth.login} // todo: login with scopes
>
Login
</Button>
);
}
}

export default AuthWidget;
4 changes: 0 additions & 4 deletions apps/model_catalog/src/Introduction.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ import "./App.css";

const ResponsiveEllipsis = responsiveHOC()(LinesEllipsis);

// const openInNewTab = (url) => {
// const newWindow = window.open(url, "_blank", "noopener,noreferrer");
// if (newWindow) newWindow.opener = null;
// };

const styles = (theme) => ({
header: {
Expand Down
6 changes: 6 additions & 0 deletions apps/model_catalog/src/ValidationFramework.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import ContextMain from "./ContextMain";
import Theme from "./theme";
import { withSnackbar } from "notistack";
import WarningBox from "./WarningBox";
import AuthWidget from "./AuthWidget";

// if working on the appearance/layout set globals.DevMode=true
// to avoid loading the models and tests over the network every time;
Expand Down Expand Up @@ -129,6 +130,7 @@ class ValidationFramework extends React.Component {
this.state = {
modelData: [],
testData: [],
currentUser: null,
currentModel: null,
currentTest: null,
currentResult: null,
Expand Down Expand Up @@ -1174,6 +1176,10 @@ class ValidationFramework extends React.Component {
</IconButton>
</a>
</Tooltip>
<AuthWidget
currentUser={this.state.currentUser}
setCurrentUser={(user) => this.setState({currentUser: user})}
/>
</div>
</div>
</div>
Expand Down
114 changes: 114 additions & 0 deletions apps/model_catalog/src/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Keycloak from 'keycloak-js';


// We start by configuring the Keycloak javascript client
// It needs to know your app id in order to authenticate users for it
const keycloak = Keycloak({
url: 'https://iam.ebrains.eu/auth',
realm: 'hbp',
clientId: 'model-catalog',
'public-client': true,
'confidential-port': 0,
});
const YOUR_APP_SCOPES = 'team email profile'; // full list at https://iam.ebrains.eu/auth/realms/hbp/.well-known/openid-configuration


export default function initAuth(main) {
console.log('DOM content is loaded, initialising Keycloak client...');
keycloak
//.init({ flow: 'standard', pkceMethod: 'S256' })
.init({ flow: 'implicit' })
.then(() => checkAuth(main))
.catch(console.log);
}

function checkAuth(main) {
console.log('Keycloak client is initialised, verifying authentication...');

// Is the user anonymous or authenticated?
const isAuthenticated = keycloak.authenticated;
const isAnonymous = !keycloak.authenticated;
// Is this app a standalone app, a framed app or a delegate?
const isParent = (window.opener == null);
const isIframe = (window !== window.parent);
const isMainFrame = (window === window.parent);
const isStandaloneApp = isMainFrame && isParent;
const isFramedApp = isIframe && isParent;
const isDelegate = (window.opener != null);
// Posting and listening to messages
const postMessageToParentTab = (message, parentTabOrigin) => window.opener.postMessage(message, parentTabOrigin);
const listenToMessage = (callback) => window.addEventListener('message', callback);
const AUTH_MESSAGE = 'clb.authenticated';
const myAppOrigin = window.location.origin;
// Manipulating URLs and tabs
const openTab = (url) => window.open(url);
const getCurrentURL = () => new URL(window.location);
const closeCurrentTab = () => window.close();

const login = (scopes) => keycloak.login({ scope: scopes });

// A standalone app should simply login if the user is not authenticated
// and do its business logic otherwise
if (isStandaloneApp) {
console.log('This is a standalone app...');
if (isAnonymous) {
console.log('...which is not authenticated, starting app without authentication')
return main(keycloak);
}
if (isAuthenticated) {
console.log('...which is authenticated, starting app with authentication');
return main(keycloak);
}
}

// A framed app should open a delegate to do the authentication for it and listen to its messages and verify them
// If the user is authenticated, it should do its business logic
if (isFramedApp) {
console.log('This is a framed app...');
if (isAnonymous) {
console.log('...which is not authenticated, delegating to new tab...');
listenToMessage(verifyMessage);
return openTab(getCurrentURL());
}
if (isAuthenticated) {
console.log('...which is authenticated, starting business logic...');
return main(keycloak);
}
}

// A delegate should login if the user is not authenticated
// Otherwise, it should inform its opener that the user is authenticated and close itself
if (isDelegate) {
console.log('This is a delegate tab...');
if (isAnonymous) {
console.log('...which is not authenticated, starting login...');
return login(YOUR_APP_SCOPES);
}
if (isAuthenticated) {
console.log('...which is authenticated, warn parent and close...');
postMessageToParentTab(AUTH_MESSAGE, myAppOrigin);
return closeCurrentTab();
}
}
}

function verifyMessage(event) {
console.log('Message receveived, verifying it...');

const AUTH_MESSAGE = 'clb.authenticated';
const receivedMessage = event.data;
const messageOrigin = event.origin;
const myAppOrigin = window.location.origin;
// const reload = () => window.location.reload(); // TODO: remove?
const login = (scopes) => keycloak.login({ scope: scopes });


// Stop if the message is not the auth message
if (receivedMessage !== AUTH_MESSAGE) return;

// Stop if the message is not coming from our app origin
if (messageOrigin !== myAppOrigin) return;

// Login otherwise
return login(YOUR_APP_SCOPES);
}
133 changes: 9 additions & 124 deletions apps/model_catalog/src/index.js
Original file line number Diff line number Diff line change
@@ -1,136 +1,21 @@
import Keycloak from "keycloak-js";
import React from "react";
import ReactDOM from "react-dom";

import { SnackbarProvider } from "notistack";
import React from 'react';
import ReactDOM from 'react-dom';
import initAuth from './auth';
import { datastore } from "./datastore";
import { ContextMainProvider } from "./ContextMain";
import { SnackbarProvider } from "notistack";
import ValidationFramework from "./ValidationFramework";

// We start by configuring the Keycloak javascript client
// It needs to know your app id in order to authenticate users for it
const keycloak = Keycloak({
url: "https://iam.ebrains.eu/auth",
realm: "hbp",
clientId: "model-catalog", // TODO: change clientID to validation-framework, once client is registered
});
const YOUR_APP_SCOPES = "team email profile"; // full list at https://iam.ebrains.eu/auth/realms/hbp/.well-known/openid-configuration

// When ready, we initialise the keycloak client
// Once done, it will call our `checkAuth` function
window.addEventListener("DOMContentLoaded", initKeycloak);

function initKeycloak() {
console.log("DOM content is loaded, initialising Keycloak client...");
keycloak.init({ flow: "implicit" }).success(checkAuth).error(console.log);
}

function checkAuth() {
console.log("Keycloak client is initialised, verifying authentication...");

// Is the user anonymous or authenticated?
const isAuthenticated = keycloak.authenticated;
const isAnonymous = !keycloak.authenticated;
// Is this app a standalone app, a framed app or a delegate?
const isParent = window.opener == null;
const isIframe = window !== window.parent;
const isMainFrame = window === window.parent;
const isStandaloneApp = isMainFrame && isParent;
const isFramedApp = isIframe && isParent;
const isDelegate = window.opener != null;
// Posting and listening to messages
const postMessageToParentTab = (message, parentTabOrigin) =>
window.opener.postMessage(message, parentTabOrigin);
const listenToMessage = (callback) =>
window.addEventListener("message", callback);
const AUTH_MESSAGE = "clb.authenticated";
const myAppOrigin = window.location.origin;
// Manipulating URLs and tabs
const openTab = (url) => window.open(url);
const getCurrentURL = () => new URL(window.location);
const closeCurrentTab = () => window.close();

const login = (scopes) => keycloak.login({ scope: scopes });

// A standalone app should simply login if the user is not authenticated
// and do its business logic otherwise
if (isStandaloneApp) {
console.log("This is a standalone app...");
if (isAnonymous) {
console.log("...which is not authenticated, starting login...");
return login(YOUR_APP_SCOPES);
}
if (isAuthenticated) {
console.log(
"...which is authenticated, starting business logic..."
);
return doBusinessLogic(keycloak);
}
}

// A framed app should open a delegate to do the authentication for it and listen to its messages and verify them
// If the user is authenticated, it should do its business logic
if (isFramedApp) {
console.log("This is a framed app...");
if (isAnonymous) {
console.log(
"...which is not authenticated, delegating to new tab..."
);
listenToMessage(verifyMessage);
return openTab(getCurrentURL());
}
if (isAuthenticated) {
console.log(
"...which is authenticated, starting business logic..."
);
return doBusinessLogic(keycloak);
}
}

// A delegate should login if the user is not authenticated
// Otherwise, it should inform its opener that the user is authenticated and close itself
if (isDelegate) {
console.log("This is a delegate tab...");
if (isAnonymous) {
console.log("...which is not authenticated, starting login...");
return login(YOUR_APP_SCOPES);
}
if (isAuthenticated) {
console.log("...which is authenticated, warn parent and close...");
postMessageToParentTab(AUTH_MESSAGE, myAppOrigin);
return closeCurrentTab();
}
}
}

function verifyMessage(event) {
console.log("Message receveived, verifying it...");

const AUTH_MESSAGE = "clb.authenticated";
const receivedMessage = event.data;
const messageOrigin = event.origin;
const myAppOrigin = window.location.origin;
// const reload = () => window.location.reload(); // TODO: remove?
const login = (scopes) => keycloak.login({ scope: scopes });

// Stop if the message is not the auth message
if (receivedMessage !== AUTH_MESSAGE) return;

// Stop if the message is not coming from our app origin
if (messageOrigin !== myAppOrigin) return;

// Login otherwise
return login(YOUR_APP_SCOPES);
}

function doBusinessLogic(auth) {
function renderApp(auth) {
datastore.auth = auth;
ReactDOM.render(
<ContextMainProvider>
<SnackbarProvider maxSnack={3}>
<ValidationFramework auth={auth} />
</SnackbarProvider>
</ContextMainProvider>,
document.getElementById("root")
document.getElementById('root')
);
}
};

window.addEventListener('DOMContentLoaded', () => initAuth(renderApp));