├── .babelrc
├── .env
├── .gitignore
├── README.md
├── karma.conf.js
├── package.json
├── scripts
└── prepublish.sh
├── src
├── app.css
├── app.js
├── containers
│ └── App
│ │ ├── App.js
│ │ ├── App.spec.js
│ │ └── styles.module.css
├── routes.js
├── styles
│ ├── base.css
│ ├── colors.css
│ └── queries.css
├── utils
│ ├── AuthService.js
│ ├── constants.js
│ └── jwtHelper.js
└── views
│ └── Main
│ ├── Container.js
│ ├── Home
│ ├── Home.js
│ └── styles.module.css
│ ├── Instructor
│ ├── Instructor.js
│ └── styles.module.css
│ ├── Login
│ ├── Login.js
│ └── styles.module.css
│ ├── NewInstructor
│ ├── NewInstructor.js
│ └── styles.module.css
│ ├── Profile
│ ├── Profile.js
│ └── styles.module.css
│ ├── routes.js
│ └── styles.module.css
├── tests.webpack.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "env": {
4 | "development": {
5 | "presets": ["react-hmre"]
6 | },
7 | "production": {
8 | "presets": []
9 | },
10 | "test": {
11 | "presets": []
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/react-user-authentication-master/2f05ef87105da32992475a3ed729062efae08f2d/.env
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | dist
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Authentication for Front End Masters
2 |
3 | ## Running the App
4 |
5 | Install the dependencies:
6 |
7 | ```bash
8 | npm install
9 | ```
10 |
11 | Start the app:
12 |
13 | ```bash
14 | npm start
15 | ```
16 |
17 | The app will be served at `localhost:3000`.
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var argv = require('yargs').argv;
2 | var path = require('path');
3 |
4 | var webpackConfig = require('./webpack.config');
5 |
6 | module.exports = function(config) {
7 | config.set({
8 | basePath: '',
9 | frameworks: ['mocha', 'chai'],
10 | files: [
11 | 'tests.webpack.js'
12 | ],
13 |
14 | preprocessors: {
15 | // add webpack as preprocessor
16 | 'tests.webpack.js': ['webpack', 'sourcemap'],
17 | },
18 |
19 | webpack: webpackConfig,
20 | webpackServer: {
21 | noInfo: true
22 | },
23 |
24 | plugins: [
25 | 'karma-mocha',
26 | 'karma-chai',
27 | 'karma-webpack',
28 | 'karma-phantomjs-launcher',
29 | 'karma-spec-reporter',
30 | 'karma-sourcemap-loader'
31 | ],
32 |
33 | reporters: ['spec'],
34 | port: 9876,
35 | colors: true,
36 | logLevel: config.LOG_INFO,
37 | browsers: ['PhantomJS'],
38 | singleRun: !argv.watch
39 | })
40 | };
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth0-react-sample",
3 | "version": "1.0.0",
4 | "description": "A minimal reactJS sample application showing auth0 integration",
5 | "repository": "https://github.com/auth0-samples/auth0-react-sample",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "npm-run-all --parallel dev-server",
9 | "dev-server": "cross-env NODE_ENV=development hjs-dev-server",
10 | "clean": "rimraf dist",
11 | "build": "npm run clean && cross-env NODE_ENV=production webpack",
12 | "publish_pages": "gh-pages -d dist",
13 | "ghpages": "npm run build && npm run publish_pages",
14 | "test": "cross-env NODE_ENV=test karma start karma.conf.js",
15 | "test:watch": "npm run test -- --watch"
16 | },
17 | "devDependencies": {
18 | "autoprefixer": "^6.3.6",
19 | "babel-core": "^6.7.7",
20 | "babel-loader": "^6.2.4",
21 | "babel-plugin-transform-es2015-modules-umd": "^6.8.0",
22 | "babel-polyfill": "^6.7.4",
23 | "babel-preset-es2015": "^6.6.0",
24 | "babel-preset-react": "^6.5.0",
25 | "babel-preset-react-hmre": "^1.1.1",
26 | "babel-preset-stage-0": "^6.5.0",
27 | "babel-register": "^6.7.2",
28 | "chai": "^3.5.0",
29 | "chai-enzyme": "^0.4.2",
30 | "cheerio": "^0.20.0",
31 | "cross-env": "^1.0.8",
32 | "css-loader": "^0.23.1",
33 | "cssnano": "^3.5.2",
34 | "dotenv": "^2.0.0",
35 | "enzyme": "^2.2.0",
36 | "expect": "^1.18.0",
37 | "file-loader": "^0.8.5",
38 | "gh-pages": "^0.11.0",
39 | "hjs-webpack": "^8.1.0",
40 | "jasmine-core": "^2.4.1",
41 | "json-loader": "^0.5.4",
42 | "karma": "^0.13.22",
43 | "karma-chai": "^0.1.0",
44 | "karma-jasmine": "^0.3.8",
45 | "karma-mocha": "^1.0.1",
46 | "karma-phantomjs-launcher": "^1.0.0",
47 | "karma-sourcemap-loader": "^0.3.7",
48 | "karma-spec-reporter": "0.0.26",
49 | "karma-webpack": "^1.7.0",
50 | "mocha": "^2.4.5",
51 | "npm-run-all": "^2.3.0",
52 | "phantomjs-polyfill": "0.0.2",
53 | "phantomjs-prebuilt": "^2.1.7",
54 | "postcss-loader": "^0.9.1",
55 | "precss": "^1.4.0",
56 | "prettyjson": "^1.1.3",
57 | "react-addons-test-utils": "^15.0.2",
58 | "sinon": "^1.17.4",
59 | "style-loader": "^0.13.1",
60 | "transform-loader": "^0.2.3",
61 | "url-loader": "^0.5.7",
62 | "webpack": "^1.13.0",
63 | "yargs": "^4.7.1"
64 | },
65 | "dependencies": {
66 | "auth0-lock": "^11.27.0",
67 | "bootstrap": "^3.3.7",
68 | "classnames": "^2.2.5",
69 | "express-jwt": "^5.0.0",
70 | "jwt-decode": "^2.1.0",
71 | "md5": "^2.2.1",
72 | "react": "^15.3.1",
73 | "react-bootstrap": "^0.30.0-rc.1",
74 | "react-dom": "^15.3.1",
75 | "react-router": "^2.8.0",
76 | "react-router-bootstrap": "^0.23.1"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/scripts/prepublish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "=> Compiling..."
4 | echo ""
5 | rm -rf ./dist
6 | NODE_ENV=production ./node_modules/.bin/webpack
7 | echo ""
8 | echo "=> Complete"
9 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/react-user-authentication-master/2f05ef87105da32992475a3ed729062efae08f2d/src/app.css
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import 'bootstrap/dist/css/bootstrap.css'
5 | import './app.css'
6 |
7 | import App from 'containers/App/App'
8 |
9 | import {browserHistory} from 'react-router'
10 | import makeRoutes from './routes'
11 |
12 | const routes = makeRoutes()
13 |
14 | const mountNode = document.querySelector('#root');
15 | ReactDOM.render(
16 | ,
18 | mountNode)
19 |
--------------------------------------------------------------------------------
/src/containers/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import { Router } from 'react-router'
3 |
4 | class App extends React.Component {
5 | static contextTypes = {
6 | router: PropTypes.object
7 | }
8 |
9 | static propTypes = {
10 | history: PropTypes.object.isRequired,
11 | routes: PropTypes.element.isRequired
12 | }
13 |
14 | get content() {
15 | return (
16 |
19 | )
20 | }
21 |
22 | render () {
23 | return (
24 |
25 | {this.content}
26 |
27 | )
28 | }
29 | }
30 |
31 | export default App
32 |
--------------------------------------------------------------------------------
/src/containers/App/App.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { expect } from 'chai'
3 | import { shallow } from 'enzyme'
4 |
5 | import App from './App'
6 | import styles from './styles.module.css'
7 |
8 | describe('', () => {
9 | let wrapper
10 | let history = {}
11 | beforeEach(() => {
12 | wrapper =
13 | shallow()
14 | })
15 |
16 | it('has a Router component', () => {
17 | expect(wrapper.find('Router'))
18 | .to.have.length(1)
19 | })
20 |
21 | it('passes a history prop', () => {
22 | const props = wrapper.find('Router').props();
23 |
24 | expect(props.history)
25 | .to.be.defined
26 | })
27 |
28 | })
29 |
--------------------------------------------------------------------------------
/src/containers/App/styles.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | }
4 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {browserHistory, Router, Route, Redirect} from 'react-router'
3 |
4 | import makeMainRoutes from './views/Main/routes'
5 |
6 | export const makeRoutes = () => {
7 | const main = makeMainRoutes()
8 |
9 | return (
10 |
11 | {main}
12 |
13 | )
14 | }
15 |
16 | export default makeRoutes
17 |
--------------------------------------------------------------------------------
/src/styles/base.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --topbar-height: 80px;
3 | --padding: 25px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/colors.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --dark: #404040;
3 | --light-gray: #a2a2a2;
4 | --white: #ffffff;
5 | --highlight: #48b5e9;
6 | --heading-color: var(--highlight);
7 | }
8 |
--------------------------------------------------------------------------------
/src/styles/queries.css:
--------------------------------------------------------------------------------
1 | @custom-media --screen-phone (width <= 35.5em);
2 | @custom-media --screen-phone-lg (width > 35.5em);
3 |
4 | @custom-media --screen-sm var(--screen-phone) and (width < 48em);
5 | @custom-media --screen-md (width >= 48em) and (width < 64em);
6 | @custom-media --screen-lg (width >= 64em) and (width < 80em);
7 | @custom-media --screen-xl (width >= 80em);
8 |
--------------------------------------------------------------------------------
/src/utils/AuthService.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events'
2 | import { isTokenExpired } from './jwtHelper'
3 | import { browserHistory } from 'react-router'
4 | import jwtDecode from 'jwt-decode'
5 |
6 | import { API_URL } from './constants'
7 |
8 | export default class AuthService extends EventEmitter {
9 | constructor() {
10 | super()
11 |
12 | // binds login functions to keep this context
13 | this.login = this.login.bind(this)
14 | }
15 |
16 | _doAuthentication(endpoint, values) {
17 | return this.fetch(`${API_URL}/${endpoint}`, {
18 | method: 'POST',
19 | body: JSON.stringify(values),
20 | headers: { 'Content-Type': 'application/json' }
21 | })
22 | }
23 |
24 | login(user, password) {
25 | return this._doAuthentication('users/authenticate', { user, password })
26 | }
27 |
28 | signup(username, email, password) {
29 | return this._doAuthentication('users', { username, email, password })
30 | }
31 |
32 | isAuthenticated() {
33 | // Checks if there is a saved token and it's still valid
34 | const token = localStorage.getItem('token')
35 | if (token) {
36 | return !isTokenExpired(token)
37 | } else {
38 | return false
39 | }
40 | }
41 |
42 | isAdmin() {
43 | return jwtDecode(this.getToken()).scope === 'admin'
44 | }
45 |
46 | finishAuthentication(token) {
47 | localStorage.setItem('token', token)
48 | }
49 |
50 | getToken() {
51 | // Retrieves the user token from localStorage
52 | return localStorage.getItem('token')
53 | }
54 |
55 | logout() {
56 | // Clear user token and profile data from localStorage
57 | localStorage.removeItem('token')
58 | }
59 |
60 | _checkStatus(response) {
61 | // raises an error in case response status is not a success
62 | if (response.status >= 200 && response.status < 300) {
63 | return response
64 | } else {
65 | var error = new Error(response.statusText)
66 | error.response = response
67 | return error
68 | }
69 | }
70 |
71 | fetch(url, options) {
72 | // performs api calls sending the required authentication headers
73 | const headers = {
74 | 'Accept': 'application/json',
75 | 'Content-Type': 'application/json'
76 | }
77 |
78 | if (this.isAuthenticated()) {
79 | headers['Authorization'] = 'Bearer ' + this.getToken()
80 | }
81 |
82 | return fetch(url, {
83 | headers,
84 | ...options
85 | })
86 | .then(response => response.json())
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const API_URL = 'https://fem-user-authentication-api.herokuapp.com/api'
2 |
--------------------------------------------------------------------------------
/src/utils/jwtHelper.js:
--------------------------------------------------------------------------------
1 | import decode from 'jwt-decode'
2 |
3 | export function getTokenExpirationDate(token) {
4 | const decoded = decode(token)
5 | if(!decoded.exp) {
6 | return null
7 | }
8 |
9 | const date = new Date(0) // The 0 here is the key, which sets the date to the epoch
10 | date.setUTCSeconds(decoded.exp)
11 | return date
12 | }
13 |
14 | export function isTokenExpired(token) {
15 | const date = getTokenExpirationDate(token)
16 | const offsetSeconds = 0
17 | if (date === null) {
18 | return false
19 | }
20 | return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)))
21 | }
22 |
--------------------------------------------------------------------------------
/src/views/Main/Container.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import { Nav, Navbar, NavItem, Header, Brand } from 'react-bootstrap'
3 | import { Link } from 'react-router'
4 | import { LinkContainer } from 'react-router-bootstrap'
5 | import AuthService from 'utils/AuthService'
6 |
7 | const auth = new AuthService()
8 |
9 | export class Container extends React.Component {
10 | static contextTypes = {
11 | router: T.object
12 | }
13 |
14 | logout(){
15 | auth.logout()
16 | this.context.router.push('/home')
17 | }
18 |
19 | render() {
20 | let children = null
21 | if (this.props.children) {
22 | children = React.cloneElement(this.props.children, {
23 | auth: this.props.route.auth //sends auth instance to children
24 | })
25 | }
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | React Authentication
34 |
35 |
36 |
37 |
49 |
60 |
61 |
62 | { children }
63 |
64 |
65 | )
66 | }
67 | }
68 |
69 | export default Container
70 |
--------------------------------------------------------------------------------
/src/views/Main/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import {Jumbotron, Button} from 'react-bootstrap'
3 | import AuthService from 'utils/AuthService'
4 | import styles from './styles.module.css'
5 | import { Link } from 'react-router'
6 |
7 | export class Home extends React.Component {
8 | static contextTypes = {
9 | router: T.object
10 | }
11 |
12 | static propTypes = {
13 | auth: T.instanceOf(AuthService)
14 | }
15 |
16 | logout(){
17 | this.props.auth.logout()
18 | this.context.router.push('/home')
19 | }
20 |
21 | render() {
22 | const { isAuthenticated } = this.props.auth
23 | return (
24 |
25 | Welcome!
26 | This app demonstrates how to add authentication to a React app. More specifically, it covers how to:
27 |
28 | - Add a login/signup area which returns a JSON Web Token that is saved in
localStorage
29 | - Conditionally hide and show various parts of the application depending on the user's authentication state
30 | - Create a profile area which displays user information from the payload of the JWT
31 | - Protect client-side routes with the
onEnter
route event
32 | - Make requests for server resources protected by JWT middleware on the server
33 | - Make requests for server resources that require a specific
scope
to be present in the JWT payload
34 |
35 | { !isAuthenticated() &&
36 |
37 |
38 |
39 | }
40 | { isAuthenticated() &&
41 |
42 | }
43 |
44 | )
45 | }
46 | }
47 |
48 | export default Home
49 |
--------------------------------------------------------------------------------
/src/views/Main/Home/styles.module.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/react-user-authentication-master/2f05ef87105da32992475a3ed729062efae08f2d/src/views/Main/Home/styles.module.css
--------------------------------------------------------------------------------
/src/views/Main/Instructor/Instructor.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import {Panel, Col, Button} from 'react-bootstrap'
3 | import styles from './styles.module.css'
4 | import { Link } from 'react-router'
5 | import jwtDecode from 'jwt-decode'
6 | import md5 from 'md5'
7 | import { API_URL } from './../../../utils/constants'
8 |
9 | function getInstructorListItem (instructor) {
10 | const avatarUrl = `https://www.gravatar.com/avatar/${md5(instructor.email).toLowerCase().trim()}s=200`
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | { instructor.first_name + ' ' + instructor.last_name }
18 | { instructor.email }
19 | { instructor.company }
20 |
21 |
22 | )
23 | }
24 |
25 | export class Instructor extends React.Component {
26 | static contextTypes = {
27 | router: T.object
28 | }
29 |
30 | constructor(props, context) {
31 | super(props, context)
32 | this.state = {
33 | instructors: []
34 | }
35 | this.props.auth.fetch(`${API_URL}/instructors`).then(data => this.setState({ instructors: data }))
36 | }
37 |
38 | onAddInstructorClick() {
39 | this.context.router.push('/instructor/new')
40 | }
41 |
42 | render() {
43 | let instructorList
44 | if (this.state.instructors) {
45 | instructorList = this.state.instructors.map(instructor => getInstructorListItem(instructor))
46 | }
47 |
48 | const { auth } = this.props
49 |
50 | return (
51 |
52 |
Front End Masters Instructors
53 | { auth.isAuthenticated() && auth.isAdmin() &&
54 |
57 | }
58 |
59 | {instructorList}
60 |
61 |
62 | )
63 | }
64 | }
65 |
66 | export default Instructor
67 |
--------------------------------------------------------------------------------
/src/views/Main/Instructor/styles.module.css:
--------------------------------------------------------------------------------
1 | h3 {
2 | margin-top: 0;
3 | }
4 |
5 | button {
6 | margin-bottom: 15px !important;
7 | }
8 |
9 | ul {
10 | padding: 0;
11 | }
--------------------------------------------------------------------------------
/src/views/Main/Login/Login.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import {Tabs, Col, Tab, ButtonToolbar, Button, FormGroup, FormControl, ControlLabel, Alert} from 'react-bootstrap'
3 | import AuthService from 'utils/AuthService'
4 | import styles from './styles.module.css'
5 |
6 | export class Login extends React.Component {
7 | constructor(context) {
8 | super()
9 | this.state = {
10 | user: '',
11 | username: '',
12 | email: '',
13 | password: '',
14 | loginError: '',
15 | signupError: ''
16 | }
17 | }
18 | static contextTypes = {
19 | router: T.object
20 | }
21 |
22 | static propTypes = {
23 | location: T.object,
24 | auth: T.instanceOf(AuthService)
25 | }
26 |
27 | onLoginSubmit(event) {
28 | event.preventDefault()
29 | const { user, password } = this.state
30 | if (user && password) {
31 | this.props.auth.login(user, password)
32 | .then(result => {
33 | if (!result.token) {
34 | this.setState({loginError: result.message})
35 | return
36 | }
37 | this.props.auth.finishAuthentication(result.token)
38 | this.context.router.push('/profile')
39 | })
40 | }
41 | }
42 |
43 | onSignupSubmit(event) {
44 | event.preventDefault()
45 | const { username, email, password } = this.state
46 | if (username && email && password) {
47 | this.props.auth.signup(username, email, password)
48 | .then(result => {
49 | if (!result.token) {
50 | this.setState({signupError: result.message})
51 | return
52 | }
53 | this.props.auth.finishAuthentication(result.token)
54 | this.context.router.push('/profile')
55 | })
56 | }
57 | }
58 |
59 | handleChange(event) {
60 | this.setState({[event.target.name]: event.target.value})
61 | }
62 |
63 | render() {
64 | const { auth } = this.props
65 | return (
66 |
67 |
68 |
69 |
93 | { this.state.loginError &&
94 | {this.state.loginError}
95 | }
96 |
97 |
98 |
131 | { this.state.signupError &&
132 | {this.state.signupError}
133 | }
134 |
135 |
136 |
137 | )
138 | }
139 | }
140 |
141 | export default Login
142 |
--------------------------------------------------------------------------------
/src/views/Main/Login/styles.module.css:
--------------------------------------------------------------------------------
1 | .tab-content {
2 | margin-top: 15px !important;
3 | }
--------------------------------------------------------------------------------
/src/views/Main/NewInstructor/NewInstructor.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import {Panel, Col, Button, FormGroup, FormControl, ControlLabel, Alert} from 'react-bootstrap'
3 | import styles from './styles.module.css'
4 | import jwtDecode from 'jwt-decode'
5 | import md5 from 'md5'
6 |
7 | import { API_URL } from './../../../utils/constants'
8 |
9 | export class NewInstructor extends React.Component {
10 | static contextTypes = {
11 | router: T.object
12 | }
13 |
14 | constructor(props, context) {
15 | super(props, context)
16 | this.state = {
17 | first_name: '',
18 | last_name: '',
19 | email: '',
20 | company: '',
21 | error: ''
22 | }
23 | }
24 |
25 | handleChange(event) {
26 | this.setState({ [event.target.name]: event.target.value })
27 | }
28 |
29 | onNewInstructorSubmit() {
30 | const { first_name, last_name, email, company } = this.state
31 | const data = { first_name, last_name, email, company }
32 | this.props.auth.fetch(`${API_URL}/instructors`, {
33 | method: 'POST',
34 | body: JSON.stringify(data)
35 | }).then(result => {
36 | if (result.error) {
37 | this.setState({ error: result })
38 | return
39 | }
40 | this.context.router.push('/instructor')
41 | })
42 | }
43 |
44 | render() {
45 | return (
46 |
47 |
48 | First Name
49 |
57 |
58 |
59 | Last Name
60 |
68 |
69 |
70 | Email
71 |
79 |
80 |
81 | Company
82 |
90 |
91 | { this.state.error &&
92 | {this.state.error.message}
93 | }
94 |
95 |
96 | )
97 | }
98 | }
99 |
100 | export default NewInstructor
101 |
--------------------------------------------------------------------------------
/src/views/Main/NewInstructor/styles.module.css:
--------------------------------------------------------------------------------
1 | h3 {
2 | margin-top: 0;
3 | }
4 |
5 | button {
6 | margin-bottom: 15px !important;
7 | }
8 |
9 | ul {
10 | padding: 0;
11 | }
--------------------------------------------------------------------------------
/src/views/Main/Profile/Profile.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes as T } from 'react'
2 | import {Panel, Col} from 'react-bootstrap'
3 | import AuthService from 'utils/AuthService'
4 | import styles from './styles.module.css'
5 | import { Link } from 'react-router'
6 | import jwtDecode from 'jwt-decode'
7 |
8 | export class Profile extends React.Component {
9 | static contextTypes = {
10 | router: T.object
11 | }
12 |
13 | static propTypes = {
14 | auth: T.instanceOf(AuthService)
15 | }
16 |
17 | constructor(props, context) {
18 | super(props, context)
19 | this.state = {
20 | profile: jwtDecode(this.props.auth.getToken()),
21 | payload: jwtDecode(this.props.auth.getToken())
22 | }
23 | this.state.profile.gravatar = `${this.state.profile.gravatar}?s=200`
24 | }
25 |
26 | render(){
27 | const { profile, payload } = this.state
28 | return (
29 |
30 |
Profile
31 |
32 |
33 |
34 |
35 |
36 | {profile.username}
37 |
38 | { profile.email }
39 | Payload
40 | { JSON.stringify(payload, null, 2) }
41 |
42 |
43 |
44 | )
45 | }
46 | }
47 |
48 | export default Profile
49 |
--------------------------------------------------------------------------------
/src/views/Main/Profile/styles.module.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3solution/react-user-authentication-master/2f05ef87105da32992475a3ed729062efae08f2d/src/views/Main/Profile/styles.module.css
--------------------------------------------------------------------------------
/src/views/Main/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Route, IndexRedirect} from 'react-router'
3 | import AuthService from 'utils/AuthService'
4 | import Container from './Container'
5 | import Home from './Home/Home'
6 | import Login from './Login/Login'
7 | import Profile from './Profile/Profile'
8 | import Instructor from './Instructor/Instructor'
9 | import NewInstructor from './NewInstructor/NewInstructor'
10 |
11 | const auth = new AuthService()
12 |
13 | // onEnter callback to validate authentication in private routes
14 | const requireAuth = (nextState, replace) => {
15 | if (!auth.isAuthenticated()) {
16 | replace({ pathname: '/login' })
17 | }
18 | }
19 |
20 | const requireAdmin = (nextState, replace) => {
21 | if (!auth.isAuthenticated() || !auth.isAdmin()) {
22 | replace({ pathname: '/login' })
23 | }
24 | }
25 |
26 | export const makeMainRoutes = () => {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default makeMainRoutes
40 |
--------------------------------------------------------------------------------
/src/views/Main/styles.module.css:
--------------------------------------------------------------------------------
1 | @import url("../../styles/base.css");
2 | @import url("../../styles/queries.css");
3 |
4 | .wrapper {
5 | overflow-y: scroll;
6 | display: flex;
7 | margin: 0;
8 |
9 | height: 100vh;
10 | -webkit-box-orient: horizontal;
11 | -o-box-orient: horizontal;
12 |
13 | flex-direction: column;
14 |
15 | @media (--screen-phone-lg) {
16 | flex-direction: row;
17 | }
18 |
19 | }
20 |
21 | .content {
22 | position: relative;
23 | top: var(--topbar-height);
24 | left: 0;
25 |
26 | flex: 1;
27 | order: 1;
28 |
29 | @media (--screen-phone-lg) {
30 | flex: 2;
31 | order: 2;
32 | }
33 | }
34 |
35 | .mainTitle{
36 | text-align: center;
37 | }
38 |
--------------------------------------------------------------------------------
/tests.webpack.js:
--------------------------------------------------------------------------------
1 | require('babel-polyfill');
2 | // some setup first
3 |
4 | var chai = require('chai');
5 | var chaiEnzyme = require('chai-enzyme');
6 |
7 | chai.use(chaiEnzyme())
8 |
9 | var context = require.context('./src', true, /\.spec\.js$/);
10 | context.keys().forEach(context);
11 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const NODE_ENV = process.env.NODE_ENV;
2 | const dotenv = require('dotenv');
3 |
4 | const webpack = require('webpack');
5 | const fs = require('fs');
6 | const path = require('path'),
7 | join = path.join,
8 | resolve = path.resolve;
9 |
10 | const getConfig = require('hjs-webpack');
11 |
12 | const isDev = NODE_ENV === 'development';
13 | const isTest = NODE_ENV === 'test';
14 |
15 | const root = resolve(__dirname);
16 | const src = join(root, 'src');
17 | const modules = join(root, 'node_modules');
18 | const dest = join(root, 'dist');
19 |
20 | var config = getConfig({
21 | isDev: isDev,
22 | in: join(src, 'app.js'),
23 | out: dest,
24 | html: function (context) {
25 | return {
26 | 'index.html': context.defaultTemplate({
27 | title: 'auth0 React Sample',
28 | publicPath: isDev ? 'http://localhost:3000/' : '',
29 | meta: {
30 | 'name': 'auth0 React Sample',
31 | 'description': 'A minimal reactJS sample application showing auth0 integration'
32 | }
33 | })
34 | }
35 | },
36 | devServer: {
37 | proxy: {
38 | context: "/api",
39 | options: {
40 | target: "http://localhost:3001"
41 | }
42 | }
43 | }
44 | });
45 |
46 | // ENV variables
47 | const dotEnvVars = dotenv.config();
48 | const environmentEnv = dotenv.config({
49 | path: join(root, 'config', `${NODE_ENV}.config.js`),
50 | silent: true,
51 | });
52 | const envVariables =
53 | Object.assign({}, dotEnvVars, environmentEnv);
54 |
55 | const defines =
56 | Object.keys(envVariables)
57 | .reduce((memo, key) => {
58 | const val = JSON.stringify(envVariables[key]);
59 | memo[`__${key.toUpperCase()}__`] = val;
60 | return memo;
61 | }, {
62 | __NODE_ENV__: JSON.stringify(NODE_ENV)
63 | });
64 |
65 | config.plugins = [
66 | new webpack.DefinePlugin(defines)
67 | ].concat(config.plugins);
68 | // END ENV variables
69 |
70 | // CSS modules
71 | const cssModulesNames = `${isDev ? '[path][name]__[local]__' : ''}[hash:base64:5]`;
72 |
73 | const matchCssLoaders = /(^|!)(css-loader)($|!)/;
74 |
75 | const findLoader = (loaders, match) => {
76 | const found = loaders.filter(l => l && l.loader && l.loader.match(match))
77 | return found ? found[0] : null;
78 | }
79 | // existing css loader
80 | const cssloader =
81 | findLoader(config.module.loaders, matchCssLoaders);
82 |
83 | const newloader = Object.assign({}, cssloader, {
84 | test: /\.module\.css$/,
85 | include: [src],
86 | loader: cssloader.loader.replace(matchCssLoaders, `$1$2?modules&localIdentName=${cssModulesNames}$3`)
87 | })
88 | config.module.loaders.push(newloader);
89 | cssloader.test = new RegExp(`^(?!.*(module|bootstrap)).*${cssloader.test.source}`)
90 | cssloader.loader = newloader.loader
91 |
92 | config.module.loaders.push({
93 | test: /bootstrap\.css$/,
94 | include: [modules],
95 | loader: 'style-loader!css-loader'
96 | })
97 |
98 | // postcss
99 | config.postcss = [].concat([
100 | require('precss')({}),
101 | require('autoprefixer')({}),
102 | require('cssnano')({})
103 | ])
104 | // END postcss
105 |
106 | // Roots
107 | config.resolve.root = [src, modules]
108 | config.resolve.alias = {
109 | 'css': join(src, 'styles'),
110 | 'containers': join(src, 'containers'),
111 | 'components': join(src, 'components'),
112 | 'utils': join(src, 'utils'),
113 |
114 | 'styles': join(src, 'styles')
115 | }
116 | // end Roots
117 |
118 | // Testing
119 | if (isTest) {
120 | config.externals = {
121 | 'react/addons': true,
122 | 'react/lib/ReactContext': true,
123 | 'react/lib/ExecutionEnvironment': true,
124 | }
125 | config.module.noParse = /[/\\]sinon\.js/;
126 | config.resolve.alias['sinon'] = 'sinon/pkg/sinon';
127 |
128 | config.plugins = config.plugins.filter(p => {
129 | const name = p.constructor.toString();
130 | const fnName = name.match(/^function (.*)\((.*\))/)
131 |
132 | const idx = [
133 | 'DedupePlugin',
134 | 'UglifyJsPlugin'
135 | ].indexOf(fnName[1]);
136 | return idx < 0;
137 | })
138 | }
139 | // End Testing
140 |
141 | module.exports = config;
142 |
--------------------------------------------------------------------------------