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

POD login & proxy authentication #1091

Merged
merged 21 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
22e5f1c
Improve login/signup and create profile page, start
srosset81 Dec 12, 2022
9f45bea
Finish PodLoginPage
srosset81 Dec 13, 2022
81793af
Handle non-GET methods on proxy endpoint through multipart/form-data
srosset81 Dec 14, 2022
5e1bf52
Refactor data provider with new proxy endpoint
srosset81 Dec 14, 2022
3d2d83d
Refactor auth provider with new proxy endpoint
srosset81 Dec 14, 2022
088b4e8
Fix proxyUrl not added correctly
srosset81 Dec 16, 2022
eee7777
Add logout route in auth.local service (for consistency sake)
srosset81 Dec 16, 2022
31f0fda
Small fixes to semantic-data-provider
srosset81 Dec 16, 2022
285590b
Refactor auth provider, UserMenu and PodLoginPage
srosset81 Dec 16, 2022
ab62eeb
Move proxy service to signature package, split keypair service
srosset81 Dec 16, 2022
1896d96
Update docs
srosset81 Dec 16, 2022
d3f5453
Make CommentsField more flexible
srosset81 Dec 22, 2022
88d2c3d
Merge branch 'master' into pod-login-2
srosset81 Jan 9, 2023
6e55dbd
Fix named route (post-merge)
srosset81 Jan 9, 2023
d1bfdc2
Merge branch 'master' into pod-login-2
srosset81 Jan 9, 2023
d0e7b95
Remove code to add user as this is not needed by all apps (will be ad…
srosset81 Jan 9, 2023
4ecd734
Merge branch 'master' into pod-login-2
srosset81 Jan 10, 2023
a5dda0e
Add formatLink method to single-mail notifications
srosset81 Jan 11, 2023
1f4c71e
SingleMailNotificationService: allow to change primary color
srosset81 Jan 12, 2023
bd9f857
Improve PodLoginPage
srosset81 Jan 13, 2023
3ce7db0
Doc
srosset81 Jan 13, 2023
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/frontend/packages/activitypub-components/dist/index.es.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ const CommentsList = ({ comments, userResource, loading }) => {
.map(comment => (
<Box className={classes.container}>
<Box className={classes.avatar}>
<ReferenceField record={comment} reference="Person" source="attributedTo" linkType="show">
<ReferenceField record={comment} reference={userResource} source="attributedTo" linkType="show">
<AvatarWithLabelField image={userDataModel?.fieldsMapping?.image} />
</ReferenceField>
</Box>
<Box className={classes.text}>
<Typography variant="body2">
<ReferenceField record={comment} reference="Person" source="attributedTo" linkType="show">
<ReferenceField record={comment} reference={userResource} source="attributedTo" linkType="show">
<TextField variant="body2" source={userDataModel?.fieldsMapping?.title} className={classes.label} />
</ReferenceField>
&nbsp;•&nbsp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const PostCommentForm = ({ context, placeholder, helperText, mentions, userResou
return (
<form onSubmit={handleSubmit} className={classes.form}>
<Box className={classes.container} onClick={openAuthIfDisconnected}>
<Avatar src={identity?.webIdData?.[userDataModel?.fieldsMapping?.image]} className={classes.avatar} />
<Avatar src={identity?.webIdData?.[userDataModel?.fieldsMapping?.image] || identity?.profileData?.[userDataModel?.fieldsMapping?.image]} className={classes.avatar} />
<RichTextInput
source="comment"
label=""
Expand Down
4 changes: 2 additions & 2 deletions 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.

4 changes: 2 additions & 2 deletions 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.

112 changes: 33 additions & 79 deletions src/frontend/packages/auth-provider/src/authProvider.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import jwtDecode from 'jwt-decode';
import { defaultToArray, getAclUri, getAclContext } from './utils';
import urlJoin from 'url-join';
import { defaultToArray, getAclUri, getAclContext, getAuthServerUrl } from './utils';

const authProvider = ({
middlewareUri,
dataProvider,
allowAnonymous = true,
localAccounts = false,
checkUser,
httpClient,
checkPermissions
checkPermissions = false
}) => ({
login: async params => {
const url = new URL(window.location.href);
const serverUrl = middlewareUri || (params.domain && `https://${params.domain}/`);
if (!serverUrl)
throw new Error(
'You must specify a middlewareUri in the authProvider config, or specify a domain when calling the login method'
);
const authServerUrl = await getAuthServerUrl(dataProvider);
if (localAccounts) {
const { username, password } = params;
try {
const { json } = await httpClient(`${serverUrl}auth/login`, {
const { json } = await dataProvider.fetch(urlJoin(authServerUrl, 'auth/login'), {
method: 'POST',
body: JSON.stringify({ username: username.trim(), password: password.trim() }),
headers: new Headers({ 'Content-Type': 'application/json' })
Expand All @@ -32,21 +27,17 @@ const authProvider = ({
throw new Error('ra.auth.sign_in_error');
}
} else {
let redirectUrl = url.origin + '/login?login=true';
let redirectUrl = (new URL(window.location.href)).origin + '/login?login=true';
if (params.redirect) redirectUrl += '&redirect=' + encodeURIComponent(params.redirect);
window.location.href = `${serverUrl}auth?redirectUrl=` + encodeURIComponent(redirectUrl);
window.location.href = urlJoin(authServerUrl, 'auth?redirectUrl=' + encodeURIComponent(redirectUrl));
}
},
signup: async params => {
const serverUrl = middlewareUri || (params.domain && `https://${params.domain}/`);
if (!serverUrl)
throw new Error(
'You must specify a middlewareUri in the authProvider config, or specify a domain when calling the signup method'
);
const authServerUrl = await getAuthServerUrl(dataProvider);
if (localAccounts) {
const { username, email, password, domain, ...profileData } = params;
try {
const { json } = await httpClient(`${serverUrl || `https://${domain}`}auth/signup`, {
const { json } = await dataProvider.fetch(urlJoin(authServerUrl, 'auth/signup'), {
method: 'POST',
body: JSON.stringify({
username: username.trim(),
Expand All @@ -72,36 +63,26 @@ const authProvider = ({
}
}
} else {
window.location.href = `${serverUrl}auth?redirectUrl=` + encodeURIComponent(url.origin + '/login?login=true');
const redirectUrl = (new URL(window.location.href)).origin + '/login?login=true';
window.location.href = urlJoin(authServerUrl, 'auth?redirectUrl=' + encodeURIComponent(redirectUrl));
}
},
logout: async () => {
let serverUrl;
if (middlewareUri) {
serverUrl = middlewareUri;
} else {
// Get the server URL from the connected user's webId
const token = localStorage.getItem('token');
if (token) {
const { webId } = jwtDecode(token);
serverUrl = new URL(webId).origin + '/';
}
}

localStorage.removeItem('token');
const authServerUrl = await getAuthServerUrl(dataProvider);
// Delete token but also any other value in local storage
localStorage.clear();
if (localAccounts) {
// Reload to ensure the dataServer config is reset
window.location.reload();
window.location.href = '/';
} else if (serverUrl) {
const url = new URL(window.location.href);
} else {
const baseUrl = (new URL(window.location.href)).origin;
if (!allowAnonymous) {
window.location.href = `${serverUrl}auth/logout?redirectUrl=` + encodeURIComponent(url.origin + '/login');
window.location.href = urlJoin(authServerUrl, 'auth/logout?redirectUrl=' + encodeURIComponent(baseUrl + '/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 =
`${serverUrl}auth/logout?redirectUrl=` + encodeURIComponent(url.origin + '/login?logout=true');
window.location.href = urlJoin(authServerUrl, 'auth/logout?redirectUrl=' + encodeURIComponent(baseUrl + '/login?logout=true'));
}
}

Expand All @@ -121,23 +102,18 @@ const authProvider = ({
},
checkError: error => Promise.resolve(),
getPermissions: async uri => {
// Do not get permissions for servers other than the one used for auth
// as this will always fail as long as cross-servers auth is not available
if (!uri.startsWith(middlewareUri)) return false;

if (!checkPermissions) return true;

if (!uri || !uri.startsWith('http')) throw new Error('The first parameter passed to getPermissions must be an URL');

const aclUri = getAclUri(uri);

const { json } = await httpClient(aclUri);
const { json } = await dataProvider.fetch(aclUri);

return json['@graph'];
},
addPermission: async (uri, agentId, predicate, mode) => {
if (!uri || !uri.startsWith('http')) throw new Error('The first parameter passed to addPermission must be an URL');
if (!uri.startsWith(middlewareUri)) new Error('Cannot add permissions on servers other than the one used for auth');

const aclUri = getAclUri(uri);

Expand All @@ -149,7 +125,7 @@ const authProvider = ({
'acl:mode': mode
};

await httpClient(aclUri, {
await dataProvider.fetch(aclUri, {
method: 'PATCH',
body: JSON.stringify({
'@context': getAclContext(aclUri),
Expand All @@ -160,13 +136,11 @@ const authProvider = ({
removePermission: async (uri, agentId, predicate, mode) => {
if (!uri || !uri.startsWith('http'))
throw new Error('The first parameter passed to removePermission must be an URL');
if (!uri.startsWith(middlewareUri))
throw new Error('Cannot remove permissions on servers other than the one used for auth');

const aclUri = getAclUri(uri);

// Fetch current permissions
let { json } = await httpClient(aclUri);
let { json } = await dataProvider.fetch(aclUri);

const updatedPermissions = json['@graph']
.filter(authorization => !authorization['@id'].includes('#Default'))
Expand All @@ -179,7 +153,7 @@ const authProvider = ({
return { ...authorization, [predicate]: agents };
});

await httpClient(aclUri, {
await dataProvider.fetch(aclUri, {
method: 'PUT',
body: JSON.stringify({
'@context': getAclContext(aclUri),
Expand All @@ -192,8 +166,8 @@ const authProvider = ({
if (token) {
const { webId } = jwtDecode(token);

const { json: webIdData } = await httpClient(webId);
const { json: profileData } = webIdData.url ? await httpClient(webIdData.url) : {};
const { json: webIdData } = await dataProvider.fetch(webId);
const { json: profileData } = webIdData.url ? await dataProvider.fetch(webIdData.url) : {};

return {
id: webId,
Expand All @@ -204,14 +178,10 @@ const authProvider = ({
}
},
resetPassword: async params => {
const serverUrl = middlewareUri || (params.domain && `https://${params.domain}/`);
if (!serverUrl)
throw new Error(
'You must specify a middlewareUri in the authProvider config, or specify a domain when calling the forgot password method'
);
const { email } = params;
const authServerUrl = await getAuthServerUrl(dataProvider);
try {
await httpClient(`${serverUrl}auth/reset_password`, {
await dataProvider.fetch(urlJoin(authServerUrl, 'auth/reset_password'), {
method: 'POST',
body: JSON.stringify({ email: email.trim() }),
headers: new Headers({ 'Content-Type': 'application/json' })
Expand All @@ -221,14 +191,10 @@ const authProvider = ({
}
},
setNewPassword: async params => {
const serverUrl = middlewareUri || (params.domain && `https://${params.domain}/`);
if (!serverUrl)
throw new Error(
'You must specify a middlewareUri in the authProvider config, or specify a domain when calling the new password method'
);
const { email, token, password } = params;
const authServerUrl = await getAuthServerUrl(dataProvider);
try {
await httpClient(`${serverUrl}auth/new_password`, {
await dataProvider.fetch(urlJoin(authServerUrl, 'auth/new_password'), {
method: 'POST',
body: JSON.stringify({ email: email.trim(), token, password }),
headers: new Headers({ 'Content-Type': 'application/json' })
Expand All @@ -238,32 +204,20 @@ const authProvider = ({
}
},
getAccountSettings: async params => {
const serverUrl = middlewareUri || (params.domain && `https://${params.domain}/`);
if (!serverUrl)
throw new Error(
'You must specify a middlewareUri in the authProvider config, or specify a domain when calling the getAccountSettings method'
);

const authServerUrl = await getAuthServerUrl(dataProvider);
try {
const { json } = await httpClient(`${serverUrl}auth/account`, {
method: 'GET'
});
const { json } = await dataProvider.fetch(urlJoin(authServerUrl, 'auth/account'));
return json;
} catch (e) {
throw new Error('app.notification.get_settings_error');
}
},
updateAccountSettings: async params => {
const serverUrl = middlewareUri || (params.domain && `https://${params.domain}/`);
if (!serverUrl)
throw new Error(
'You must specify a middlewareUri in the authProvider config, or specify a domain when calling the updateAccountSettings method'
);

const authServerUrl = await getAuthServerUrl(dataProvider);
try {
const { email, currentPassword, newPassword } = params;

await httpClient(`${serverUrl}auth/account`, {
await dataProvider.fetch(urlJoin(authServerUrl, 'auth/account'), {
method: 'POST',
body: JSON.stringify({ currentPassword, email: email.trim(), newPassword }),
headers: new Headers({ 'Content-Type': 'application/json' })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { useMemo } from 'react';
import { Notification } from 'react-admin';
import { createTheme, ThemeProvider } from '@material-ui/core';
import PodLoginPageView from "./PodLoginPageView";

const PodLoginPage = (props) => {
const muiTheme = useMemo(() => createTheme(props.theme), [props.theme]);
return (
<ThemeProvider theme={muiTheme}>
<PodLoginPageView {...props} />
<Notification />
</ThemeProvider >
);
};

export default PodLoginPage;
Loading