Gestion des autorisations côté serveur #20
|
@ -1,4 +1,4 @@
|
||||||
import React, { FunctionComponent, useState } from 'react';
|
import React, { FunctionComponent, useState, useEffect } from 'react';
|
||||||
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
||||||
import { HomePage } from './HomePage/HomePage';
|
import { HomePage } from './HomePage/HomePage';
|
||||||
import { ProfilePage } from './ProfilePage/ProfilePage';
|
import { ProfilePage } from './ProfilePage/ProfilePage';
|
||||||
|
@ -6,44 +6,76 @@ import { WorkgroupPage } from './WorkgroupPage/WorkgroupPage';
|
||||||
import { DecisionSupportFilePage } from './DecisionSupportFilePage/DecisionSupportFilePage';
|
import { DecisionSupportFilePage } from './DecisionSupportFilePage/DecisionSupportFilePage';
|
||||||
import { DashboardPage } from './DashboardPage/DashboardPage';
|
import { DashboardPage } from './DashboardPage/DashboardPage';
|
||||||
import { useUserProfile } from '../gql/queries/profile';
|
import { useUserProfile } from '../gql/queries/profile';
|
||||||
import { LoggedInContext } from '../hooks/useLoggedIn';
|
import { LoggedInContext, getSavedLoggedIn, saveLoggedIn } from '../hooks/useLoggedIn';
|
||||||
import { PrivateRoute } from './PrivateRoute';
|
import { PrivateRoute } from './PrivateRoute';
|
||||||
import { useKonamiCode } from '../hooks/useKonamiCode';
|
import { useKonamiCode } from '../hooks/useKonamiCode';
|
||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
|
import { createClient } from '../util/apollo';
|
||||||
|
import { ApolloProvider } from '@apollo/client';
|
||||||
|
import { LogoutPage } from './LogoutPage';
|
||||||
|
|
||||||
export interface AppProps {
|
export interface AppProps {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const App: FunctionComponent<AppProps> = () => {
|
export const App: FunctionComponent<AppProps> = () => {
|
||||||
const { user } = useUserProfile();
|
const [ loggedIn, setLoggedIn ] = useState(getSavedLoggedIn());
|
||||||
|
|
||||||
|
const client = createClient((loggedIn) => {
|
||||||
|
setLoggedIn(loggedIn);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
saveLoggedIn(loggedIn);
|
||||||
|
}, [loggedIn]);
|
||||||
|
|
||||||
const [ showBoneyM, setShowBoneyM ] = useState(false);
|
const [ showBoneyM, setShowBoneyM ] = useState(false);
|
||||||
useKonamiCode(() => setShowBoneyM(true));
|
useKonamiCode(() => setShowBoneyM(true));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoggedInContext.Provider value={user.id !== ''}>
|
<ApolloProvider client={client}>
|
||||||
<BrowserRouter>
|
<LoggedInContext.Provider value={loggedIn}>
|
||||||
<Switch>
|
<UserSessionCheck setLoggedIn={setLoggedIn} />
|
||||||
<Route path="/" exact component={HomePage} />
|
<BrowserRouter>
|
||||||
<PrivateRoute path="/profile" exact component={ProfilePage} />
|
<Switch>
|
||||||
<PrivateRoute path="/workgroups/:id" exact component={WorkgroupPage} />
|
<Route path="/" exact component={HomePage} />
|
||||||
<PrivateRoute path="/decisions/:id" exact component={DecisionSupportFilePage} />
|
<PrivateRoute path="/profile" exact component={ProfilePage} />
|
||||||
<PrivateRoute path="/dashboard" exact component={DashboardPage} />
|
<PrivateRoute path="/workgroups/:id" exact component={WorkgroupPage} />
|
||||||
<Route component={() => <Redirect to="/" />} />
|
<PrivateRoute path="/decisions/:id" exact component={DecisionSupportFilePage} />
|
||||||
</Switch>
|
<PrivateRoute path="/dashboard" exact component={DashboardPage} />
|
||||||
</BrowserRouter>
|
<PrivateRoute path="/logout" exact component={LogoutPage} />
|
||||||
{
|
<Route component={() => <Redirect to="/" />} />
|
||||||
showBoneyM ?
|
</Switch>
|
||||||
<Modal active={true} showCloseButton={true} onClose={() => setShowBoneyM(false)}>
|
</BrowserRouter>
|
||||||
<iframe width={560} height={315}
|
{
|
||||||
frameBorder={0}
|
showBoneyM ?
|
||||||
allowFullScreen={true}
|
<Modal active={true} showCloseButton={true} onClose={() => setShowBoneyM(false)}>
|
||||||
src="https://www.youtube.com/embed/uVzT5QEEQ2c?autoplay=1" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture">
|
<iframe width={560} height={315}
|
||||||
</iframe>
|
frameBorder={0}
|
||||||
</Modal> :
|
allowFullScreen={true}
|
||||||
null
|
src="https://www.youtube.com/embed/uVzT5QEEQ2c?autoplay=1" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture">
|
||||||
}
|
</iframe>
|
||||||
</LoggedInContext.Provider>
|
</Modal> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</LoggedInContext.Provider>
|
||||||
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UserSessionCheckProps {
|
||||||
|
setLoggedIn: (boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserSessionCheck: FunctionComponent<UserSessionCheckProps> = ({ setLoggedIn }) => {
|
||||||
|
const { user, loading } = useUserProfile();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loading) return;
|
||||||
|
setLoggedIn(user.id !== '');
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
|
@ -1,10 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { } from 'react';
|
||||||
import { Workgroup, inWorkgroup } from '../../types/workgroup';
|
import { Workgroup, inWorkgroup } from '../../types/workgroup';
|
||||||
import { User } from '../../types/user';
|
import { useWorkgroups } from '../../gql/queries/workgroups';
|
||||||
import { Link } from 'react-router-dom';
|
import { useUserProfile } from '../../gql/queries/profile';
|
||||||
import { useWorkgroupsQuery, useWorkgroups } from '../../gql/queries/workgroups';
|
|
||||||
import { useUserProfileQuery, useUserProfile } from '../../gql/queries/profile';
|
|
||||||
import { WithLoader } from '../WithLoader';
|
|
||||||
import { ItemPanel, Item } from './ItemPanel';
|
import { ItemPanel, Item } from './ItemPanel';
|
||||||
|
|
||||||
export function WorkgroupsPanel() {
|
export function WorkgroupsPanel() {
|
||||||
|
|
|
@ -3,14 +3,15 @@ import { Page } from '../Page';
|
||||||
import { WelcomeContent } from './WelcomeContent';
|
import { WelcomeContent } from './WelcomeContent';
|
||||||
import { useUserProfile } from '../../gql/queries/profile';
|
import { useUserProfile } from '../../gql/queries/profile';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
import { useLoggedIn } from '../../hooks/useLoggedIn';
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
const { user } = useUserProfile();
|
const loggedIn = useLoggedIn();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user.id !== '') history.push('/dashboard');
|
if (loggedIn) history.push('/dashboard');
|
||||||
}, [user.id])
|
}, [loggedIn])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title="Accueil">
|
<Page title="Accueil">
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React, { FunctionComponent, useEffect } from "react";
|
||||||
|
import { saveLoggedIn } from "../hooks/useLoggedIn";
|
||||||
|
import { Config } from "../config";
|
||||||
|
|
||||||
|
export interface LogoutPageProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LogoutPage: FunctionComponent<LogoutPageProps> = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
saveLoggedIn(false);
|
||||||
|
window.location.replace(Config.logoutURL);
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
};
|
|
@ -44,11 +44,11 @@ export function Navbar() {
|
||||||
</span>
|
</span>
|
||||||
<span>Mon profil</span>
|
<span>Mon profil</span>
|
||||||
</Link>
|
</Link>
|
||||||
<a className="button is-warning" href={Config.logoutURL}>
|
<Link className="button is-warning" to="/logout">
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<i className="fas fa-sign-out-alt"></i>
|
<i className="fas fa-sign-out-alt"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</Link>
|
||||||
</Fragment> :
|
</Fragment> :
|
||||||
<a className="button is-primary" href={Config.loginURL}>
|
<a className="button is-primary" href={Config.loginURL}>
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
|
|
||||||
import { Config } from '../config';
|
|
||||||
import { WebSocketLink } from "@apollo/client/link/ws";
|
|
||||||
import { RetryLink } from "@apollo/client/link/retry";
|
|
||||||
import { SubscriptionClient } from "subscriptions-transport-ws";
|
|
||||||
import { User } from '../types/user';
|
|
||||||
|
|
||||||
const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, {
|
|
||||||
reconnect: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const link = new RetryLink({attempts: {max: 2}}).split(
|
|
||||||
(operation) => operation.operationName === 'subscription',
|
|
||||||
new WebSocketLink(subscriptionClient),
|
|
||||||
new HttpLink({ uri: Config.graphQLEndpoint, credentials: 'include' })
|
|
||||||
);
|
|
||||||
|
|
||||||
const cache = new InMemoryCache({
|
|
||||||
typePolicies: {
|
|
||||||
Workgroup: {
|
|
||||||
fields: {
|
|
||||||
members: {
|
|
||||||
merge: mergeArrayByField<User>("id"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const client = new ApolloClient<any>({
|
|
||||||
cache: cache,
|
|
||||||
link: link,
|
|
||||||
});
|
|
||||||
|
|
||||||
function mergeArrayByField<T>(fieldName: string) {
|
|
||||||
return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => {
|
|
||||||
|
|
||||||
const merged: any[] = existing ? existing.slice(0) : [];
|
|
||||||
|
|
||||||
const objectFieldToIndex: Record<string, number> = Object.create(null);
|
|
||||||
if (existing) {
|
|
||||||
existing.forEach((obj, index) => {
|
|
||||||
objectFieldToIndex[readField(fieldName, obj)] = index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
incoming.forEach(obj => {
|
|
||||||
const field = readField(fieldName, obj);
|
|
||||||
const index = objectFieldToIndex[field];
|
|
||||||
if (typeof index === "number") {
|
|
||||||
merged[index] = mergeObjects(merged[index], obj);
|
|
||||||
} else {
|
|
||||||
objectFieldToIndex[name] = merged.length;
|
|
||||||
merged.push(obj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,23 @@
|
||||||
import React, { useState, useContext } from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
|
|
||||||
export const LoggedInContext = React.createContext(false);
|
const LOGGED_IN_KEY = 'loggedIn';
|
||||||
|
|
||||||
|
export const LoggedInContext = React.createContext(getSavedLoggedIn());
|
||||||
|
|
||||||
export const useLoggedIn = () => {
|
export const useLoggedIn = () => {
|
||||||
return useContext(LoggedInContext);
|
return useContext(LoggedInContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function saveLoggedIn(loggedIn: boolean) {
|
||||||
|
console.log("saveLoggedIn", JSON.stringify(loggedIn))
|
||||||
|
window.sessionStorage.setItem(LOGGED_IN_KEY, JSON.stringify(loggedIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSavedLoggedIn(): boolean {
|
||||||
|
try {
|
||||||
|
const loggedIn = JSON.parse(window.sessionStorage.getItem(LOGGED_IN_KEY));
|
||||||
|
return !!loggedIn;
|
||||||
|
} catch(err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import './sass/_all.scss';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { App } from './components/App';
|
import { App } from './components/App';
|
||||||
import { client } from './gql/client';
|
|
||||||
|
|
||||||
import '@fortawesome/fontawesome-free/js/fontawesome'
|
import '@fortawesome/fontawesome-free/js/fontawesome'
|
||||||
import '@fortawesome/fontawesome-free/js/solid'
|
import '@fortawesome/fontawesome-free/js/solid'
|
||||||
|
@ -12,8 +11,6 @@ import './resources/favicon.png';
|
||||||
import { ApolloProvider } from '@apollo/client';
|
import { ApolloProvider } from '@apollo/client';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<ApolloProvider client={client}>
|
<App />,
|
||||||
<App />
|
|
||||||
</ApolloProvider>,
|
|
||||||
document.getElementById('app')
|
document.getElementById('app')
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
|
||||||
|
import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client';
|
||||||
|
import { Config } from '../config';
|
||||||
|
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||||
|
import { RetryLink } from "@apollo/client/link/retry";
|
||||||
|
import { onError } from "@apollo/client/link/error";
|
||||||
|
import { SubscriptionClient } from "subscriptions-transport-ws";
|
||||||
|
import { User } from '../types/user';
|
||||||
|
|
||||||
|
export function createClient(setLoggedIn: (boolean) => void) {
|
||||||
|
const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, {
|
||||||
|
reconnect: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorLink = onError(({ operation }) => {
|
||||||
|
const { response } = operation.getContext();
|
||||||
|
if (response.status === 401) setLoggedIn(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const retryLink = new RetryLink({attempts: {max: 2}}).split(
|
||||||
|
(operation) => operation.operationName === 'subscription',
|
||||||
|
new WebSocketLink(subscriptionClient),
|
||||||
|
new HttpLink({
|
||||||
|
uri: Config.graphQLEndpoint,
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const cache = new InMemoryCache({
|
||||||
|
typePolicies: {
|
||||||
|
Workgroup: {
|
||||||
|
fields: {
|
||||||
|
members: {
|
||||||
|
merge: mergeArrayByField<User>("id"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ApolloClient<any>({
|
||||||
|
cache: cache,
|
||||||
|
link: from([
|
||||||
|
errorLink,
|
||||||
|
retryLink
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeArrayByField<T>(fieldName: string) {
|
||||||
|
return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => {
|
||||||
|
const merged: any[] = existing ? existing.slice(0) : [];
|
||||||
|
|
||||||
|
const objectFieldToIndex: Record<string, number> = Object.create(null);
|
||||||
|
if (existing) {
|
||||||
|
existing.forEach((obj, index) => {
|
||||||
|
objectFieldToIndex[readField(fieldName, obj)] = index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
incoming.forEach(obj => {
|
||||||
|
const field = readField(fieldName, obj);
|
||||||
|
const index = objectFieldToIndex[field];
|
||||||
|
if (typeof index === "number") {
|
||||||
|
merged[index] = mergeObjects(merged[index], obj);
|
||||||
|
} else {
|
||||||
|
objectFieldToIndex[name] = merged.length;
|
||||||
|
merged.push(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ package voter
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
@ -40,8 +38,6 @@ func (m *Manager) Authorized(ctx context.Context, subject interface{}, obj inter
|
||||||
decisions = append(decisions, dec)
|
decisions = append(decisions, dec)
|
||||||
}
|
}
|
||||||
|
|
||||||
spew.Dump(decisions)
|
|
||||||
|
|
||||||
result, err := m.strategy(ctx, decisions)
|
result, err := m.strategy(ctx, decisions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Deny, errors.WithStack(err)
|
return Deny, errors.WithStack(err)
|
||||||
|
|
Loading…
Reference in New Issue