Skip to content
Snippets Groups Projects
Commit 823da861 authored by thijsheijden's avatar thijsheijden
Browse files

feat(authorization): worked on adding the popup sign-in window

parent 6c880f69
No related branches found
No related tags found
2 merge requests!13merge develop into main,!8feat(authorization): first version that works well
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { AuthorizationHandler } from '@graphpolaris/shared/data-access/authorization';
import {
assignNewGraphQueryResult,
useAppDispatch,
} from '@graphpolaris/shared/data-access/store';
import { useEffect, useState } from 'react';
import GridLayout from 'react-grid-layout';
import Panel from '../web/components/panels/panel';
import { RawJSONVis } from '../web/components/visualisations/rawjsonvis/rawjsonvis';
import Panel from '../web/components/panels/Panel';
import { RawJSONVis } from '../web/components/visualisations/RawJSONVis/rawjsonvis';
import LoginScreen from '../web/components/login/loginScreen';
export function App() {
const dispatch = useAppDispatch();
const [userAuthorized, setUserAuthorized] = useState<boolean>(false);
// Attempt to Authorize the user
const authorize = async () => {
const authorized = await AuthorizationHandler.instance().Authorize();
console.log('User authorized: ' + authorized);
// If user is authorized don't show login screen
if (authorized) {
setUserAuthorized(true);
}
};
useEffect(() => {
authorize();
}, []);
return (
<GridLayout
className="layout"
cols={10}
rowHeight={30}
width={window.innerWidth}
>
<div
key="schema-panel"
data-grid={{ x: 0, y: 0, w: 3, h: 30, maxW: 5, isDraggable: false }}
>
<Panel content="Schema Panel" color="red"></Panel>
</div>
<div
key="query-panel"
data-grid={{ x: 3, y: 20, w: 5, h: 10, maxH: 20, isDraggable: false }}
>
<Panel content="Query Panel" color="blue"></Panel>
</div>
<div
key="visualisation-panel"
data-grid={{ x: 3, y: 0, w: 7, h: 20, isDraggable: false }}
>
<Panel content="Visualisation Panel" color="green">
<div>
<button
onClick={() =>
dispatch(
assignNewGraphQueryResult({
nodes: [
{ id: 'agent/007', attributes: { name: 'Daniel Craig' } },
],
links: [],
})
)
}
>
Load in mock result
</button>
<button
onClick={() =>
dispatch(assignNewGraphQueryResult({ nodes: [], links: [] }))
}
>
Remove mock result
</button>
</div>
<RawJSONVis />
<div />
</Panel>
</div>
<div
key="history-panel"
data-grid={{ x: 8, y: 20, w: 2, h: 10, isDraggable: false }}
<>
{!userAuthorized && <LoginScreen />}
<GridLayout
className="layout"
cols={10}
rowHeight={30}
width={window.innerWidth}
style={{ zIndex: 0 }}
>
<Panel content="History Channel" color="purple"></Panel>
</div>
</GridLayout>
<div
key="schema-panel"
data-grid={{
x: 0,
y: 0,
w: 3,
h: 30,
maxW: 5,
isDraggable: false,
}}
>
<Panel content="Schema Panel" color="white"></Panel>
</div>
<div
key="query-panel"
data-grid={{
x: 3,
y: 20,
w: 5,
h: 10,
maxH: 20,
isDraggable: false,
}}
>
<Panel content="Query Panel" color="white"></Panel>
</div>
<div
key="visualisation-panel"
data-grid={{ x: 3, y: 0, w: 7, h: 20, isDraggable: false }}
>
<Panel content="Visualisation Panel" color="white">
<div>
<button
onClick={() =>
dispatch(
assignNewGraphQueryResult({
nodes: [
{
id: 'agent/007',
attributes: { name: 'Daniel Craig' },
},
],
links: [],
})
)
}
>
Load in mock result
</button>
<button
onClick={() =>
dispatch(assignNewGraphQueryResult({ nodes: [], links: [] }))
}
>
Remove mock result
</button>
</div>
<RawJSONVis />
<div />
</Panel>
</div>
<div
key="history-panel"
data-grid={{ x: 8, y: 20, w: 2, h: 10, isDraggable: false }}
>
<Panel content="History Channel" color="white"></Panel>
</div>
</GridLayout>
</>
);
}
......
apps/graphpolaris/src/assets/login-screen/github.png

7.04 KiB

apps/graphpolaris/src/assets/login-screen/google.png

