├── .gitignore ├── client ├── .buckconfig ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── index.android.js ├── index.ios.js ├── package.json └── views │ ├── HomeView.js │ ├── LoginView.js │ ├── ProtectedView.js │ └── RegisterView.js └── server ├── app.js ├── bin └── www ├── models └── user.js ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── index.js └── users.js └── views ├── error.jade ├── index.jade └── layout.jade /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | client/android/ 3 | client/ios/ 4 | -------------------------------------------------------------------------------- /client/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | node_modules/** 3 | dist/** 4 | *.spec.js 5 | src/index.html 6 | app.js 7 | -------------------------------------------------------------------------------- /client/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser" : "babel-eslint", 3 | "extends" : "airbnb", 4 | "env" : { 5 | "browser" : true 6 | }, 7 | "globals" : { 8 | "__DEV__" : false, 9 | "__PROD__" : false, 10 | "__DEBUG__" : false, 11 | "__DEBUG_NEW_WINDOW__" : false, 12 | "__BASENAME__" : false, 13 | "React": true, 14 | "ReactDOM": true 15 | }, 16 | "rules": { 17 | "semi" : [2, "never"], 18 | "react-in-jsx-scope": 0, 19 | "comma-dangle": [1, "never"], 20 | "func-names": 0, 21 | "indent": [1, 2], 22 | "jsx-quotes": [2, "prefer-single"], 23 | "key-spacing": 0, 24 | "new-cap": 0, 25 | "no-else-return": 0, 26 | "no-multi-spaces": 0, 27 | "no-shadow": 1, 28 | "no-unused-vars": 1, 29 | "no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 30 | "one-var": 0, 31 | "padded-blocks": 1, 32 | "quotes": [2, "single"], 33 | "space-before-function-paren": [0, "never"], 34 | "spaced-comment": 1, 35 | "object-curly-spacing": [1, "always"], 36 | "quote-props": [1, 'as-needed', { 'keywords': false, 'unnecessary': true, 'numbers': false }], 37 | "react/jsx-no-bind": 1 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*.web.js 5 | .*/*.android.js 6 | 7 | # Some modules have their own node_modules with overlap 8 | .*/node_modules/node-haste/.* 9 | 10 | # Ugh 11 | .*/node_modules/babel.* 12 | .*/node_modules/babylon.* 13 | .*/node_modules/invariant.* 14 | 15 | # Ignore react and fbjs where there are overlaps, but don't ignore 16 | # anything that react-native relies on 17 | .*/node_modules/fbjs/lib/Map.js 18 | .*/node_modules/fbjs/lib/fetch.js 19 | .*/node_modules/fbjs/lib/ExecutionEnvironment.js 20 | .*/node_modules/fbjs/lib/ErrorUtils.js 21 | 22 | # Flow has a built-in definition for the 'react' module which we prefer to use 23 | # over the currently-untyped source 24 | .*/node_modules/react/react.js 25 | .*/node_modules/react/lib/React.js 26 | .*/node_modules/react/lib/ReactDOM.js 27 | 28 | .*/__mocks__/.* 29 | .*/__tests__/.* 30 | 31 | .*/commoner/test/source/widget/share.js 32 | 33 | # Ignore commoner tests 34 | .*/node_modules/commoner/test/.* 35 | 36 | # See https://github.com/facebook/flow/issues/442 37 | .*/react-tools/node_modules/commoner/lib/reader.js 38 | 39 | # Ignore jest 40 | .*/node_modules/jest-cli/.* 41 | 42 | # Ignore Website 43 | .*/website/.* 44 | 45 | # Ignore generators 46 | .*/local-cli/generator.* 47 | 48 | # Ignore BUCK generated folders 49 | .*\.buckd/ 50 | 51 | .*/node_modules/is-my-json-valid/test/.*\.json 52 | .*/node_modules/iconv-lite/encodings/tables/.*\.json 53 | .*/node_modules/y18n/test/.*\.json 54 | .*/node_modules/spdx-license-ids/spdx-license-ids.json 55 | .*/node_modules/spdx-exceptions/index.json 56 | .*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json 57 | .*/node_modules/resolve/lib/core.json 58 | .*/node_modules/jsonparse/samplejson/.*\.json 59 | .*/node_modules/json5/test/.*\.json 60 | .*/node_modules/ua-parser-js/test/.*\.json 61 | .*/node_modules/builtin-modules/builtin-modules.json 62 | .*/node_modules/binary-extensions/binary-extensions.json 63 | .*/node_modules/url-regex/tlds.json 64 | .*/node_modules/joi/.*\.json 65 | .*/node_modules/isemail/.*\.json 66 | .*/node_modules/tr46/.*\.json 67 | 68 | 69 | [include] 70 | 71 | [libs] 72 | node_modules/react-native/Libraries/react-native/react-native-interface.js 73 | node_modules/react-native/flow 74 | flow/ 75 | 76 | [options] 77 | module.system=haste 78 | 79 | esproposal.class_static_fields=enable 80 | esproposal.class_instance_fields=enable 81 | 82 | munge_underscores=true 83 | 84 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 85 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub' 86 | 87 | suppress_type=$FlowIssue 88 | suppress_type=$FlowFixMe 89 | suppress_type=$FixMe 90 | 91 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-3]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 92 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-3]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 93 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 94 | 95 | [version] 96 | 0.23.0 97 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | .idea 28 | .gradle 29 | local.properties 30 | 31 | # node.js 32 | # 33 | node_modules/ 34 | npm-debug.log 35 | 36 | # BUCK 37 | buck-out/ 38 | \.buckd/ 39 | android/app/libs 40 | android/keystores/debug.keystore 41 | -------------------------------------------------------------------------------- /client/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /client/index.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import { 8 | AppRegistry, 9 | StyleSheet, 10 | Text, 11 | View 12 | } from 'react-native'; 13 | 14 | class client extends Component { 15 | render() { 16 | return ( 17 | 18 | 19 | Welcome to React Native! 20 | 21 | 22 | To get started, edit index.android.js 23 | 24 | 25 | Shake or press menu button for dev menu 26 | 27 | 28 | ); 29 | } 30 | } 31 | 32 | const styles = StyleSheet.create({ 33 | container: { 34 | flex: 1, 35 | justifyContent: 'center', 36 | alignItems: 'center', 37 | backgroundColor: '#F5FCFF', 38 | }, 39 | welcome: { 40 | fontSize: 20, 41 | textAlign: 'center', 42 | margin: 10, 43 | }, 44 | instructions: { 45 | textAlign: 'center', 46 | color: '#333333', 47 | marginBottom: 5, 48 | }, 49 | }); 50 | 51 | AppRegistry.registerComponent('client', () => client); 52 | -------------------------------------------------------------------------------- /client/index.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { 9 | AppRegistry, 10 | StyleSheet, 11 | NavigatorIOS, 12 | Text, 13 | View 14 | } from 'react-native'; 15 | 16 | var HomeView = require('./views/HomeView'); 17 | 18 | class Client extends Component { 19 | render() { 20 | return ( 21 | 27 | ) 28 | } 29 | } 30 | 31 | var styles = StyleSheet.create({ 32 | container: { 33 | flex: 1 34 | } 35 | }); 36 | 37 | AppRegistry.registerComponent('client', () => Client); 38 | 39 | module.exports = Client; 40 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start" 7 | }, 8 | "dependencies": { 9 | "babel-eslint": "^6.0.4", 10 | "eslint": "^2.10.1", 11 | "eslint-config-airbnb": "^9.0.1", 12 | "eslint-plugin-import": "^1.8.0", 13 | "eslint-plugin-jsx-a11y": "^1.2.0", 14 | "eslint-plugin-react": "^5.1.1", 15 | "react": "^0.14.8", 16 | "react-native": "^0.25.1", 17 | "tcomb-form-native": "^0.4.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/views/HomeView.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | AsyncStorage, 3 | Component, 4 | StyleSheet, 5 | TouchableHighlight, 6 | Text, 7 | View 8 | } from 'react-native' 9 | 10 | const LoginView = require('./LoginView') 11 | const RegisterView = require('./RegisterView') 12 | const ProtectedView = require('./ProtectedView') 13 | 14 | class HomeView extends Component { 15 | _handleRegisterView = () => { 16 | this.props.navigator.push({ 17 | title: 'Register', 18 | component: RegisterView, 19 | backButtonTitle: 'Back' 20 | }) 21 | } 22 | _handleLoginView = () => { 23 | this.props.navigator.push({ 24 | title: 'Login', 25 | component: LoginView, 26 | backButtonTitle: 'Back' 27 | }) 28 | } 29 | _handleProtectedView = () => { 30 | this.props.navigator.push({ 31 | title: 'Protected Content', 32 | component: ProtectedView, 33 | backButtonTitle: 'Back' 34 | }) 35 | } 36 | _handleLogOut = () => { 37 | AsyncStorage.removeItem('jwt'); 38 | alert('You have been logged out.'); 39 | } 40 | render() { 41 | return ( 42 | 43 | 44 | 45 | Register 46 | 47 | 48 | 49 | 50 | Log In 51 | 52 | 53 | 54 | 55 | Log Out 56 | 57 | 58 | 59 | 60 | Protected Content 61 | 62 | 63 | 64 | ) 65 | } 66 | } 67 | 68 | const styles = StyleSheet.create({ 69 | container: { 70 | marginTop: 40, 71 | padding: 80, 72 | flex: 1, 73 | flexDirection: 'column' 74 | }, 75 | button: { 76 | borderRadius: 4, 77 | padding: 20, 78 | textAlign: 'center', 79 | marginBottom: 20, 80 | color: '#fff' 81 | }, 82 | greenButton: { 83 | backgroundColor: '#4CD964' 84 | }, 85 | blueButton: { 86 | backgroundColor: '#34AADC' 87 | }, 88 | redButton: { 89 | backgroundColor: '#FF3B30', 90 | color: '#fff' 91 | }, 92 | greyButton: { 93 | backgroundColor: '#777', 94 | color: '#fff' 95 | } 96 | }) 97 | 98 | module.exports = HomeView 99 | -------------------------------------------------------------------------------- /client/views/LoginView.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | AsyncStorage, 3 | Component, 4 | ScrollView, 5 | StyleSheet, 6 | TouchableHighlight, 7 | Text 8 | } from 'react-native' 9 | 10 | const t = require('tcomb-form-native') 11 | 12 | const Form = t.form.Form 13 | 14 | const User = t.struct({ 15 | email: t.String, 16 | password: t.String 17 | }) 18 | 19 | const options = { 20 | fields: { 21 | email: { 22 | autoCapitalize: 'none', 23 | autoCorrect: false 24 | }, 25 | password: { 26 | autoCapitalize: 'none', 27 | password: true, 28 | autoCorrect: false 29 | } 30 | } 31 | } 32 | 33 | class LoginView extends Component { 34 | 35 | constructor(props) { 36 | super(props) 37 | this.state = { 38 | value: { 39 | email: '', 40 | password: '' 41 | } 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.setState = { 47 | value: { 48 | email: '', 49 | password: null 50 | } 51 | } 52 | } 53 | 54 | _onChange = (value) => { 55 | this.setState({ 56 | value 57 | }) 58 | } 59 | _handleAdd = () => { 60 | const value = this.refs.form.getValue(); 61 | // If the form is valid... 62 | if (value) { 63 | const data = { 64 | username: value.email, 65 | password: value.password 66 | } 67 | // Serialize and post the data 68 | const json = JSON.stringify(data) 69 | fetch('http://localhost:3000/users/login', { 70 | method: 'POST', 71 | headers: { 72 | 'Content-Type': 'application/json', 73 | Accept: 'application/json' 74 | }, 75 | body: json 76 | }) 77 | .then((response) => response.json()) 78 | .then((res) => { 79 | if (res.error) { 80 | alert(res.error) 81 | } else { 82 | AsyncStorage.setItem('jwt', res.token) 83 | alert(`Success! You may now access protected content.`) 84 | // Redirect to home screen 85 | this.props.navigator.pop() 86 | } 87 | }) 88 | .catch(() => { 89 | alert('There was an error logging in.'); 90 | }) 91 | .done() 92 | } else { 93 | // Form validation error 94 | alert('Please fix the errors listed and try again.') 95 | } 96 | } 97 | 98 | render() { 99 | return ( 100 | 101 |
108 | 109 | Log In 110 | 111 | 112 | ) 113 | } 114 | }; 115 | 116 | var styles = StyleSheet.create({ 117 | container: { 118 | padding: 20, 119 | flex: 1, 120 | flexDirection: 'column' 121 | }, 122 | button: { 123 | borderRadius: 4, 124 | padding: 20, 125 | textAlign: 'center', 126 | marginBottom: 20, 127 | color: '#fff' 128 | }, 129 | greenButton: { 130 | backgroundColor: '#4CD964' 131 | }, 132 | centering: { 133 | alignItems: 'center', 134 | justifyContent: 'center' 135 | } 136 | }) 137 | 138 | module.exports = LoginView 139 | -------------------------------------------------------------------------------- /client/views/ProtectedView.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | ActivityIndicatorIOS, 3 | AsyncStorage, 4 | Component, 5 | StyleSheet, 6 | Text, 7 | View 8 | } from 'react-native' 9 | 10 | class ProtectedView extends Component { 11 | constructor(props) { 12 | super(props) 13 | this.state = { 14 | showIndicator: false, 15 | secret: null 16 | } 17 | } 18 | 19 | componentWillMount() { 20 | this.setState({ 21 | showIndicator: true 22 | }, this._fetchData) 23 | } 24 | 25 | _fetchData = () => { 26 | AsyncStorage.getItem('jwt', (err, token) => { 27 | fetch('http://localhost:3000/protected', { 28 | headers: { 29 | Accept: 'application/json', 30 | Authorization: `JWT ${token}` 31 | } 32 | }) 33 | .then((response) => response.json()) 34 | .then((json) => { 35 | this.setState({ 36 | secret: json.secret, 37 | showIndicator: false 38 | }) 39 | }) 40 | .catch(() => { 41 | alert('There was an error fetching the secret info.') 42 | }) 43 | .done() 44 | }) 45 | } 46 | 47 | _renderIndicator = () => ( 48 | 53 | ) 54 | 55 | _renderSecret = () => ( 56 | 57 | The secret code is {this.state.secret} 58 | 59 | ) 60 | 61 | render() { 62 | return ( 63 | 64 | { 65 | this.state.showIndicator 66 | ? this._renderIndicator() 67 | : 68 | 69 | {this.state.secret ? this._renderSecret() : You are not authorized!} 70 | 71 | } 72 | 73 | ) 74 | } 75 | } 76 | 77 | var styles = StyleSheet.create({ 78 | container: { 79 | marginTop: 40, 80 | padding: 80, 81 | flex: 1, 82 | flexDirection: 'column' 83 | }, 84 | button: { 85 | borderRadius: 4, 86 | padding: 20, 87 | textAlign: 'center', 88 | marginBottom: 20, 89 | color: '#fff' 90 | }, 91 | greenButton: { 92 | backgroundColor: '#4CD964' 93 | }, 94 | blueButton: { 95 | backgroundColor: '#34AADC', 96 | }, 97 | centering: { 98 | flex: 1, 99 | paddingTop: 28, 100 | justifyContent: 'center', 101 | alignItems: 'center' 102 | } 103 | }) 104 | 105 | module.exports = ProtectedView 106 | -------------------------------------------------------------------------------- /client/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | ScrollView, 4 | StyleSheet, 5 | TouchableHighlight, 6 | Text 7 | } from 'react-native' 8 | 9 | const t = require('tcomb-form-native'); 10 | 11 | const Form = t.form.Form 12 | 13 | const newUser = t.struct({ 14 | email: t.String, 15 | password: t.String 16 | }) 17 | 18 | const options = { 19 | fields: { 20 | email: { 21 | autoCapitalize: 'none', 22 | autoCorrect: false 23 | }, 24 | password: { 25 | autoCapitalize: 'none', 26 | password: true, 27 | autoCorrect: false 28 | } 29 | } 30 | } 31 | 32 | class RegisterView extends Component { 33 | 34 | constructor(props) { 35 | super(props) 36 | this.state = { 37 | value: { 38 | email: '', 39 | password: '' 40 | } 41 | } 42 | } 43 | 44 | componentWillUnmount() { 45 | this.setState = { 46 | value: { 47 | email: '', 48 | password: null 49 | } 50 | } 51 | } 52 | 53 | _onChange = (value) => { 54 | this.setState({ 55 | value 56 | }) 57 | } 58 | 59 | _handleAdd = () => { 60 | const value = this.refs.form.getValue(); 61 | // If the form is valid... 62 | if (value) { 63 | const data = { 64 | email: value.email, 65 | password: value.password, 66 | } 67 | // Serialize and post the data 68 | const json = JSON.stringify(data); 69 | fetch('http://localhost:3000/users/register', { 70 | method: 'POST', 71 | headers: { 72 | 'Content-Type': 'application/json', 73 | Accept: 'application/json' 74 | }, 75 | body: json 76 | }) 77 | .then((response) => response.json()) 78 | .then(() => { 79 | alert('Success! You may now log in.'); 80 | // Redirect to home screen 81 | this.props.navigator.pop(); 82 | }) 83 | .catch((error) => { 84 | alert('There was an error creating your account.'); 85 | }) 86 | .done() 87 | } else { 88 | // Form validation error 89 | alert('Please fix the errors listed and try again.') 90 | } 91 | } 92 | 93 | render() { 94 | return ( 95 | 96 | 103 | 104 | Create account 105 | 106 | 107 | ) 108 | } 109 | } 110 | 111 | const styles = StyleSheet.create({ 112 | container: { 113 | padding: 20, 114 | flex: 1, 115 | flexDirection: 'column' 116 | }, 117 | button: { 118 | borderRadius: 4, 119 | padding: 20, 120 | textAlign: 'center', 121 | marginBottom: 20, 122 | color: '#fff' 123 | }, 124 | greenButton: { 125 | backgroundColor: '#4CD964' 126 | }, 127 | centering: { 128 | alignItems: 'center', 129 | justifyContent: 'center' 130 | } 131 | }) 132 | 133 | module.exports = RegisterView 134 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var mongoose = require('mongoose'); 8 | var passport = require('passport'); 9 | var LocalStrategy = require('passport-local').Strategy; 10 | var JwtStrategy = require('passport-jwt').Strategy; 11 | var ExtractJwt = require('passport-jwt').ExtractJwt; 12 | var routes = require('./routes/index'); 13 | var users = require('./routes/users'); 14 | var User = require('./models/user'); 15 | 16 | var app = express(); 17 | 18 | // JWT configration 19 | var options = {} 20 | options.jwtFromRequest = ExtractJwt.fromAuthHeader(); 21 | options.secretOrKey = '7x0jhxt"9(thpX6' 22 | 23 | app.use(passport.initialize()); 24 | 25 | // view engine setup 26 | app.set('views', path.join(__dirname, 'views')); 27 | app.set('view engine', 'jade'); 28 | 29 | // uncomment after placing your favicon in /public 30 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 31 | app.use(logger('dev')); 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ 34 | extended: false 35 | })); 36 | app.use(cookieParser()); 37 | app.use(express.static(path.join(__dirname, 'public'))); 38 | 39 | // Configure Passport to use local strategy for initial authentication. 40 | passport.use('local', new LocalStrategy(User.authenticate())); 41 | 42 | // Configure Passport to use JWT strategy to look up Users. 43 | passport.use('jwt', new JwtStrategy(options, function(jwt_payload, done) { 44 | User.findOne({ 45 | _id: jwt_payload.id 46 | }, function(err, user) { 47 | if (err) { 48 | return done(err, false); 49 | } 50 | if (user) { 51 | done(null, user); 52 | } else { 53 | done(null, false); 54 | } 55 | }) 56 | })) 57 | 58 | app.use('/', routes); 59 | app.use('/users', users); 60 | 61 | // connect to database 62 | var db = process.env.MONGO_URL || 'localhost/react-native-jwt' 63 | mongoose.connect(db); 64 | mongoose.connection.on('error', function() { 65 | console.info('Error: Could not connect to MongoDB. Did you forget to run `mongod`?') 66 | }); 67 | 68 | // catch 404 and forward to error handler 69 | app.use(function(req, res, next) { 70 | var err = new Error('Not Found'); 71 | err.status = 404; 72 | next(err); 73 | }); 74 | 75 | // error handlers 76 | 77 | // development error handler 78 | // will print stacktrace 79 | // app.use(function(err, req, res, next) { 80 | // res.status(err.status || 500); 81 | // res.render('error', { 82 | // message: err.message, 83 | // error: err 84 | // }); 85 | // }); 86 | // } 87 | 88 | // production error handler 89 | // no stacktraces leaked to user 90 | app.use(function(err, req, res, next) { 91 | res.status(err.status || 500); 92 | res.render('error', { 93 | message: err.message, 94 | error: {} 95 | }); 96 | }); 97 | 98 | 99 | module.exports = app; 100 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('passport-local-express4:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var passportLocalMongoose = require('passport-local-mongoose'); 4 | 5 | var User = new Schema({}); 6 | 7 | // See passport-local-mongoose docs for schema customization options 8 | // https://github.com/saintedlama/passport-local-mongoose#options 9 | User.plugin(passportLocalMongoose, { 10 | usernameField: 'email', 11 | usernameUnique: true 12 | }); 13 | 14 | module.exports = mongoose.model('User', User); 15 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-jwt", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.13.2", 10 | "cookie-parser": "~1.3.5", 11 | "debug": "~2.2.0", 12 | "express": "~4.13.1", 13 | "jade": "~1.11.0", 14 | "jsonwebtoken": "^6.2.0", 15 | "mongoose": "^4.4.16", 16 | "morgan": "~1.6.1", 17 | "passport": "^0.3.2", 18 | "passport-jwt": "^2.0.0", 19 | "passport-local": "^1.0.0", 20 | "passport-local-mongoose": "^4.0.0", 21 | "serve-favicon": "~2.3.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var passport = require('passport'); 4 | 5 | /* GET home page. */ 6 | router.get('/protected', function (req, res, next) { 7 | console.log(req.headers); 8 | passport.authenticate('jwt', function (err, user, info) { 9 | if (err) { 10 | return next(err); 11 | } 12 | if (!user) { 13 | return res.status(401).json({ error: 'Invalid credentials.' }); 14 | } 15 | if (user) { 16 | return res 17 | .status(200) 18 | .json({ secret: '123' }); 19 | } 20 | })(req, res, next); 21 | }); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var passport = require('passport'); 4 | var jwt = require('jsonwebtoken'); 5 | var User = require('../models/user'); 6 | 7 | var secret = '7x0jhxt"9(thpX6'; 8 | 9 | router.post('/login', function (req, res, next) { 10 | passport.authenticate('local', function (err, user, info) { 11 | if (err) { 12 | return next(err); 13 | } 14 | if (!user) { 15 | return res.status(401).json({ error: 'Invalid credentials.' }); 16 | } 17 | if (user) { 18 | var token = jwt.sign({ id: user._id, email: user.email }, secret); 19 | return res 20 | .status(200) 21 | .json({ token }); 22 | } 23 | })(req, res, next); 24 | }); 25 | 26 | router.post('/register', function (req, res) { 27 | User.register(new User({ email: req.body.email }), req.body.password, function (err, user) { 28 | if (err) { 29 | return res.status(400).send({ error: 'Email address in use.' }) 30 | } 31 | res.status(200).send(user); 32 | }); 33 | }); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /server/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /server/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /server/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | --------------------------------------------------------------------------------