diff --git a/client/package.json b/client/package.json index a7340d9..965b1d5 100644 --- a/client/package.json +++ b/client/package.json @@ -2,7 +2,10 @@ "name": "client", "version": "0.1.0", "private": true, + "proxy": "http://localhost:3001", "devDependencies": { + "eslint-plugin-babel": "^4.1.0", + "eslint-plugin-react": "^6.10.0", "react-scripts": "0.9.3" }, "dependencies": { diff --git a/client/src/App.js b/client/src/App.js index d03f4a8..f4760f0 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { BrowserRouter, Route } from 'react-router-dom'; +import { BrowserRouter, Route, Switch } from 'react-router-dom'; import './App.css'; import SignUpSignIn from './SignUpSignIn'; import TopNavbar from './TopNavbar'; @@ -7,7 +7,102 @@ import Secret from './Secret'; import axios from 'axios'; class App extends Component { + constructor(props) { + super(props); + this.state = { + signUpSignInError: '', + authenicated: localStorage.getItem('token') || false + }; + } + + handleSignUp(credentials) { + const { username, password, confirmPassword } = credentials; + + if (!username.trim() || !password.trim() || password.trim() !== confirmPassword.trim()) { + this.setState({ + signUpSignInError: 'Must provide all fields' + }); + } else { + axios.post('/api/signup', credentials) + .then(resp => { + const { token } = resp.data; + // palces token + localStorage.setItem('token', token); + + this.setState({ + signUpSignInError: '', + authenicated: token + }); + }); + } + } + + handleSignIn() { + /* const { username, password } = credentials; + + if (!username.trim() || !password.trim() ) { + this.setState({ + signUpSignInError: 'Must provide all fields' + }); + } else { + axios.get('/api/signin', credentials) + .then(resp => { + const { token } = resp.data; + // places token + localStorage.setItem('token', token); + + this.setState({ + signUpSignInError: '', + authenicated: token + }); + }); + } */ + } + + handleSignOut() { + // removes token + localStorage.removeItem('token'); + + this.setState({ + authenicated: false + }); + } + + renderSignUpSignIn() { + return ( + + ); + } + + renderApp() { + return ( +
+ +

I am protected!

} /> + +

NOT FOUND!

} /> +
+
+ ); + } + + render() { + return ( + +
+ + {this.state.authenicated ? this.renderApp() : this.renderSignUpSignIn()} +
+
+ ); + } } export default App; diff --git a/client/src/SignUp.js b/client/src/SignUp.js index 7b533c2..46cbd6f 100644 --- a/client/src/SignUp.js +++ b/client/src/SignUp.js @@ -9,7 +9,7 @@ class SignUp extends Component { username: '', password: '', confirmPassword: '', - } + }; } handleSubmit(event) { diff --git a/client/src/SignUpSignIn.js b/client/src/SignUpSignIn.js index 8bbfc4f..6cac960 100644 --- a/client/src/SignUpSignIn.js +++ b/client/src/SignUpSignIn.js @@ -19,7 +19,7 @@ class SignUpSignIn extends Component { {this.props.error && this.renderError()} - + Sign In @@ -27,7 +27,7 @@ class SignUpSignIn extends Component { - ) + ); } } diff --git a/client/src/TopNavbar.js b/client/src/TopNavbar.js index e5d36b9..6b5b85d 100644 --- a/client/src/TopNavbar.js +++ b/client/src/TopNavbar.js @@ -1,6 +1,6 @@ import React, { PropTypes } from 'react'; import { Navbar, Nav, NavItem } from 'react-bootstrap'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; const TopNavbar = (props) => { return ( @@ -13,6 +13,7 @@ const TopNavbar = (props) => { { props.showNavItems ? + ( - : null + ) : null } ); -} +}; TopNavbar.propTypes = { onSignOut: PropTypes.func.isRequired, diff --git a/client/yarn.lock b/client/yarn.lock index 1860cc5..4d7b4ff 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -146,6 +146,13 @@ array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" +array.prototype.find@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.3.tgz#08c3ec33e32ec4bab362a2958e686ae92f59271d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -1577,6 +1584,13 @@ default-require-extensions@^1.0.0: dependencies: strip-bom "^2.0.0" +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" @@ -1751,6 +1765,23 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.0" + is-callable "^1.1.3" + is-regex "^1.0.3" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: version "0.10.12" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" @@ -1858,6 +1889,10 @@ eslint-module-utils@^1.0.0: debug "2.2.0" pkg-dir "^1.0.0" +eslint-plugin-babel@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-4.1.1.tgz#ef285c87039b67beb3bbd227f5b0eed4fb376b87" + eslint-plugin-flowtype@2.21.0: version "2.21.0" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.21.0.tgz#a47e85abcdd181d37a336054bd552149ae387d9c" @@ -1894,6 +1929,16 @@ eslint-plugin-react@6.4.1: doctrine "^1.2.2" jsx-ast-utils "^1.3.1" +eslint-plugin-react@^6.10.0: + version "6.10.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78" + dependencies: + array.prototype.find "^2.0.1" + doctrine "^1.2.2" + has "^1.0.1" + jsx-ast-utils "^1.3.4" + object.assign "^4.0.4" + eslint@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.8.1.tgz#7d02db44cd5aaf4fa7aa489e1f083baa454342ba" @@ -2215,6 +2260,10 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -2273,7 +2322,7 @@ fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2: +function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" @@ -2647,12 +2696,20 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + is-ci@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" dependencies: ci-info "^1.0.0" +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + is-dotfile@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d" @@ -2750,6 +2807,12 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-regex@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" @@ -2766,6 +2829,10 @@ is-svg@^2.0.0: dependencies: html-comment-regex "^1.1.0" +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3161,7 +3228,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.3.6" -jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.1: +jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.1, jsx-ast-utils@^1.3.4: version "1.4.0" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.0.tgz#5afe38868f56bc8cc7aeaef0100ba8c75bd12591" dependencies: @@ -3625,6 +3692,18 @@ object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-keys@^1.0.10, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object.assign@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + object-keys "^1.0.10" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" diff --git a/package.json b/package.json index 7098847..4a9035f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "babel": "^6.23.0", "babel-cli": "^6.23.0", "babel-core": "^6.23.1", + "babel-eslint": "^7.1.1", "babel-istanbul": "^0.12.2", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-builtin-extend": "^1.1.2", diff --git a/src/index.js b/src/index.js index 96dee02..ceabaa7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,23 +1,34 @@ // dotenv allows us to declare environment variables in a .env file, \ // find out more here https://github.com/motdotla/dotenv -require("dotenv").config(); -const express = require("express"); -const bodyParser = require("body-parser"); -const mongoose = require("mongoose"); -const passport = require("passport"); +require('dotenv').config(); +import express from 'express'; +import bodyParser from 'body-parser'; +import mongoose from 'mongoose'; +import passport from 'passport'; + + +require('./services/passport'); mongoose.Promise = global.Promise; mongoose - .connect("mongodb://localhost/todo-list-app") - .then(() => console.log("[mongoose] Connected to MongoDB")) - .catch(() => console.log("[mongoose] Error connecting to MongoDB")); + .connect('mongodb://localhost/todo-list-app') + .then(() => console.log('[mongoose] Connected to MongoDB')) + .catch(() => console.log('[mongoose] Error connecting to MongoDB')); const app = express(); -const authenticationRoutes = require("./routes/AuthenticationRoutes"); +const authenticationRoutes = require('./routes/AuthenticationRoutes'); +console.log(authenticationRoutes); app.use(bodyParser.json()); -// app.use(authenticationRoutes); +app.use(authenticationRoutes); + +const authStrategy = passport.authenticate('authStrategy', { session: false }); + +app.get('/api/secert', authStrategy, (request, response) => { + response.send(`The current user is ${request.user.username}`); +}); + const port = process.env.PORT || 3001; app.listen(port, () => { console.log(`Listening on port:${port}`); diff --git a/src/models/UserModel.js b/src/models/UserModel.js index 47c5e39..f0818a7 100644 --- a/src/models/UserModel.js +++ b/src/models/UserModel.js @@ -1,6 +1,5 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const Schema = mongoose.Schema; -const bcrypt = require('bcrypt'); const userSchema = new Schema({ username: { diff --git a/src/routes/AuthenticationRoutes.js b/src/routes/AuthenticationRoutes.js index 611413a..c3aecc8 100644 --- a/src/routes/AuthenticationRoutes.js +++ b/src/routes/AuthenticationRoutes.js @@ -1,5 +1,48 @@ -const express = require('express'); +import express from 'express'; const router = express.Router(); -const jwt = require('jwt-simple'); -const User = require('../models/UserModel'); -const bcrypt = require('bcrypt'); +import jwt from 'jwt-simple'; +import User from '../models/UserModel'; +import bcrypt from 'bcrypt'; +import passport from 'passport'; +import '../services/passport'; + +const signinStrategy = passport.authenticate('signinStrategy', { session: false }); + +function tokenForUser(user) { + const timestamp = new Date().getTime(); + return jwt.encode({ userId: user.id, iat: timestamp }, process.env.SECRET); +} + +router.post('/api/signin', (request, response) => { + response.json({ message: 'You\'ve been authenticated!'}); +}); + +router.post('/api/signup', signinStrategy, (request, response, next) => { + const {username, password} = request.body; + + if (!username || !password) { + return response.status(422) + .json({error: 'You must enter a username and/or password'}); + } + + User.findOne({ username }).exec() + .then(existingUser => { + if (existingUser) { + return response.status(422).json({ error: 'Username is in use' }); + } + + bcrypt.hash(password, 10, (err, hashedPassword) => { + if (err) { + return next(err); + } + + const newUser = new User({ username, password: hashedPassword }); + + newUser.save() + .then(user => response.json({token: tokenForUser(user)})); + }); + }) + .catch(err => next(err)); +}); + +export default router; diff --git a/src/services/passport.js b/src/services/passport.js index 5680076..8b38ba2 100644 --- a/src/services/passport.js +++ b/src/services/passport.js @@ -1,5 +1,52 @@ -const bcrypt = require('bcrypt'); -const passport = require('passport'); -const User = require('../models/UserModel'); -const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); -const LocalStrategy = require('passport-local'); +import bcrypt from 'bcrypt'; +import passport from 'passport'; +import User from '../models/UserModel'; +import { + Strategy as JwtStrategy, + ExtractJwt +} from 'passport-jwt'; +import LocalStrategy from 'passport-local'; +require('dotenv').config(); + +const signinStrategy = new LocalStrategy((username, password, done) => { + User.findOne({ username }).exec() + .then(user => { + if (!user) { + return done(null, false); + } + + bcrypt.compare(password, user.password, (err, isMatch) => { + + if (err) { + return done(err, false); + } + + if (!isMatch) { + return done(null, false); + } + + return done(null, user); + }); + }) + .catch(err => done(err, false)); +}); + +const jwtOptions = { + secretOrKey: process.env.SECRET, + jwtFromRequest: ExtractJwt.fromHeader('authorization') +}; + +const authStrategy = new JwtStrategy(jwtOptions, (payload, done) => { + User.findById(payload.userId, (err, user) => { + if (err) { return done(err, false); } + + if (user) { + done(null, user); + } else { + done(null, false); + } + }); +}); + +passport.use('authStrategy', authStrategy); +passport.use('signinStrategy', signinStrategy); diff --git a/yarn.lock b/yarn.lock index bf94ef4..aa617de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -232,6 +232,16 @@ babel-core@^6.23.0, babel-core@^6.23.1: slash "^1.0.0" source-map "^0.5.0" +babel-eslint@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.1.1.tgz#8a6a884f085aa7060af69cfc77341c2f99370fb2" + dependencies: + babel-code-frame "^6.16.0" + babel-traverse "^6.15.0" + babel-types "^6.15.0" + babylon "^6.13.0" + lodash.pickby "^4.6.0" + babel-generator@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5" @@ -822,7 +832,7 @@ babel-template@^6.22.0, babel-template@^6.23.0, babel-template@^6.3.0: babylon "^6.11.0" lodash "^4.2.0" -babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1: +babel-traverse@^6.15.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1: version "6.23.1" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48" dependencies: @@ -836,7 +846,7 @@ babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1: invariant "^2.2.0" lodash "^4.2.0" -babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0: +babel-types@^6.15.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf" dependencies: @@ -849,7 +859,7 @@ babel@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel/-/babel-6.23.0.tgz#d0d1e7d803e974765beea3232d4e153c0efb90f4" -babylon@^6.11.0, babylon@^6.15.0: +babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: version "6.16.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" @@ -2303,6 +2313,10 @@ lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" +lodash.pickby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" + lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"