Skip to content

Commit

Permalink
Merge pull request #648 from NERC-CEH/NERCDL-867-sign-up-page
Browse files Browse the repository at this point in the history
NERCDL-867 create sign-up page
  • Loading branch information
andrew-longmore-tessella authored Dec 21, 2020
2 parents 582e005 + 7f47234 commit d32f352
Show file tree
Hide file tree
Showing 21 changed files with 369 additions and 83 deletions.
25 changes: 23 additions & 2 deletions architecture/deployment/oidc-deployment-and-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

In order for DataLabs to function there must be an OpenID connect (OIDC) compliant authentication provider (e.g Auth0, Keycloak, AWS Cognito) which will either
store user information directly or serve to re-direct users to a different
authentication provider which in turn will allow a user to recieve an identity
authentication provider which in turn will allow a user to receive an identity
token.

All providers are different and offer various advantages. There are multiple
Expand Down Expand Up @@ -138,9 +138,30 @@ used;
| OIDC_PROVIDER_AUDIENCE | This will be a value which is custom to the DataLabs deployment and will be used as the audience parameter on internal tokens that the authentication service generates | https://datalabs.domain/api |


Not all providers offer a `${OIDC_PROVIDER_DOMAIN}/.well-known/openid-configuration` endpoint. If the provider you are using does not, two additional paramters must be specified for the necessary configuration information.
Not all providers offer a `${OIDC_PROVIDER_DOMAIN}/.well-known/openid-configuration` endpoint. If the provider you are using does not, two additional parameters must be specified for the necessary configuration information.

| Name | Description | Example (and default) |
|---------------------------|------------------------------------------------------------|------------------------|
| OIDC_OAUTH_TOKEN_ENDPOINT | Endpoint from the BASE URL where oauth tokens can be found | /oauth/token |
| OIDC_JWKS_ENDPOINT | Endpoint from the BASE URL where JWKs can be found | /.well-known/jwks.json |