7.81 KiB

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { store } from '@graphpolaris/shared/data-access/store';
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './app/app';
import LoginPopupComponent from './web/components/login/popup';
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
<Router>
<Routes>
{/* Route to auth component in popup */}
<Route path="/auth" element={<LoginPopupComponent />}></Route>
{/* App */}
<Route path="/" element={<App />}></Route>
</Routes>
</Router>
</Provider>
</StrictMode>,
document.getElementById('root')
......
import styled from 'styled-components';
const Wrapper = styled.div`
font-family: 'Arial';
position: absolute;
left: 0;
top: 0;
// Cover the screen
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
`;
const Background = styled.div`
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
// Blur
background: rgba(
0,
0,
0,
0.4
); // Make sure this color has an opacity of less than 1
backdrop-filter: blur(8px); // This be the blur
`;
const Content = styled.div`
background-color: white;
box-shadow: 0 3px 10px rgb(0 0 0 / 0.2);
padding: 2em;
z-index: 2;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 1em;
align-items: center;
justify-content: center;
// Give children 0 padding and margin
* {
display: flex;
margin: 0;
padding: 0;
// Same width flexbox items
flex: 1 1 0px;
max-height: 3em;
&:hover {
cursor: pointer;
}
}
`;
const LoginScreen = () => {
const openSignInWindow = (url: string) => {
// remove any existing event listeners
window.removeEventListener('message', receiveMessage);
// window features
const strWindowFeatures =
'toolbar=no, menubar=no, width=600, height=700, top=100, left=100';
window.open(url, 'Google Oauth', strWindowFeatures);
// add the listener for receiving a message from the popup
window.addEventListener('message', (event) => receiveMessage(event), false);
};
const receiveMessage = (event: any) => {
// Do we trust the sender of this message? (might be
// different from what we originally opened)
if (event.origin !== 'http://localhost:4200') {
return;
}
console.log(event);
};
return (
<Wrapper>
<Background></Background>
<Content>
<h1>Sign In</h1>
<img
onClick={() =>
openSignInWindow('http://localhost:3000/sign-in?provider=1')
}
src="assets/login-screen/google.png"
alt="sign up with google"
/>
<img
onClick={() =>
openSignInWindow('http://localhost:3000/sign-in?provider=2')
}
src="assets/login-screen/github.png"
alt="sign up with github"
/>
<p
onClick={() =>
openSignInWindow(
'https://datastrophe.science.uu.nl/user/create_free/'
)
}
>
Developer
</p>
</Content>
</Wrapper>
);
};
export default LoginScreen;
const LoginPopupComponent = () => {
if (window.opener) {
// Get the access token from the query params
const urlParams = new URLSearchParams(window.location.search);
const accessToken = urlParams.get('access_token');
// Send the access token to the parent window
window.opener.postMessage(accessToken, '*');
// Close this window
window.close();
}
return <h1>Loading...</h1>;
};
export default LoginPopupComponent;
......@@ -8,7 +8,10 @@ interface Props {
const Wrapper = styled.div<{ color: string }>`
background-color: ${(props) => props.color};
font: 'Arial';
font-family: 'Arial';
// Light shadow
box-shadow: 0 3px 10px rgb(0 0 0 / 0.2);
height: 100%;
width: 100%;
......
export * from './lib/authorizationHandler';
export { AuthorizationHandler } from './lib/authorizationHandler';
export const test = 'hey!';
export class AuthorizationHandler {
private static _instance: AuthorizationHandler;
private accessToken = '';
private authorized = false;
private userID = '';
private sessionID = '';
// instance gets the AuthorizationHandler singleton instance
public static instance(): AuthorizationHandler {
if (!AuthorizationHandler._instance) {
AuthorizationHandler._instance = new AuthorizationHandler();
}
return AuthorizationHandler._instance;
}
// MARK: Authorization code
/**
* Authorize attempts to authorize using a refresh-token set as a cookie. If the user has been inactive for more than 7 days this cookie will be gone.
* @returns true is authorization was successful, else returns false
*/
public async Authorize(): Promise<boolean> {
// Attempt to log in with a refresh-token
const authResponse = await this.getNewAccessToken();
// If the request was a success, we have an accessToken, userID and sessionID
if (authResponse.success) {
// Store them
this.accessToken = authResponse.accessToken ?? '';
this.userID = authResponse.userID ?? '';
this.sessionID = authResponse.sessionID ?? '';
// Start the automatic refreshing every 10 minutes
setInterval(() => {
this.refreshTokens();
}, 10 * 60 * 1000);
}
return new Promise((resolve) => {
resolve(authResponse.success);
});
}
/**
* getNewAccessToken gets a new access token using the refresh-token cookie
* @returns an authResponse containing details
*/
private async getNewAccessToken(): Promise<authResponse> {
// If we have an access token already, append it to the url as a query param to keep sessionID the same
let url = 'https://datastrophe.science.uu.nl/auth/refresh';
if (this.accessToken != '') {
url += '?access_token=' + this.accessToken;
}
return new Promise<authResponse>((resolve) => {
fetch(url, {
method: 'GET',
credentials: 'include',
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.then((responseJSON) => {
resolve({
success: true,
accessToken: responseJSON.accessToken,
userID: responseJSON.userID,
sessionID: responseJSON.sessionID,
});
})
.catch(() => {
// User is not authorized
resolve({ success: false });
});
});
}
/**
* refreshTokens refreshes tokens
*/
private async refreshTokens() {
// Get a new access + refresh token pair
const authResponse = await this.getNewAccessToken();
if (authResponse.success) {
// Set the new access token
this.accessToken = authResponse.accessToken ?? '';
// Initialise the new refresh token
this.initialiseRefreshToken();
}
}
/**
* initialiseRefreshToken attempts to initialise a refresh token
*/
private async initialiseRefreshToken() {
fetch('https://datastrophe.science.uu.nl/auth/refresh', {
method: 'POST',
credentials: 'include',
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
})
.catch((error) => {
console.error(error);
});
}
// MARK: Getters
/**
* Authorized returns the current authorization status
* @returns true if authorized
*/
Authorized(): boolean {
return this.authorized;
}
/**
* AccessToken returns the current access token
* @returns token
*/
AccessToken(): string {
return this.accessToken;
}
/**
* UserID returns the current user' ID
* @returns id
*/
UserID(): string {
return this.userID;
}
/**
* SessionID returns the current session' ID
* @returns id
*/
SessionID(): string {
return this.sessionID;
}
}
type authResponse = {
success: boolean;
accessToken?: string;
userID?: string;
sessionID?: string;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment