Skip to content

Commit 51faa6e

Browse files
committed
fix security flaws
1 parent 299942c commit 51faa6e

File tree

7 files changed

+251
-11
lines changed

7 files changed

+251
-11
lines changed

app.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ const socket = require('socket.io')
88
const multer = require('multer')
99
const bodyParser = require('body-parser')
1010
const cors = require('cors')
11+
const helmet = require('helmet')
12+
const hpp = require('hpp')
1113
var winston = require('./config/winston')
14+
const rateLimiter = require('./app/middleware/rateLimiter')
15+
const sanitizer = require('./app/middleware/sanitise')
1216
const fileConstants = require('./config/fileHandlingConstants')
1317

1418
const indexRouter = require('./app/routes/index')
@@ -31,6 +35,7 @@ const server = require('http').Server(app)
3135
app.use(cors())
3236

3337
app.use(bodyParser.json({ limit: '200mb' }))
38+
app.use(cookieParser())
3439
app.use(bodyParser.urlencoded(fileConstants.fileParameters))
3540

3641
const memoryStorage = multer.memoryStorage()
@@ -72,6 +77,23 @@ app.use((req, res, next) => {
7277
next()
7378
})
7479

80+
// TO PREVENT DOS ATTACK AND RATE LIMITER
81+
app.use(rateLimiter.customRateLimiter)
82+
83+
// TO PREVENT XSS ATTACK
84+
app.use(sanitizer.cleanBody)
85+
app.use(helmet())
86+
87+
// TO PREVENT CLICK JACKING
88+
app.use((req, res, next) => {
89+
res.append('X-Frame-Options', 'Deny')
90+
res.set('Content-Security-Policy', "frame-ancestors 'none';")
91+
next()
92+
})
93+
94+
// TO PREVENT THE QUERY PARAMETER POLLUTION
95+
app.use(hpp())
96+
7597
app.use('/notification', notificationRouter)
7698
app.use('/', indexRouter)
7799
app.use('/auth', authRouter)
@@ -104,7 +126,7 @@ app.use(function (err, req, res, next) {
104126

105127
// render the error page
106128
res.status(err.status || 500)
107-
res.render('error')
129+
res.render('error', { csrfToken: req.csrfToken() })
108130

109131
// Socket event error handler (On max event)
110132
req.io.on('error', function (err) {

app/controllers/auth.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ const activityHelper = require('../utils/activity-helper')
44

55
module.exports = {
66
authenticateUser: async (req, res, next) => {
7-
const email = req.body.email
8-
const password = req.body.password
7+
const email = escape(req.body.email)
8+
const password = escape(req.body.password)
99
try {
1010
const user = await User.findByCredentials(email, password)
1111
const token = await user.generateAuthToken()

app/controllers/user.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ module.exports = {
4545
// create redis db for activity for the user
4646
const activity = new Activity({ userId: data._id })
4747
await activity.save()
48-
48+
// hide password
49+
user.password = undefined
4950
return res.status(HttpStatus.CREATED).json({ user: user, token: token })
5051
} catch (error) {
5152
return res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error })
@@ -81,6 +82,9 @@ module.exports = {
8182
if (!user) {
8283
return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exist!' })
8384
}
85+
// hide password and tokens
86+
user.password = undefined
87+
user.tokens = []
8488
return res.status(HttpStatus.OK).json({ user })
8589
} catch (error) {
8690
HANDLER.handleError(res, error)
@@ -94,6 +98,7 @@ module.exports = {
9498
'phone',
9599
'info',
96100
'about',
101+
'socialMedia',
97102
'isDeactivated'
98103
]
99104
// added control as per org settings
@@ -118,6 +123,9 @@ module.exports = {
118123
user[update] = req.body[update]
119124
})
120125
await user.save()
126+
// hide password and tokens
127+
user.password = undefined
128+
user.tokens = []
121129
return res.status(HttpStatus.OK).json({ data: user })
122130
} catch (error) {
123131
return res.status(HttpStatus.BAD_REQUEST).json({ error })
@@ -308,6 +316,9 @@ module.exports = {
308316
.populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin'])
309317
.populate('blocked', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin'])
310318
.exec()
319+
// hide password and tokens
320+
userData.password = undefined
321+
userData.tokens = []
311322
return res.status(HttpStatus.OK).json({ user: userData })
312323
} catch (error) {
313324
HANDLER.handleError(res, error)
@@ -358,6 +369,9 @@ module.exports = {
358369
.populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin'])
359370
.populate('blocked', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin'])
360371
.exec()
372+
// hide password and tokens
373+
userData.password = undefined
374+
userData.tokens = []
361375
return res.status(HttpStatus.OK).json({ user: userData })
362376
} catch (error) {
363377
HANDLER.handleError(res, error)
@@ -404,6 +418,9 @@ module.exports = {
404418
if (unblockIndex !== -1) {
405419
user.blocked.splice(unblockIndex, 1)
406420
await user.save()
421+
// hide password and tokens
422+
user.password = undefined
423+
user.tokens = []
407424
return res.status(HttpStatus.OK).json({ user })
408425
}
409426
return res.status(HttpStatus.NOT_FOUND).json({ user })
@@ -441,6 +458,9 @@ module.exports = {
441458
}
442459
user.isRemoved = true
443460
await user.save()
461+
// hide password and tokens
462+
user.password = undefined
463+
user.tokens = []
444464
return res.status(HttpStatus.OK).json({ user })
445465
} catch (error) {
446466
HANDLER.handleError(res, error)
@@ -451,6 +471,9 @@ module.exports = {
451471
try {
452472
req.user.isActivated = !req.user.isActivated
453473
const user = await req.user.save()
474+
// hide password and tokens
475+
user.password = undefined
476+
user.tokens = []
454477
return res.status(HttpStatus.OK).json({ user })
455478
} catch (error) {
456479
HANDLER.handleError(error)

app/middleware/rateLimiter.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const redis = require('../../config/redis')
2+
const redisClient = redis.redisClient
3+
const moment = require('moment')
4+
const WINDOW_SIZE_IN_HOURS = 24
5+
const MAX_WINDOW_REQUEST_COUNT = 100
6+
const WINDOW_LOG_INTERVAL_IN_HOURS = 1
7+
8+
module.exports = {
9+
customRateLimiter: (req, res, next) => {
10+
try {
11+
// check if redis exists
12+
if (!redisClient) {
13+
throw new Error('RedisClient not found on the server')
14+
}
15+
// if exists check if request made earlier from same ip
16+
redisClient.get(req.ip, (err, reply) => {
17+
if (err) {
18+
console.log('Error in fetching data from redis', err)
19+
}
20+
const currentRequestTime = moment()
21+
// if no reply from redis then store the users request to the server in redis
22+
if (reply === null) {
23+
const newRecord = []
24+
const info = {
25+
requestTimeStamp: currentRequestTime.unix(),
26+
requestCount: 1
27+
}
28+
newRecord.unshift(info)
29+
// set to redis => ip => [{ requestTimeStamp, requestCount }]
30+
redisClient.set(req.ip, JSON.stringify(newRecord))
31+
next()
32+
}
33+
34+
// if record is found, parse it's value and calculate number of requests users has made within the last window
35+
const data = JSON.parse(reply)
36+
37+
const windowStartTimestamp = moment()
38+
.subtract(WINDOW_SIZE_IN_HOURS, 'hours')
39+
.unix()
40+
41+
const requestsWithinWindow = data.filter(entry => {
42+
return entry.requestTimeStamp > windowStartTimestamp
43+
})
44+
45+
const totalWindowRequestsCount = requestsWithinWindow.reduce((accumulator, entry) => {
46+
return accumulator + entry.requestCount
47+
}, 0)
48+
49+
// if number of requests made is greater than or equal to the desired maximum, return error
50+
if (totalWindowRequestsCount >= MAX_WINDOW_REQUEST_COUNT) {
51+
return res.status(429).json({
52+
error: `You have exceeded the ${MAX_WINDOW_REQUEST_COUNT} requests in ${WINDOW_SIZE_IN_HOURS} hrs limit!`
53+
})
54+
} else {
55+
// if number of requests made is less than allowed maximum, log new entry
56+
const lastRequestLog = data[data.length - 1]
57+
const potentialCurrentWindowIntervalStartTimeStamp = currentRequestTime
58+
.subtract(WINDOW_LOG_INTERVAL_IN_HOURS, 'hours')
59+
.unix()
60+
61+
// if interval has not passed since last request log, increment counter
62+
if (lastRequestLog.requestTimeStamp > potentialCurrentWindowIntervalStartTimeStamp) {
63+
lastRequestLog.requestCount++
64+
data[data.length - 1] = lastRequestLog
65+
} else {
66+
// if interval has passed, log new entry for current user and timestamp
67+
data.unshift({
68+
requestTimeStamp: currentRequestTime.unix(),
69+
requestCount: 1
70+
})
71+
}
72+
redisClient.set(req.ip, JSON.stringify(data))
73+
next()
74+
}
75+
})
76+
} catch (error) {
77+
console.log('Error in rateLimiter', error)
78+
next(error)
79+
}
80+
}
81+
}

app/middleware/sanitise.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const sanitize = require('mongo-sanitize')
2+
module.exports = {
3+
cleanBody: (req, res, next) => {
4+
req.body = sanitize(req.body)
5+
next()
6+
}
7+
}

0 commit comments

Comments
 (0)