Skip to content

Commit 57f17c6

Browse files
committed
Initial commit
1 parent d8c3b44 commit 57f17c6

File tree

19 files changed

+11959
-1
lines changed

19 files changed

+11959
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea/
2+
backend/node_modules
3+
frontend/node_modules

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,30 @@
1-
# react-node-twitter-login
1+
# react-node-twitter-login
2+
3+
Demo application that shows how implement Twitter login with React on frontend and Node.js/Express on backend that is implementing REST API.
4+
5+
# About
6+
7+
This application was created as material that is described in the blog post.
8+
For creating React app we have used [crate-react-app](https://github.com/facebookincubator/create-react-app).
9+
10+
# What you need to install
11+
12+
* [Node.js](https://nodejs.org/en/)
13+
* [crate-react-app](https://github.com/facebookincubator/create-react-app)
14+
* [Gulp](http://gulpjs.com/)
15+
* [MongoDB](https://www.mongodb.com/)
16+
17+
# How To Start Application?
18+
19+
* Start MongoDB - our application expects that there is `twitter-demo` database in MongoDB
20+
* Go to [frontend]() folder
21+
* `npm install`
22+
* `ng serve`
23+
* Go to [backend]() folder
24+
* `npm install`
25+
* `gulp develop`
26+
27+
# License
28+
29+
react-node-twitter-login is released under [MIT License](https://opensource.org/licenses/MIT).
30+

backend/gulpfile.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
var gulp = require('gulp'),
4+
eslint = require('gulp-eslint'),
5+
nodemon = require('gulp-nodemon');
6+
7+
8+
//test the quality of the code with ESLint
9+
gulp.task('lint', function () {
10+
return gulp.src(['**/*.js', '!node_modules/**'])
11+
.pipe(eslint())
12+
.pipe(eslint.formatEach())
13+
.pipe(eslint.failAfterError());
14+
});
15+
16+
gulp.task('develop', function () {
17+
nodemon({
18+
exec: 'node --inspect',
19+
script: 'server.js',
20+
ext: 'html js',
21+
tasks: ['lint'],
22+
env: {'NODE_ENV': 'development'}
23+
});
24+
});

backend/mongoose.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
var mongoose = require('mongoose'),
4+
Schema = mongoose.Schema;
5+
6+
module.exports = function () {
7+
8+
var db = mongoose.connect('mongodb://localhost:27017/twitter-demo');
9+
10+
var UserSchema = new Schema({
11+
email: {
12+
type: String, required: true,
13+
trim: true, unique: true,
14+
match: /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/
15+
},
16+
twitterProvider: {
17+
type: {
18+
id: String,
19+
token: String
20+
},
21+
select: false
22+
}
23+
});
24+
25+
UserSchema.set('toJSON', {getters: true, virtuals: true});
26+
27+
UserSchema.statics.upsertTwitterUser = function(token, tokenSecret, profile, cb) {
28+
var that = this;
29+
return this.findOne({
30+
'twitterProvider.id': profile.id
31+
}, function(err, user) {
32+
// no user was found, lets create a new one
33+
if (!user) {
34+
var newUser = new that({
35+
email: profile.emails[0].value,
36+
twitterProvider: {
37+
id: profile.id,
38+
token: token,
39+
tokenSecret: tokenSecret
40+
}
41+
});
42+
43+
newUser.save(function(error, savedUser) {
44+
if (error) {
45+
console.log(error);
46+
}
47+
return cb(error, savedUser);
48+
});
49+
} else {
50+
return cb(err, user);
51+
}
52+
});
53+
};
54+
55+
mongoose.model('User', UserSchema);
56+
57+
return db;
58+
};

backend/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "twitter-demo",
3+
"version": "0.0.1",
4+
"description": "Demo application that shows how to enable Twitter login on Express/Node.js backend when creating REST API",
5+
"keywords": [
6+
"node.js",
7+
"express.js",
8+
"twitter-login"
9+
],
10+
"author": "Ivan Vasiljevic",
11+
"license": "MIT",
12+
"devDependencies": {
13+
"gulp": "~3.9.1",
14+
"gulp-eslint": "^2.0.0",
15+
"gulp-nodemon": "^2.2.1"
16+
},
17+
"dependencies": {
18+
"body-parser": "^1.17.1",
19+
"cors": "^2.8.1",
20+
"express": "^4.15.2",
21+
"express-jwt": "^5.1.0",
22+
"jsonwebtoken": "^7.3.0",
23+
"mongoose": "^4.9.0",
24+
"passport": "^0.3.2",
25+
"passport-twitter-token": "^1.3.0",
26+
"request": "^2.81.0"
27+
}
28+
}

backend/passport.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
var passport = require('passport'),
4+
TwitterTokenStrategy = require('passport-twitter-token'),
5+
User = require('mongoose').model('User');
6+
7+
module.exports = function () {
8+
9+
passport.use(new TwitterTokenStrategy({
10+
consumerKey: 'KEY',
11+
consumerSecret: 'SECRET',
12+
includeEmail: true
13+
},
14+
function (token, tokenSecret, profile, done) {
15+
User.upsertTwitterUser(token, tokenSecret, profile, function(err, user) {
16+
return done(err, user);
17+
});
18+
}));
19+
20+
};

backend/server.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
'use strict';
2+
3+
//mongoose file must be loaded before all other files in order to provide
4+
// models to other modules
5+
var mongoose = require('./mongoose'),
6+
passport = require('passport'),
7+
express = require('express'),
8+
jwt = require('jsonwebtoken'),
9+
expressJwt = require('express-jwt'),
10+
router = express.Router(),
11+
cors = require('cors'),
12+
bodyParser = require('body-parser'),
13+
request = require('request');
14+
15+
mongoose();
16+
17+
var User = require('mongoose').model('User');
18+
var passportConfig = require('./passport');
19+
20+
//setup configuration for facebook login
21+
passportConfig();
22+
23+
var app = express();
24+
25+
// enable cors
26+
var corsOption = {
27+
origin: true,
28+
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
29+
credentials: true,
30+
exposedHeaders: ['x-auth-token']
31+
};
32+
app.use(cors(corsOption));
33+
34+
//rest API requirements
35+
app.use(bodyParser.urlencoded({
36+
extended: true
37+
}));
38+
app.use(bodyParser.json());
39+
40+
router.route('/health-check').get(function(req, res) {
41+
res.status(200);
42+
res.send('Hello World');
43+
});
44+
45+
var createToken = function(auth) {
46+
return jwt.sign({
47+
id: auth.id
48+
}, 'my-secret',
49+
{
50+
expiresIn: 60 * 120
51+
});
52+
};
53+
54+
var generateToken = function (req, res, next) {
55+
req.token = createToken(req.auth);
56+
return next();
57+
};
58+
59+
var sendToken = function (req, res) {
60+
res.setHeader('x-auth-token', req.token);
61+
return res.status(200).send(JSON.stringify(req.user));
62+
};
63+
64+
router.route('/auth/twitter/reverse')
65+
.post(function(req, res) {
66+
request.post({
67+
url: 'https://api.twitter.com/oauth/request_token',
68+
oauth: {
69+
oauth_callback: "http%3A%2F%2Flocalhost%3A3000%2Ftwitter-callback",
70+
consumer_key: 'KEY',
71+
consumer_secret: 'SECRET'
72+
}
73+
}, function (err, r, body) {
74+
if (err) {
75+
return res.send(500, { message: e.message });
76+
}
77+
78+
79+
var jsonStr = '{ "' + body.replace(/&/g, '", "').replace(/=/g, '": "') + '"}';
80+
res.send(JSON.parse(jsonStr));
81+
});
82+
});
83+
84+
router.route('/auth/twitter')
85+
.post((req, res, next) => {
86+
request.post({
87+
url: `https://api.twitter.com/oauth/access_token?oauth_verifier`,
88+
oauth: {
89+
consumer_key: 'KEY',
90+
consumer_secret: 'SECRET',
91+
token: req.query.oauth_token
92+
},
93+
form: { oauth_verifier: req.query.oauth_verifier }
94+
}, function (err, r, body) {
95+
if (err) {
96+
return res.send(500, { message: e.message });
97+
}
98+
99+
console.log(body);
100+
const bodyString = '{ "' + body.replace(/&/g, '", "').replace(/=/g, '": "') + '"}';
101+
const parsedBody = JSON.parse(bodyString);
102+
103+
req.body['oauth_token'] = parsedBody.oauth_token;
104+
req.body['oauth_token_secret'] = parsedBody.oauth_token_secret;
105+
req.body['user_id'] = parsedBody.user_id;
106+
107+
next();
108+
});
109+
}, passport.authenticate('twitter-token', {session: false}), function(req, res, next) {
110+
if (!req.user) {
111+
return res.send(401, 'User Not Authenticated');
112+
}
113+
114+
// prepare token for API
115+
req.auth = {
116+
id: req.user.id
117+
};
118+
119+
return next();
120+
}, generateToken, sendToken);
121+
122+
//token handling middleware
123+
var authenticate = expressJwt({
124+
secret: 'my-secret',
125+
requestProperty: 'auth',
126+
getToken: function(req) {
127+
if (req.headers['x-auth-token']) {
128+
return req.headers['x-auth-token'];
129+
}
130+
return null;
131+
}
132+
});
133+
134+
var getCurrentUser = function(req, res, next) {
135+
User.findById(req.auth.id, function(err, user) {
136+
if (err) {
137+
next(err);
138+
} else {
139+
req.user = user;
140+
next();
141+
}
142+
});
143+
};
144+
145+
var getOne = function (req, res) {
146+
var user = req.user.toObject();
147+
148+
delete user['twitterProvider'];
149+
delete user['__v'];
150+
151+
res.json(user);
152+
};
153+
154+
router.route('/auth/me')
155+
.get(authenticate, getCurrentUser, getOne);
156+
157+
app.use('/api/v1', router);
158+
159+
app.listen(4000);
160+
module.exports = app;
161+
162+
console.log('Server running at http://localhost:4000/');

docker/docker-compose.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
version: '2'
2+
services:
3+
twitter-demo-db:
4+
image: mongo:3.4.2
5+
ports:
6+
- "27017:27017"
7+

frontend/.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*

0 commit comments

Comments
 (0)