Skip to content

Commit 04cda59

Browse files
committed
implements GoogleOAuth via Passport and Cookie based auth
1 parent b59ccca commit 04cda59

File tree

12 files changed

+253
-9
lines changed

12 files changed

+253
-9
lines changed

.env.dev

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ NODE_ENV="development"
33
JWT_SECRET="thisismysupersecrettokenjustkidding"
44
DATABASE_URL="mongodb://mongo:27017/donut-development"
55
SENDGRID_API_KEY='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'
6+
SENDGRID_FROM_EMAIL_ADDRESS='services@codeuino.com'
67
SOCKET_PORT=8810
78
clientbaseurl="http://localhost:3000"
89
SOCKET_PORT=8810
@@ -13,3 +14,8 @@ REDIS_DB=0
1314
REDIS_ACTIVITY_DB=1
1415
GITHUB_OAUTH_APP_CLIENTID="a3e08516c35fe7e83f43"
1516
GITHUB_OAUTH_APP_CLIENTSECRET="8393d95c3bfeeb0943045f447a9d055cb660bce0"
17+
GOOGLE_OAUTH_CLIENT_ID=""
18+
GOOGLE_OAUTH_CLIENT_SECRET=""
19+
20+
# For Ex: http://localhost:5000/user/auth/google/callback
21+
GOOGLE_OAUTH_CALLBACK=""

app.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,21 @@ const analyticsRouter = require('./app/routes/analytics')
3030
const wikisRouter = require('./app/routes/wikis')
3131
const activityRouter = require('./app/routes/activity')
3232
const ticketRouter = require('./app/routes/ticket')
33-
33+
const passport = require('passport');
3434
const app = express()
3535
const server = require('http').Server(app)
36+
const clientbaseurl = process.env.clientbaseurl || 'http://localhost:3000'
3637

37-
app.use(cors())
38+
app.use(cors({origin: clientbaseurl, credentials: true}))
3839

3940
app.use(bodyParser.json({ limit: '200mb' }))
4041
app.use(cookieParser())
4142
app.use(bodyParser.urlencoded(fileConstants.fileParameters))
4243

44+
// PassportJS for OAuth
45+
app.use(passport.initialize());
46+
require('./app/middleware/passport');
47+
4348
const memoryStorage = multer.memoryStorage()
4449
app.use(multer({ storage: memoryStorage }).single('file'))
4550

app/controllers/auth.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ module.exports = {
99
try {
1010
const user = await User.findByCredentials(email, password)
1111
const token = await user.generateAuthToken()
12-
res.send({ user: user, token: token })
12+
res.cookie("token", token, { httpOnly: true }).send({ user: user })
1313
} catch (error) {
1414
res.status(HttpStatus.BAD_REQUEST).json({ error: error.message })
1515
}
1616
},
1717
logout: (req, res, next) => {
1818
activityHelper.addActivityToDb(req, res)
19+
res.clearCookie("token");
1920
res.status(HttpStatus.OK).json({ success: 'ok' })
2021
},
2122
logoutAll: (req, res, next) => {

app/controllers/email.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const sendgridMail = require('@sendgrid/mail')
22
const ejs = require('ejs')
33
const path = require('path')
44
const sendGridApi = process.env.SENDGRID_API_KEY || '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'
5-
5+
const SENDGRID_FROM_EMAIL_ADDRESS = process.env.SENDGRID_FROM_EMAIL_ADDRESS || 'services@codeuino.com';
66
sendgridMail.setApiKey(sendGridApi)
77

88
module.exports = {
@@ -14,7 +14,7 @@ module.exports = {
1414
} else {
1515
const message = {
1616
to: req.body.email,
17-
from: 'services@codeuino.com',
17+
from: SENDGRID_FROM_EMAIL_ADDRESS,
1818
subject: `Welcome to Donut ${req.body.name.firstName}`,
1919
html: data
2020
}

app/controllers/user.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const TAGS = require('../utils/notificationTags')
1212
const settingHelper = require('../utils/settingHelpers')
1313
const Activity = require('../models/Activity')
1414
const activityHelper = require('../utils/activity-helper')
15+
const formatUser = require('../utils/format-user')
1516

1617
const notification = {
1718
heading: '',
@@ -47,7 +48,7 @@ module.exports = {
4748
await activity.save()
4849
// hide password
4950
user.password = undefined
50-
return res.status(HttpStatus.CREATED).json({ user: user, token: token })
51+
res.cookie("token", token, { httpOnly: true }).status(HttpStatus.CREATED).send({ user: user })
5152
} catch (error) {
5253
return res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error })
5354
}
@@ -90,6 +91,22 @@ module.exports = {
9091
HANDLER.handleError(res, error)
9192
}
9293
},
94+
// Load User
95+
loadUser: async (req, res, next) => {
96+
try {
97+
const id = req.params.id || req.user._id
98+
const user = await User.findById({ _id: id })
99+
if (!user) {
100+
return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exist!' })
101+
}
102+
// hide password and tokens
103+
user.password = undefined
104+
user.tokens = []
105+
return res.status(HttpStatus.OK).json({ user: user })
106+
} catch (error) {
107+
HANDLER.handleError(res, error)
108+
}
109+
},
93110

94111
// USER PROFILE UPDATE
95112
userProfileUpdate: async (req, res, next) => {
@@ -479,5 +496,47 @@ module.exports = {
479496
} catch (error) {
480497
HANDLER.handleError(error)
481498
}
499+
},
500+
// Find User and Create if doesn't exist
501+
findOrCreateForOAuth: async(profile, provider) => {
502+
let user;
503+
if(provider==='google'){
504+
user = formatUser.formatUser(profile, provider)
505+
}
506+
try {
507+
const existingUser = await User.findOne({
508+
email: user.email
509+
})
510+
if (existingUser) {
511+
const token = await existingUser.generateAuthToken()
512+
return {token, user: existingUser};
513+
} else {
514+
const newUser = new User(user)
515+
const isRegisteredUserExists = await User.findOne({ firstRegister: true })
516+
const Org = await Organization.find({}).lean().exec()
517+
// for the first user who will be setting up the platform for their community
518+
if (!isRegisteredUserExists) {
519+
newUser.isAdmin = true
520+
newUser.firstRegister = true
521+
}
522+
if (Org.length > 0) {
523+
newUser.orgId = Org[0]._id
524+
}
525+
const data = await newUser.save()
526+
if (!isRegisteredUserExists) {
527+
settingHelper.addAdmin(data._id)
528+
}
529+
const token = await newUser.generateAuthToken()
530+
531+
// create redis db for activity for the user
532+
const activity = new Activity({ userId: data._id })
533+
await activity.save()
534+
// hide password
535+
data.password = undefined
536+
return {token: token, user: data}
537+
}
538+
} catch(e) {
539+
throw e;
540+
}
482541
}
483542
}

