Skip to content

Commit 55d53d3

Browse files
authored
Helium 6435 auth0 integration (#190)
* Adds base auth provider and removes additional redirect to login * HELIUM-6435 Update configuration * Added the current organization management, user retrieval, and access token verification * Remove unnecessary console log * Fixed redirect broken by auth0 path overwrite and fixed migrations * Fixes relationships to not require user entry * Refactors to use ID Token instead of Access Token and removes some log statements * Fixes invalid associations, allows terms to be viewed while not registered, modifies data received on invitation * Updates to allow users to accept organization invitations TODO: remove dependency on auth modules * Remove console log * HELIUM-6435 Addresses PR comments, corrects socket authentication, pauses rendering unallowed children * Updates to fix broken tests due to changed authentication process * Update to remove unused function stub added by mistake * Updates regarding review comments, cleaned unecessary var def, reorganized router, removed unused method stub * Update to remove unnecessary comment and move from window to push * Removes two factor from members table information * Update to remove repeated Auth0 jwk fetching (#185) * Update to allow rendering children once the organizations have been fetched (#186) * Updates to use the store instead of the localStorage for org id (#187) * Allows users to switch organizations and join organizations initially (#188) * Allows users to switch organizations and join organizations initially * Removed additional imports * Updates to allow users to read role and api keys to associate user email (#189) * Updates to allow users to read role and api keys to associate user email * Updates disgusting line length * Updates to not have undefined organization * Corrects redirect on register and removes commented out code
1 parent 2adf352 commit 55d53d3

File tree

75 files changed

+927
-1475
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+927
-1475
lines changed

assets/.babelrc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
{
2-
"presets": ["env", "react"],
2+
"presets": [
3+
[
4+
"env",
5+
{
6+
"targets": {
7+
"node": "10"
8+
}
9+
}
10+
],
11+
"babel-preset-react"
12+
],
313
"plugins": [
414
"transform-es2015-destructuring",
515
"transform-es2015-parameters",

assets/js/Router.jsx

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
1-
import React from "react"
1+
import React, { useEffect } from 'react'
22

33
import { store, persistor, history } from './store/configureStore';
44
import { PersistGate } from 'redux-persist/lib/integration/react';
55

66
import { ApolloProvider } from 'react-apollo';
7-
import apolloClient from './util/apolloClient'
7+
import { setupApolloClient } from './util/apolloClient'
88

99
// Routes
1010
import { Provider } from 'react-redux';
1111
import { ConnectedRouter } from 'connected-react-router';
1212
import { Redirect } from 'react-router';
1313
import { Route, Switch } from 'react-router-dom';
14-
import PrivateRoute from './components/routes/PrivateRoute.jsx';
14+
import ConsoleRoute from './components/routes/ConsoleRoute.jsx';
1515
import PublicRoute from './components/routes/PublicRoute.jsx';
1616
import UserOrgProvider from './components/UserOrgProvider'
17-
import Login from './components/auth/Login.jsx';
18-
import Terms from './components/auth/Terms.jsx';
1917
import Register from './components/auth/Register.jsx';
20-
import ResendVerification from './components/auth/ResendVerification.jsx';
21-
import ForgotPassword from './components/auth/ForgotPassword.jsx';
22-
import ResetPassword from './components/auth/ResetPassword.jsx';
2318
import Profile from './components/profile/Profile.jsx';
24-
import TwoFactorPrompt from './components/auth/TwoFactorPrompt.jsx';
25-
import ConfirmEmailPrompt from './components/auth/ConfirmEmailPrompt.jsx';
2619
import DeviceIndex from './components/devices/DeviceIndex';
2720
import DeviceShow from './components/devices/DeviceShow';
2821
import ChannelIndex from './components/channels/ChannelIndex'
@@ -33,53 +26,61 @@ import OrganizationIndex from './components/organizations/OrganizationIndex'
3326
import LabelIndex from './components/labels/LabelIndex'
3427
import LabelShow from './components/labels/LabelShow'
3528
import DataCredits from './components/billing/DataCredits'
29+
import { useAuth0 } from './components/auth/Auth0Provider'
3630
import FunctionIndex from './components/functions/FunctionIndex';
3731
import FunctionNew from './components/functions/FunctionNew';
3832
import FunctionShow from './components/functions/FunctionShow';
3933
import Welcome from './components/Welcome';
4034

41-
class Router extends React.Component {
42-
render() {
43-
return (
44-
<Provider store={store}>
45-
<PersistGate loading={null} persistor={persistor}>
35+
const Router = () => {
36+
const { loading, isAuthenticated, loginWithRedirect, getIdTokenClaims, user } = useAuth0();
37+
useEffect(() => {
38+
if (loading || isAuthenticated) {
39+
return;
40+
}
41+
const fn = async () => {
42+
await loginWithRedirect({
43+
appState: {targetUrl: window.location.pathname, params: window.location.search}
44+
});
45+
};
46+
fn();
47+
}, [loading, isAuthenticated, loginWithRedirect]);
48+
if (loading) {
49+
return <div>Loading...</div>
50+
}
51+
const apolloClient = setupApolloClient(getIdTokenClaims);
52+
return (
53+
<Provider store={store}>
54+
<PersistGate loading={null} persistor={persistor}>
55+
<UserOrgProvider>
4656
<ApolloProvider client={apolloClient}>
47-
<UserOrgProvider>
48-
{ /* ConnectedRouter will use the store from Provider automatically */ }
49-
<ConnectedRouter history={history}>
50-
<Switch>
51-
<Redirect exact from="/" to="/login" />
52-
<PublicRoute path="/login" component={Login}/>
53-
<PublicRoute path="/terms" component={Terms}/>
54-
<PublicRoute path="/resend_verification" component={ResendVerification}/>
55-
<PublicRoute path="/forgot_password" component={ForgotPassword}/>
56-
<PublicRoute path="/reset_password/:token" component={ResetPassword}/>
57-
<PublicRoute path="/register" component={Register}/>
58-
<PublicRoute path="/confirm_email" component={ConfirmEmailPrompt}/>
59-
<PrivateRoute path="/2fa_prompt" component={TwoFactorPrompt}/>
60-
<PrivateRoute path="/profile" component={Profile}/>
61-
<PrivateRoute exact path="/devices" component={DeviceIndex} />
62-
<PrivateRoute exact path="/labels" component={LabelIndex} />
63-
<PrivateRoute path="/devices/:id" component={DeviceShow}/>
64-
<PrivateRoute path="/labels/:id" component={LabelShow} />
65-
<PrivateRoute exact path="/integrations" component={ChannelIndex} />
66-
<PrivateRoute exact path="/integrations/new/:id?" component={ChannelNew} />
67-
<PrivateRoute exact path="/integrations/:id" component={ChannelShow} />
68-
<PrivateRoute exact path="/users" component={UserIndex} />
69-
<PrivateRoute exact path="/organizations" component={OrganizationIndex} />
70-
<PrivateRoute exact path="/datacredits" component={DataCredits} />
71-
<PrivateRoute exact path="/functions" component={FunctionIndex} />
72-
<PrivateRoute exact path="/functions/new" component={FunctionNew} />
73-
<PrivateRoute exact path="/functions/:id" component={FunctionShow} />
74-
<PrivateRoute exact path="/welcome" component={Welcome} />
75-
</Switch>
76-
</ConnectedRouter>
77-
</UserOrgProvider>
57+
{ /* ConnectedRouter will use the store from Provider automatically */ }
58+
<ConnectedRouter history={history}>
59+
<Switch>
60+
<Redirect exact from="/" to="/devices" />
61+
<PublicRoute path="/register" component={Register}/>
62+
<ConsoleRoute path="/profile" component={Profile} user={user}/>
63+
<ConsoleRoute exact path="/devices" component={DeviceIndex} />
64+
<ConsoleRoute exact path="/labels" component={LabelIndex} />
65+
<ConsoleRoute path="/devices/:id" component={DeviceShow}/>
66+
<ConsoleRoute path="/labels/:id" component={LabelShow} />
67+
<ConsoleRoute exact path="/integrations" component={ChannelIndex} />
68+
<ConsoleRoute exact path="/integrations/new/:id?" component={ChannelNew} />
69+
<ConsoleRoute exact path="/integrations/:id" component={ChannelShow} />
70+
<ConsoleRoute exact path="/users" component={UserIndex} user={user}/>
71+
<ConsoleRoute exact path="/organizations" component={OrganizationIndex} />
72+
<ConsoleRoute exact path="/datacredits" component={DataCredits} />
73+
<ConsoleRoute exact path="/functions" component={FunctionIndex} />
74+
<ConsoleRoute exact path="/functions/new" component={FunctionNew} />
75+
<ConsoleRoute exact path="/functions/:id" component={FunctionShow} />
76+
<ConsoleRoute exact path="/welcome" component={Welcome} />
77+
</Switch>
78+
</ConnectedRouter>
7879
</ApolloProvider>
79-
</PersistGate>
80-
</Provider>
81-
)
82-
}
80+
</UserOrgProvider>
81+
</PersistGate>
82+
</Provider>
83+
)
8384
}
8485

85-
export default Router
86+
export default Router;

assets/js/actions/organization.js

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
import { push } from 'connected-react-router';
22
import sanitizeHtml from 'sanitize-html'
33
import * as rest from '../util/rest';
4-
import { getOrganizationId, getOrganizationName } from '../util/jwt';
5-
import { logIn } from './auth'
64

7-
export const FETCH_ORGANIZATIONS = 'FETCH_ORGANIZATIONS'
5+
export const FETCHED_ORGANIZATION = 'FETCHED_ORGANIZATIONS'
6+
export const FETCHING_ORGANIZATION = 'FETCHING_ORGANIZATION'
87
export const SWITCHED_ORGANIZATION = 'SWITCHED_ORGANIZATION'
98

9+
export const fetchOrganization = () => {
10+
return async (dispatch) => {
11+
dispatch(fetchingOrganization());
12+
let organization;
13+
try {
14+
organization = JSON.parse(localStorage.getItem('organization'));
15+
} catch (e) {
16+
organization = null;
17+
}
18+
if (!organization) {
19+
// get a new organization id
20+
const organizations = await getOrganizations();
21+
if (organizations && organizations.length) {
22+
localStorage.setItem('organization', JSON.stringify({ id: organizations[0].id }));
23+
return dispatch(fetchedOrganization(organizations[0]));
24+
}
25+
} else {
26+
// validate or replace organization id
27+
const fetchedOrganizations = await getOrganizations();
28+
const org = fetchedOrganizations.find(
29+
org => org.id === organization.id
30+
);
31+
if (org) {
32+
return dispatch(fetchedOrganization(org));
33+
} else if (fetchedOrganizations.length) {
34+
localStorage.setItem(
35+
'organization',
36+
JSON.stringify({ id: fetchedOrganizations[0].id })
37+
);
38+
return dispatch(fetchedOrganization(fetchedOrganizations[0]));
39+
}
40+
}
41+
return dispatch(fetchedOrganization({ id: null, name: "", role: "" }));
42+
}
43+
}
44+
1045
export const createOrganization = (name, noOtherOrg = false) => {
1146
return (dispatch) => {
1247
rest.post('/api/organizations', {
@@ -16,19 +51,29 @@ export const createOrganization = (name, noOtherOrg = false) => {
1651
})
1752
.then(response => {
1853
if (noOtherOrg) {
19-
dispatch(logIn(response.data.jwt))
20-
window.location.reload(true)
54+
dispatch(fetchedOrganization(response));
55+
window.location.reload(true);
2156
}
2257
})
2358
}
2459
}
2560

26-
export const switchOrganization = (id) => {
61+
export const switchOrganization = (organization) => {
2762
return (dispatch) => {
28-
rest.post(`/api/organizations/${id}/switch`)
63+
localStorage.setItem('organization', JSON.stringify(organization));
64+
dispatch(switchedOrganization(organization));
65+
window.location.reload(true);
66+
}
67+
}
68+
69+
export const joinOrganization = (token) => {
70+
let params = { invitation: { token } }
71+
return async dispatch => {
72+
rest.post('/api/users', params)
2973
.then(response => {
30-
dispatch(switchedOrganization(response.data.jwt))
31-
window.location.reload(true)
74+
dispatch(fetchedOrganization(response.data[0]));
75+
localStorage.setItem('organization', JSON.stringify(response.data[0]));
76+
push('/devices');
3277
})
3378
}
3479
}
@@ -40,11 +85,30 @@ export const deleteOrganization = (id) => {
4085
}
4186
}
4287

43-
export const switchedOrganization = (apikey) => {
88+
export const fetchingOrganization = () => {
89+
return {
90+
type: FETCHING_ORGANIZATION
91+
}
92+
}
93+
94+
export const fetchedOrganization = (organization) => {
95+
return {
96+
type: FETCHED_ORGANIZATION,
97+
currentOrganizationId: organization.id,
98+
currentOrganizationName: organization.name,
99+
currentRole: organization.role
100+
}
101+
}
102+
103+
export const switchedOrganization = (organization) => {
44104
return {
45105
type: SWITCHED_ORGANIZATION,
46-
apikey,
47-
currentOrganizationId: getOrganizationId(apikey),
48-
currentOrganizationName: getOrganizationName(apikey)
106+
currentOrganizationId: organization.id,
107+
currentOrganizationName: organization.name
49108
}
50109
}
110+
111+
const getOrganizations = async () => {
112+
const organizations = await rest.get('/api/organizations/');
113+
return organizations.data;
114+
}

assets/js/app.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,40 @@ import "phoenix_html"
2020

2121
// import socket from "./socket"
2222

23-
import React from "react"
24-
import ReactDOM from "react-dom"
25-
import App from "./App.jsx"
23+
import React from 'react'
24+
import ReactDOM from 'react-dom'
25+
import { Route, Switch, BrowserRouter as Router } from 'react-router-dom';
26+
import App from './App.jsx'
27+
import { Auth0Provider } from './components/auth/Auth0Provider'
28+
import Terms from './components/auth/Terms'
29+
import { history } from './store/configureStore'
30+
import { config } from './config/auth0'
31+
32+
const onRedirectCallback = appState => {
33+
// This uses replace instead of push because the Auth0 SDK
34+
// will overwrite the history with it's own special route otherwise
35+
history.replace(
36+
appState && appState.targetUrl
37+
? appState.targetUrl + appState.params
38+
: window.location.pathname
39+
)
40+
}
2641

2742
ReactDOM.render(
28-
<App/>,
43+
<Router history={history}>
44+
<Switch>
45+
<Route exact path="/terms"><Terms/></Route>
46+
<Route>
47+
<Auth0Provider
48+
domain={config.domain}
49+
client_id={config.clientId}
50+
redirect_uri={window.location.origin}
51+
onRedirectCallback={onRedirectCallback}
52+
>
53+
<App/>
54+
</Auth0Provider>
55+
</Route>
56+
</Switch>
57+
</Router>,
2958
document.getElementById("react-root")
3059
)
Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,37 @@
1-
import React, { Component } from 'react';
2-
import { bindActionCreators } from 'redux';
3-
import { connect } from 'react-redux';
4-
import { isJwtExpired } from '../util/jwt.js'
1+
import React, { useEffect } from 'react'
2+
import { bindActionCreators } from 'redux'
3+
import { connect } from 'react-redux'
4+
import { useLocation } from 'react-router-dom'
55
import { fetchIndices } from '../actions/main'
6+
import { fetchOrganization } from '../actions/organization'
67

7-
@connect(mapStateToProps, mapDispatchToProps)
8-
class UserOrgProvider extends Component {
9-
componentDidMount() {
10-
if (this.props.isLoggedIn && this.props.currentOrganizationId) {
11-
this.props.fetchIndices()
12-
}
13-
}
14-
15-
componentDidUpdate(prevProps, prevState) {
16-
const { isLoggedIn, currentOrganizationId, apikey, fetchIndices } = this.props
17-
18-
// if the user has just logged in...
19-
if (!prevProps.isLoggedIn && isLoggedIn && currentOrganizationId) {
20-
return fetchIndices()
21-
}
22-
}
23-
24-
render() {
8+
const UserOrgProvider = (props) => {
9+
useEffect(() => {
10+
props.fetchOrganization();
11+
}, []);
12+
const location = useLocation();
13+
const { loadingOrganization } = props;
14+
if (location.pathname == '/register' || !loadingOrganization) {
2515
return (
2616
<div>
27-
{this.props.children}
17+
{ props.children }
2818
</div>
2919
)
3020
}
21+
return null;
3122
}
3223

3324
function mapStateToProps(state, ownProps) {
3425
return {
35-
isLoggedIn: state.auth.isLoggedIn,
36-
apikey: state.auth.apikey,
37-
currentOrganizationId: state.auth.currentOrganizationId
26+
loadingOrganization: state.organization.loadingOrganization
3827
}
3928
}
4029

4130
function mapDispatchToProps(dispatch) {
4231
return bindActionCreators({
4332
fetchIndices,
33+
fetchOrganization
4434
}, dispatch);
4535
}
4636

47-
export default UserOrgProvider
37+
export default connect(mapStateToProps, mapDispatchToProps)(UserOrgProvider)

0 commit comments

Comments
 (0)