### Self-service sign-up
Depending on your identity provider, self-service sign-up may or may not be possible.
The appropriate corresponding behaviour is configured in the [configmap](https://github.com/NERC-CEH/datalab-k8s-manifests/blob/master/templates/datalab/oidc-configmap.template.yml).

**Self-service is available**
```yaml
"signUp": {
"selfService": true
}
```
In this case, the 'Sign Up' button of DataLabs will redirect to the identity provider's sign-in page.

**Self-service is not available**
```yaml
"signUp": {
"selfService": false,
"requestEmail": "[email protected]"
}
```
In this case, the 'Sign Up' button of DataLabs will redirect to a page asking the user to request an account by emailing the `requestEmail` address.
44 changes: 26 additions & 18 deletions code/development-env/config/local/web_auth_config.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
{
"client_id": "Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"redirect_uri": "https://testlab.datalabs.localhost/callback",
"response_type": "code",
"scope": "openid profile",
"authority": "https://mjbr.eu.auth0.com",
"automaticSilentRenew": true,
"accessTokenExpiringNotificationTime": "600",
"filterProtocolClaims": true,
"loadUserInfo": true,
"extraQueryParams": {
"audience": "https://datalab.datalabs.nerc.ac.uk/api"
"oidc": {
"userManager": {
"client_id": "Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"redirect_uri": "https://testlab.datalabs.localhost/callback",
"response_type": "code",
"scope": "openid profile",
"authority": "https://mjbr.eu.auth0.com",
"automaticSilentRenew": true,
"accessTokenExpiringNotificationTime": "600",
"filterProtocolClaims": true,
"loadUserInfo": true,
"extraQueryParams": {
"audience": "https://datalab.datalabs.nerc.ac.uk/api"
},
"metadata": {
"issuer": "https://mjbr.eu.auth0.com/",
"authorization_endpoint": "https://mjbr.eu.auth0.com/authorize",
"userinfo_endpoint": "https://mjbr.eu.auth0.com/userinfo",
"end_session_endpoint": "https://mjbr.eu.auth0.com/v2/logout?returnTo=https://testlab.datalabs.localhost/&client_id=Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"jwks_uri": "https://mjbr.eu.auth0.com/.well-known/jwks.json",
"token_endpoint": "https://mjbr.eu.auth0.com/oauth/token"
}
}
},
"metadata": {
"issuer": "https://mjbr.eu.auth0.com/",
"authorization_endpoint": "https://mjbr.eu.auth0.com/authorize",
"userinfo_endpoint": "https://mjbr.eu.auth0.com/userinfo",
"end_session_endpoint": "https://mjbr.eu.auth0.com/v2/logout?returnTo=https://testlab.datalabs.localhost/&client_id=Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"jwks_uri": "https://mjbr.eu.auth0.com/.well-known/jwks.json",
"token_endpoint": "https://mjbr.eu.auth0.com/oauth/token"
"signUp": {
"selfService": true,
"requestEmail": "[email protected]"
}
}
40 changes: 24 additions & 16 deletions code/development-env/config/local/web_auth_config_keycloak.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
{
"client_id": "datalabs",
"redirect_uri": "https://testlab.datalabs.localhost/callback",
"response_type": "code",
"scope": "openid profile email",
"authority": "http://keycloak:8080/auth/realms/DataLabs",
"automaticSilentRenew": true,
"accessTokenExpiringNotificationTime": "600",
"filterProtocolClaims": true,
"loadUserInfo": true,
"metadata": {
"issuer": "http://keycloak:8080/auth/realms/DataLabs",
"authorization_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/auth",
"userinfo_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/userinfo",
"end_session_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/logout?redirect_uri=http://testlab.datalabs.localhost",
"jwks_uri": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/certs",
"token_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/token"
"oidc": {
"userManager": {
"client_id": "datalabs",
"redirect_uri": "https://testlab.datalabs.localhost/callback",
"response_type": "code",
"scope": "openid profile email",
"authority": "http://keycloak:8080/auth/realms/DataLabs",
"automaticSilentRenew": true,
"accessTokenExpiringNotificationTime": "600",
"filterProtocolClaims": true,
"loadUserInfo": true,
"metadata": {
"issuer": "http://keycloak:8080/auth/realms/DataLabs",
"authorization_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/auth",
"userinfo_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/userinfo",
"end_session_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/logout?redirect_uri=http://testlab.datalabs.localhost",
"jwks_uri": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/certs",
"token_endpoint": "http://keycloak:8080/auth/realms/DataLabs/protocol/openid-connect/token"
}
}
},
"signUp": {
"selfService": true,
"requestEmail": "[email protected]"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ Array [
"userId": "uid2",
},
]
`;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ describe('userRolesRepository', () => {
{ userId: 'uid1', instanceAdmin: true, projectRoles: [{ projectKey: 'project1', role: 'viewer' }] },
{ userId: 'uid1', catalogueRole: 'admin', projectRoles: [{ projectKey: 'project2', role: 'admin' }] },
)).toEqual(
{ userId: 'uid1',
{
userId: 'uid1',
instanceAdmin: true,
catalogueRole: 'admin',
projectRoles: [
{ projectKey: 'project2', role: 'admin' },
{ projectKey: 'project1', role: 'viewer' },
] },
],
},
);
});
});
Expand Down
42 changes: 25 additions & 17 deletions code/workspaces/web-app/public/web_auth_config.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
{
"client_id": "Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"redirect_uri": "https://testlab.datalabs.localhost/callback",
"response_type": "code",
"scope": "openid profile",
"authority": "https://mjbr.eu.auth0.com",
"automaticSilentRenew": true,
"filterProtocolClaims": true,
"loadUserInfo": true,
"extraQueryParams": {
"audience": "https://datalab.datalabs.nerc.ac.uk/api"
"oidc": {
"userManager": {
"client_id": "Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"redirect_uri": "https://testlab.datalabs.localhost/callback",
"response_type": "code",
"scope": "openid profile",
"authority": "https://mjbr.eu.auth0.com",
"automaticSilentRenew": true,
"filterProtocolClaims": true,
"loadUserInfo": true,
"extraQueryParams": {
"audience": "https://datalab.datalabs.nerc.ac.uk/api"
},
"metadata": {
"issuer": "https://mjbr.eu.auth0.com/",
"authorization_endpoint": "https://mjbr.eu.auth0.com/authorize",
"userinfo_endpoint": "https://mjbr.eu.auth0.com/userinfo",
"end_session_endpoint": "https://mjbr.eu.auth0.com/v2/logout?returnTo=https://testlab.datalabs.localhost/&client_id=Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"jwks_uri": "https://mjbr.eu.auth0.com/.well-known/jwks.json",
"token_endpoint": "https://mjbr.eu.auth0.com/oauth/token"
}
}
},
"metadata": {
"issuer": "https://mjbr.eu.auth0.com/",
"authorization_endpoint": "https://mjbr.eu.auth0.com/authorize",
"userinfo_endpoint": "https://mjbr.eu.auth0.com/userinfo",
"end_session_endpoint": "https://mjbr.eu.auth0.com/v2/logout?returnTo=https://testlab.datalabs.localhost/&client_id=Xf62MEzjqxfaId1DVWnFul61D9oA3eMt",
"jwks_uri": "https://mjbr.eu.auth0.com/.well-known/jwks.json",
"token_endpoint": "https://mjbr.eu.auth0.com/oauth/token"
"signUp": {
"selfService": true,
"requestEmail": "[email protected]"
}
}
4 changes: 4 additions & 0 deletions code/workspaces/web-app/src/PublicApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { publicAppTheme } from './theme';
import WelcomePage from './pages/WelcomePage';
import VerifyEmailPage from './pages/VerifyEmailPage';
import RedirectToLoginPage from './pages/RedirectToLoginPage';
import SignUpPage from './pages/SignUpPage';

const PublicApp = () => (
<MuiThemeProvider theme={publicAppTheme}>
Expand All @@ -15,6 +16,9 @@ const PublicApp = () => (
<Route exact path="/verify">
<VerifyEmailPage />
</Route>
<Route exact path="/sign-up">
<SignUpPage />
</Route>
<Route>
<RedirectToLoginPage />
</Route>
Expand Down
11 changes: 8 additions & 3 deletions code/workspaces/web-app/src/auth/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ class Auth {
this.oidcAsync = promisifyOidcInit;
this.oidcInit = oidcInit;
this.login = this.login.bind(this);
this.signUp = this.signUp.bind(this);
this.selfServiceSignUp = this.selfServiceSignUp.bind(this);
this.signUpConfig = this.signUpConfig.bind(this);
this.logout = this.logout.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.renewSession = this.renewSession.bind(this);
Expand All @@ -24,11 +25,15 @@ class Auth {
this.oidcInit.signinRedirect({ state: { appRedirect: window.location.pathname } });
}

signUp() {
selfServiceSignUp() {
// Re-direct to login screen
this.oidcInit.signinRedirect();
}

signUpConfig() {
return this.authConfig.signUp;
}

logout() {
// User redirected to home page on logout
clearSession();
Expand Down Expand Up @@ -120,7 +125,7 @@ const initialiseAuth = (authConfig) => {
Oidc.Log.level = Oidc.Log.INFO;

const userManagerConfig = {
...authConfig,
...authConfig.oidc.userManager,
userStore: new Oidc.WebStorageStateStore(),
};

Expand Down
29 changes: 22 additions & 7 deletions code/workspaces/web-app/src/auth/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ const signinRedirectCallbackMock = jest.fn();
const signoutRedirectMock = jest.fn();

const authConfig = {
authority: 'expected.auth0.com',
client_id: 'expected-client-id',
extraQueryParams: {
audience: 'https://datalab.datalabs.nerc.ac.uk/api',
oidc: {
userManager: {
authority: 'expected.auth0.com',
client_id: 'expected-client-id',
extraQueryParams: {
audience: 'https://datalab.datalabs.nerc.ac.uk/api',
},
response_type: 'token id_token',
scope: 'openid profile',
redirect_uri: `${window.location.origin}/callback`,
},
},
signUp: {
selfService: true,
},
response_type: 'token id_token',
scope: 'openid profile',
redirect_uri: `${window.location.origin}/callback`,
};

const MockAuth = {
Expand Down Expand Up @@ -53,6 +60,14 @@ describe('auth', () => {
);
});

it('signUpConfig returns expected config', () => {
// Act
const signUpConfig = auth.signUpConfig();

// Assert
expect(signUpConfig).toEqual({ selfService: true });
});

it('login calls signinRedirect with correct props', () => {
// Act
auth.login();
Expand Down

This file was deleted.

9 changes: 4 additions & 5 deletions code/workspaces/web-app/src/auth/authConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ const getAuthConfig = () => axios
.then(({ data }) => {
// Overwrite default configuration for running locally/development purposes
if (window.location.hostname.match(/localhost/)) {
return {
...data,
silent_redirect_uri: `${window.location.origin}/silent_callback`,
redirect_uri: `${window.location.origin}/callback`,
};
const localData = data;
localData.oidc.userManager.silent_redirect_uri = `${window.location.origin}/silent_callback`;
localData.oidc.userManager.redirect_uri = `${window.location.origin}/callback`;
return localData;
}

return data;
Expand Down
20 changes: 17 additions & 3 deletions code/workspaces/web-app/src/components/welcome/HeroBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { useHistory } from 'react-router-dom';
import datalabsLogo from '../../assets/images/datalabs-vert.png';
import getAuth from '../../auth/auth';
import PrimaryActionButton from '../common/buttons/PrimaryActionButton';
Expand Down Expand Up @@ -36,16 +37,29 @@ const styles = theme => ({

const tagLine = 'DataLabs provides you with tools to power your research and share the results';

const HeroBar = ({ classes }) => (
const HeroBar = ({ classes }) => {
const history = useHistory();

const signUp = () => {
const auth = getAuth();
if (auth.signUpConfig().selfService) {
auth.selfServiceSignUp();
} else {
history.push('/sign-up');
}
};

return (
<div className={classes.bar}>
<img className={classes.logo} src={datalabsLogo} alt="DataLabs-Logo" />
<Typography className={classes.tagLine} variant="h6">{tagLine}</Typography>
<div className={classes.buttons}>
<PagePrimaryActionButton className={classes.button} color="primary" onClick={getAuth().signUp}>Sign Up</PagePrimaryActionButton>
<PagePrimaryActionButton className={classes.button} color="primary" onClick={signUp}>Sign Up</PagePrimaryActionButton>
<PrimaryActionButton className={classes.button} color="primary" onClick={getAuth().login}>Log In</PrimaryActionButton>
</div>
</div>
);
);
};

HeroBar.propTypes = {
classes: PropTypes.object.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import getAuth from '../../auth/auth';
jest.mock('../../auth/auth');
getAuth.mockImplementation(() => ({
login: jest.fn(),
signUp: jest.fn(),
selfServiceSignUp: jest.fn(),
signUpConfig: jest.fn().mockReturnValue({ selfService: true }),
}));

describe('HeroBar', () => {
Expand Down
Loading

0 comments on commit d32f352

Please sign in to comment.