app/middleware/auth.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ const HttpStatus = require('http-status-codes')
44

55
const auth = async (req, res, next) => {
66
try {
7-
const token = req.header('Authorization').replace('Bearer ', '')
7+
const token = req.cookies.token || '';
8+
if(!token) {
9+
throw Error("unauthorized access")
10+
}
811
const decoded = jwt.verify(token, 'process.env.JWT_SECRET')
912
const user = await User.findOne({
1013
_id: decoded._id,

app/middleware/passport.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const passport = require('passport');
2+
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
3+
const user = require('../controllers/user');
4+
const GOOGLE_OAUTH_CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID || '';
5+
const GOOGLE_OAUTH_CLIENT_SECRET = process.env.GOOGLE_OAUTH_CLIENT_SECRET || '';
6+
const GOOGLE_OAUTH_CALLBACK = process.env.GOOGLE_OAUTH_CALLBACK || '';
7+
8+
passport.use(new GoogleStrategy({
9+
clientID: GOOGLE_OAUTH_CLIENT_ID,
10+
clientSecret: GOOGLE_OAUTH_CLIENT_SECRET,
11+
callbackURL: GOOGLE_OAUTH_CALLBACK,
12+
proxy: true
13+
}, (accessToken, refreshToken, profile, next) => {
14+
15+
const provider="google";
16+
user.findOrCreateForOAuth(profile, provider)
17+
.then(details => {
18+
if (details) {
19+
next(null, details);
20+
} else {
21+
next(null, false);
22+
}
23+
}).catch(err=>console.log(err))
24+
}));

app/models/User.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ const UserSchema = new mongoose.Schema({
5656
password: {
5757
type: String,
5858
trim: true,
59-
required: true,
6059
minlength: 6,
6160
validate (password) {
6261
if (!validator.isLength(password, { min: 6 })) {
@@ -67,6 +66,11 @@ const UserSchema = new mongoose.Schema({
6766
}
6867
}
6968
},
69+
provider: {
70+
type: String,
71+
enum: ['google', 'github', 'email'],
72+
default: 'email'
73+
},
7074
socialMedia: {
7175
youtube: {
7276
type: String

app/routes/user.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ const router = express.Router()
33
const userController = require('../controllers/user')
44
const auth = require('../middleware/auth')
55
const isUnderMaintenance = require('../middleware/maintenance')
6+
const passport = require('passport');
67
// const email = require('../middleware/email')
7-
8+
const afterAuthRedirect = (process.env.clientbaseurl + '/login') || 'http://localhost:3000/login'
89
// create a user
910
router.post(
1011
'/',
@@ -13,6 +14,14 @@ router.post(
1314
userController.createUser
1415
)
1516

17+
// load user (endpoint used to call when someone opens app)
18+
router.get(
19+
'/load_user',
20+
isUnderMaintenance,
21+
auth,
22+
userController.loadUser
23+
)
24+
1625
// get user profile
1726
router.get(
1827
'/:id',
@@ -138,4 +147,24 @@ router.patch(
138147
userController.deactivateAccount
139148
)
140149

150+
router.get(
151+
'/auth/google',
152+
isUnderMaintenance,
153+
passport.authenticate('google', { scope: ['profile','email'], session: false })
154+
)
155+
156+
router.get(
157+
'/auth/google/callback',
158+
isUnderMaintenance,
159+
(req, res, next) => {
160+
passport.authenticate('google', (err, details) => {
161+
if(details.token===undefined || !details.token) {
162+
res.redirect(afterAuthRedirect)
163+
}else {
164+
res.cookie("token", details.token, { httpOnly: true }).redirect(afterAuthRedirect);
165+
}
166+
})(req, res, next)
167+
}
168+
)
169+
141170
module.exports = router

app/utils/format-user.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
// accepts either google or github respose profile and format for insertion in db
3+
formatUser: (profile, provider) => {
4+
let formattedUser;
5+
6+
if(provider==='google') {
7+
formattedUser = {
8+
name: {
9+
firstName: profile._json.given_name,
10+
lastName: profile._json.family_name,
11+
},
12+
email: profile._json.email,
13+
provider: provider,
14+
isActivated: profile._json.email_verified
15+
}
16+
}
17+
return formattedUser;
18+
},
19+
}

0 commit comments

Comments
 (0)