diff --git a/package.json b/package.json index e2e3599..e905f1f 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.11.1", "css-loader": "^0.23.1", + "dotenv": "^2.0.0", "file-loader": "^0.9.0", "html-webpack-plugin": "^2.22.0", "postcss": "^5.0.21", @@ -36,6 +37,8 @@ "webpack-dev-server": "^1.14.1" }, "dependencies": { + "auth0-lock": "^10.0.0", + "jwt-decode": "^2.1.0", "postcss": "^5.1.0", "react": "^15.2.1", "react-dom": "^15.2.1" diff --git a/src/components/App/App.jsx b/src/components/App/App.jsx index 05145cc..a03d23f 100644 --- a/src/components/App/App.jsx +++ b/src/components/App/App.jsx @@ -1,12 +1,54 @@ import React, { Component } from 'react'; +import AuthService from '../../utils/AuthService' import './App.scss'; +import MainMenu from '../MainMenu/MainMenu'; +import Login from '../Login/Login'; + +const options = { + auth: { + params: { + redirectUrl: 'http://localhost:8080', + responseType: 'token' + } + } +}; +const auth = new AuthService(__AUTH0_CLIENT_ID__, __AUTH0_DOMAIN__, options); class App extends Component { + constructor(props) { + super(props); + this.state = { + profile: auth.getProfile() + } + auth.on('profile_updated', (newProfile) => { + this.setState({profile: newProfile}) + }) + this.loginButton = this.loginButton.bind(this); + } + + logout() { + auth.logout() + this.setState({ + profile: auth.getProfile() + }) + } + + loginButton() { + if(this.state.profile.hasOwnProperty('user_id')) + return ; + else + return ; + } + render() { return ( -
hello world
+
+ + {this.loginButton()} +
) } } +// export default App; \ No newline at end of file diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx new file mode 100644 index 0000000..2230114 --- /dev/null +++ b/src/components/Login/Login.jsx @@ -0,0 +1,36 @@ +import React, { Component, PropTypes as T } from 'react'; +import AuthService from '../../utils/AuthService' +import './Login.scss'; + +class Login extends Component { + constructor(props) { + super(props); + this.loginButton = this.loginButton.bind(this); + } + + logout() { +/* this.props.auth.logout() + this.setState({ + profile: this.props.auth.getProfile() + }) */ + } + + loginButton() { + if(this.props.profile.hasOwnProperty('user_id')) + return ; + else + return ; + } + + render() { + return ( +
{this.loginButton()}
+ ) + } +} + +Login.propTypes = { + auth: T.instanceOf(AuthService) +}; + +export default Login; \ No newline at end of file diff --git a/src/components/Login/Login.scss b/src/components/Login/Login.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/MainMenu/MainMenu.jsx b/src/components/MainMenu/MainMenu.jsx new file mode 100644 index 0000000..a8b6402 --- /dev/null +++ b/src/components/MainMenu/MainMenu.jsx @@ -0,0 +1,12 @@ +import React, { Component } from 'react'; +import './MainMenu.scss'; + +class MainMenu extends Component { + render() { + return ( +
{this.props.profile.hasOwnProperty('user_id')?this.props.profile.name+' is logged in':'not logged in'}
+ ) + } +} + +export default MainMenu; \ No newline at end of file diff --git a/src/components/MainMenu/MainMenu.scss b/src/components/MainMenu/MainMenu.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/AuthService.js b/src/utils/AuthService.js new file mode 100644 index 0000000..8adfdce --- /dev/null +++ b/src/utils/AuthService.js @@ -0,0 +1,75 @@ +import { EventEmitter } from 'events' +import { isTokenExpired } from './jwtHelper' +import Auth0Lock from 'auth0-lock' + +export default class AuthService extends EventEmitter { + constructor(clientId, domain, options) { + super() + // Configure Auth0 + this.lock = new Auth0Lock(clientId, domain, options) + // Add callback for lock `authenticated` event + this.lock.on('authenticated', this._doAuthentication.bind(this)) + // Add callback for lock `authorization_error` event + this.lock.on('authorization_error', this._authorizationError.bind(this)) + // binds login functions to keep this context + this.login = this.login.bind(this) + } + + _doAuthentication(authResult){ + // Saves the user token + this.setToken(authResult.idToken) + // Async loads the user profile data + this.lock.getProfile(authResult.idToken, (error, profile) => { + if (error) { + console.log('Error loading the Profile', error) + } else { + this.setProfile(profile) + } + }) + } + + _authorizationError(error){ + // Unexpected authentication error + console.log('Authentication Error', error) + } + + login() { + // Call the show method to display the widget. + this.lock.show() + } + + loggedIn(){ + // Checks if there is a saved token and it's still valid + const token = this.getToken() + return !!token && !isTokenExpired(token) + } + + setProfile(profile){ + // Saves profile data to localStorage + localStorage.setItem('profile', JSON.stringify(profile)) + // Triggers profile_updated event to update the UI + this.emit('profile_updated', profile) + } + + getProfile(){ + // Retrieves the profile data from localStorage + const profile = localStorage.getItem('profile') + return profile ? JSON.parse(localStorage.profile) : {} + } + + setToken(idToken){ + // Saves user token to localStorage + localStorage.setItem('id_token', idToken) + } + + getToken(){ + // Retrieves the user token from localStorage + return localStorage.getItem('id_token') + } + + logout(){ + // Clear user token and profile data from localStorage + localStorage.removeItem('id_token'); + localStorage.removeItem('profile'); + } +} diff --git a/src/utils/jwtHelper.js b/src/utils/jwtHelper.js new file mode 100644 index 0000000..666c0bc --- /dev/null +++ b/src/utils/jwtHelper.js @@ -0,0 +1,21 @@ +import decode from 'jwt-decode'; + +export function getTokenExpirationDate(token){ + const decoded = decode(token) + if(!decoded.exp) { + return null + } + + const date = new Date(0) // The 0 here is the key, which sets the date to the epoch + date.setUTCSeconds(decoded.exp) + return date +} + +export function isTokenExpired(token){ + const date = getTokenExpirationDate(token) + const offsetSeconds = 0 + if (date === null) { + return false + } + return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))) +} diff --git a/webpack.config.js b/webpack.config.js index 2d2b944..c911356 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,25 @@ -var path = require('path'); -var webpack = require('webpack'); -var autoprefixer = require('autoprefixer'); -var precss = require('precss'); -var HtmlWebPackPlugin = require('html-webpack-plugin'); +const NODE_ENV = 'development'; +const dotenv = require('dotenv'); + +const webpack = require('webpack'); +const fs = require('fs'); +const path = require('path'); +const autoprefixer = require('autoprefixer'); +const precss = require('precss'); +const HtmlWebPackPlugin = require('html-webpack-plugin'); + +const dotEnvVars = dotenv.config(); +const envVariables = + Object.assign({}, dotEnvVars); +const defines = + Object.keys(envVariables) + .reduce((memo, key) => { + const val = JSON.stringify(envVariables[key]); + memo[`__${key.toUpperCase()}__`] = val; + return memo; + }, { + __NODE_ENV__: JSON.stringify(NODE_ENV) + }); module.exports = { entry: [ @@ -24,7 +41,8 @@ module.exports = { filename: '../index.html' }), new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin() + new webpack.NoErrorsPlugin(), + new webpack.DefinePlugin(defines) ], module: { loaders: [ @@ -40,11 +58,11 @@ module.exports = { loaders: ['style-loader', 'css-loader'] }, { - test: /\.(png|jpg|svg|woff|woff2)?(\?v=\d+.\d+.\d+)?$/, + test: /\.(png|jpg|svg|woff|woff2)?(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?limit=25000' }, { - test: /\.(eot|ttf)$/, + test: /\.(eot|ttf)$/, loader: 'file-loader' } ] diff --git a/webpack.config.prod.js b/webpack.config.prod.js index 184e5ee..f53d135 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -1,8 +1,25 @@ -var path = require('path'); -var webpack = require('webpack'); -var autoprefixer = require('autoprefixer'); -var precss = require('precss'); -var HtmlWebPackPlugin = require('html-webpack-plugin'); +const NODE_ENV = 'production'; +const dotenv = require('dotenv'); + +const webpack = require('webpack'); +const fs = require('fs'); +const path = require('path'); +const autoprefixer = require('autoprefixer'); +const precss = require('precss'); +const HtmlWebPackPlugin = require('html-webpack-plugin'); + +const dotEnvVars = dotenv.config(); +const envVariables = + Object.assign({}, dotEnvVars); +const defines = + Object.keys(envVariables) + .reduce((memo, key) => { + const val = JSON.stringify(envVariables[key]); + memo[`__${key.toUpperCase()}__`] = val; + return memo; + }, { + __NODE_ENV__: JSON.stringify(NODE_ENV) + }); module.exports = { devtool: 'cheap-module-source-map', @@ -35,7 +52,8 @@ module.exports = { warnings: false }, }), - new webpack.NoErrorsPlugin() + new webpack.NoErrorsPlugin(), + new webpack.DefinePlugin(defines) ], module: { loaders: [