Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve auth provider #797

Merged
merged 6 commits into from
Jun 21, 2021
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
2 changes: 1 addition & 1 deletion src/frontend/packages/auth-provider/dist/index.cjs.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/frontend/packages/auth-provider/dist/index.cjs.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/frontend/packages/auth-provider/dist/index.es.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/frontend/packages/auth-provider/dist/index.es.js.map

Large diffs are not rendered by default.

40 changes: 25 additions & 15 deletions src/frontend/packages/auth-provider/src/authProvider.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import jwtDecode from 'jwt-decode';
import { defaultToArray, getAclUri, getAclContext } from './utils';

const authProvider = ({ middlewareUri, httpClient, checkPermissions, resources }) => ({
login: params => {
window.location.href = `${middlewareUri}auth?redirectUrl=` + encodeURIComponent(window.location.href);
return Promise.resolve();
const authProvider = ({ middlewareUri, allowAnonymous = true, checkUser, httpClient, checkPermissions, resources }) => ({
login: async params => {
const url = new URL(window.location.href);
window.location.href = `${middlewareUri}auth?redirectUrl=` + encodeURIComponent(url.origin + '/login?login=true')
},
logout: () => {
// Redirect to login page after disconnecting from SSO
// The login page will remove the token, display a notification and redirect to the homepage
logout: async () => {
const url = new URL(window.location.href);
window.location.href =
`${middlewareUri}auth/logout?redirectUrl=` + encodeURIComponent(url.origin + '/login?logout');
if( !allowAnonymous ) {
localStorage.removeItem('token');
window.location.href =
`${middlewareUri}auth/logout?redirectUrl=` + encodeURIComponent(url.origin + '/login')
} else {
// Redirect to login page after disconnecting from SSO
// The login page will remove the token, display a notification and redirect to the homepage
window.location.href =
`${middlewareUri}auth/logout?redirectUrl=` + encodeURIComponent(url.origin + '/login?logout=true');

// Avoid displaying immediately the login page
return Promise.resolve('/');
// Avoid displaying immediately the login page
return '/';
}
},
checkAuth: async () => {
const token = localStorage.getItem('token');
if( !token && !allowAnonymous ) throw new Error();
},
checkAuth: () => {
if (localStorage.getItem('token')) {
return Promise.resolve();
checkUser: userData => {
if( checkUser ) {
return checkUser(userData);
} else {
return Promise.resolve();
return true;
}
},
checkError: error => Promise.resolve(),
Expand Down
59 changes: 38 additions & 21 deletions src/frontend/packages/auth-provider/src/components/LoginPage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import React, { useEffect } from 'react';
import jwtDecode from 'jwt-decode';
import { useLogin, useNotify, Notification } from 'react-admin';
import { useLogin, useNotify, useDataProvider, useAuthProvider, Notification } from 'react-admin';
import { ThemeProvider } from '@material-ui/styles';
import { createMuiTheme, makeStyles } from '@material-ui/core/styles';
import { Avatar, Button, Card, CardActions } from '@material-ui/core';
Expand Down Expand Up @@ -33,25 +33,42 @@ const LoginPage = ({ theme, history, location, buttons, userResource }) => {
const classes = useStyles();
const notify = useNotify();
const login = useLogin();
const dataProvider = useDataProvider();
const authProvider = useAuthProvider();

const searchParams = new URLSearchParams(location.search);
if (searchParams.has('token')) {
localStorage.setItem('token', searchParams.get('token'));
if (searchParams.has('new') && searchParams.get('new') === 'true') {
notify('Votre compte a été créé, vous pouvez maintenant le compléter', 'info');
const { webId } = jwtDecode(searchParams.get('token'));
history.push('/' + userResource + '/' + encodeURIComponent(webId) + '/edit');
} else {
notify('Vous êtes maintenant connecté', 'info');
history.push('/');
}
}
useEffect(() => {
(async () => {
const searchParams = new URLSearchParams(location.search);

if (searchParams.has('logout')) {
localStorage.removeItem('token');
notify('Vous êtes maintenant déconnecté', 'info');
history.push('/');
}
if (searchParams.has('login')) {
if (searchParams.has('token')) {
const token = searchParams.get('token');
const { webId } = jwtDecode(token);
const { data } = await dataProvider.getOne('Person', { id: webId });

if (!authProvider.checkUser(data)) {
notify('auth.message.user_not_allowed_to_login', 'error');
history.replace('/login');
} else {
localStorage.setItem('token', token);
if (searchParams.has('new') && searchParams.get('new') === 'true') {
notify('auth.message.new_user_created', 'info');
history.push('/' + userResource + '/' + encodeURIComponent(webId) + '/edit');
} else {
notify('auth.message.user_connected', 'info');
history.push('/');
}
}
}
}

if (searchParams.has('logout')) {
localStorage.removeItem('token');
notify('auth.message.user_disconnected', 'info');
history.push('/');
}
})();
}, [location.search]);

return (
<ThemeProvider theme={createMuiTheme(theme)}>
Expand All @@ -63,8 +80,8 @@ const LoginPage = ({ theme, history, location, buttons, userResource }) => {
</Avatar>
</div>
{buttons &&
buttons.map(button => (
<CardActions>
buttons.map((button, i) => (
<CardActions key={i}>
{React.cloneElement(button, {
fullWidth: true,
variant: 'outlined',
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/packages/auth-provider/src/messages/french.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ const frenchMessages = {
agent_select: 'Ajouter un utilisateur...'
},
message: {
resource_edit_forbidden: "Vous n'avez pas la permission d'éditer cette ressource"
resource_edit_forbidden: "Vous n'avez pas la permission d'éditer cette ressource",
user_not_allowed_to_login: "Vous n'avez pas le droit de vous connecter avec ce compte",
new_user_created: 'Votre compte a été créé, vous pouvez maintenant le compléter',
user_connected: 'Vous êtes maintenant connecté',
user_disconnected: 'Vous êtes maintenant déconnecté'
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"foaf": "http://xmlns.com/foaf/0.1/",
"skos": "http://www.w3.org/2004/02/skos/core#",
"semapps": "http://semapps.org/ns/core#",
"oasis": "http://cooperative-oasis.org/ns/core#",

"id": "@id",
"type": "@type",
Expand Down
14 changes: 8 additions & 6 deletions src/middleware/packages/auth/Connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,19 @@ class Connector {
next();
}
globalLogout(req, res, next) {
//have to be implemented in extended class
// Must be implemented in extended class
next();
}
redirectToFront(req, res, next) {
// Redirect browser to the redirect URL pushed in session
let redirectUrl = req.session.redirectUrl;
// If a token was stored, add it to the URL so that the client may use it
if (req.user && req.user.token)
redirectUrl += '?token=' + req.user.token + '&new=' + (req.user.newUser ? 'true' : 'false');
let redirectUrl = new URL(req.session.redirectUrl);
if (req.user) {
// If a token was stored, add it to the URL so that the client may use it
if (req.user.token) redirectUrl.searchParams.set('token', req.user.token);
redirectUrl.searchParams.set('new', req.user.newUser ? 'true' : 'false');
}
// Redirect using NodeJS HTTP
res.writeHead(302, { Location: redirectUrl });
res.writeHead(302, { Location: redirectUrl.toString() });
res.end();
next();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ module.exports = {
// We want to remove in old triples only the triples for which we have provided a new literal value
const literalTriplesToAdd = triplesToAdd.filter(t => t.object.termType === 'Literal');
const triplesToRemove = oldTriples.filter(ot =>
literalTriplesToAdd.some(nt => nt.subject.value === ot.subject.value && nt.predicate.value === ot.predicate.value)
literalTriplesToAdd.some(
nt => nt.subject.value === ot.subject.value && nt.predicate.value === ot.predicate.value
)
);

// The exact same data have been posted, skip
Expand Down
13 changes: 13 additions & 0 deletions website/docs/frontend/auth-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ const App = () => (
);
```

## Forcing user to login

By default your app will be accessible to anonymous users.

If you wish to force all users to login, you can pass a `allowAnonymous: false` param to the auth provider.

## Checking allowed users

If you only want certain types of users to access your app, you can pass a `checkUser` function to the authProvider.

This function receives user data and must return true or false, depending on whether the user is granted access or not.

> This `checkUser` function is also available to any of your components using the React-Admin's `useAuthProvider` hook.

## Checking permissions

Expand Down