Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 490b595

Browse files
committed
Prevent brute force login attack
1 parent 23f4c3d commit 490b595

File tree

16 files changed

+191
-24
lines changed

16 files changed

+191
-24
lines changed

client/src/app/core/auth/auth.service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,12 @@ export class AuthService {
6666
},
6767

6868
error => {
69-
let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`
70-
errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
69+
let errorMessage = error.message
70+
71+
if (error.status === 403) {
72+
errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`
73+
errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
74+
}
7175

7276
// We put a bigger timeout
7377
// This is an important message

client/src/app/shared/rest/rest-extractor.service.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,33 @@ export class RestExtractor {
4242
console.error('An error occurred:', errorMessage)
4343
} else if (err.status !== undefined) {
4444
// A server-side error occurred.
45-
if (err.error) {
46-
if (err.error.errors) {
47-
const errors = err.error.errors
48-
const errorsArray: string[] = []
49-
50-
Object.keys(errors).forEach(key => {
51-
errorsArray.push(errors[key].msg)
52-
})
53-
54-
errorMessage = errorsArray.join('. ')
55-
} else if (err.error.error) {
56-
errorMessage = err.error.error
57-
}
45+
if (err.error && err.error.errors) {
46+
const errors = err.error.errors
47+
const errorsArray: string[] = []
48+
49+
Object.keys(errors).forEach(key => {
50+
errorsArray.push(errors[key].msg)
51+
})
52+
53+
errorMessage = errorsArray.join('. ')
54+
} else if (err.error && err.error.error) {
55+
errorMessage = err.error.error
5856
} else if (err.status === 413) {
5957
errorMessage = 'Request is too large for the server. Please contact you administrator if you want to increase the limit size.'
58+
} else if (err.status === 429) {
59+
const secondsLeft = err.headers.get('retry-after')
60+
if (secondsLeft) {
61+
const minutesLeft = Math.floor(parseInt(secondsLeft, 10) / 60)
62+
errorMessage = 'Too many attempts, please try again after ' + minutesLeft + ' minutes.'
63+
} else {
64+
errorMessage = 'Too many attempts, please try again later.'
65+
}
66+
} else if (err.status === 500) {
67+
errorMessage = 'Server error. Please retry later.'
6068
}
6169

6270
errorMessage = errorMessage ? errorMessage : 'Unknown error.'
63-
console.error(`Backend returned code ${err.status}, body was: ${errorMessage}`)
71+
console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
6472
} else {
6573
errorMessage = err
6674
}

client/src/app/signup/signup.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class SignupComponent extends FormReactive implements OnInit {
101101
const lines = [
102102
SignupComponent.getApproximateTime(fullHdSeconds) + ' of full HD videos',
103103
SignupComponent.getApproximateTime(hdSeconds) + ' of HD videos',
104-
SignupComponent.getApproximateTime(normalSeconds) + ' of normal quality videos'
104+
SignupComponent.getApproximateTime(normalSeconds) + ' of average quality videos'
105105
]
106106

107107
this.quotaHelpIndication = lines.join('<br />')

config/default.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ webserver:
66
hostname: 'localhost'
77
port: 9000
88

9+
# Proxies to trust to get real client IP
10+
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
11+
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
12+
trust_proxy:
13+
- 'loopback'
14+
915
# Your database name will be "peertube"+database.suffix
1016
database:
1117
hostname: 'localhost'

config/production.yaml.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ webserver:
77
hostname: 'example.com'
88
port: 443
99

10+
# Proxies to trust to get real client IP
11+
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
12+
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
13+
trust_proxy:
14+
- 'loopback'
15+
1016
# Your database name will be "peertube"+database.suffix
1117
database:
1218
hostname: 'localhost'

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"create-torrent": "^3.24.5",
6666
"express": "^4.12.4",
6767
"express-oauth-server": "^2.0.0",
68+
"express-rate-limit": "^2.11.0",
6869
"express-validator": "^5.0.0",
6970
"fluent-ffmpeg": "^2.1.0",
7071
"js-yaml": "^3.5.4",
@@ -104,6 +105,7 @@
104105
"@types/chai": "^4.0.4",
105106
"@types/config": "^0.0.34",
106107
"@types/express": "^4.0.35",
108+
"@types/express-rate-limit": "^2.9.3",
107109
"@types/kue": "^0.11.8",
108110
"@types/lodash": "^4.14.64",
109111
"@types/magnet-uri": "^5.1.1",

server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ if (errorMessage !== null) {
4848
throw new Error(errorMessage)
4949
}
5050

51+
// Trust our proxy (IP forwarding...)
52+
app.set('trust proxy', CONFIG.TRUST_PROXY)
53+
5154
// ----------- Database -----------
5255

5356
// Initialize database and models
@@ -81,6 +84,7 @@ if (isTestInstance()) {
8184
) {
8285
return (cors({
8386
origin: 'http://localhost:3000',
87+
exposedHeaders: 'Retry-After',
8488
credentials: true
8589
}))(req, res, next)
8690
}

server/controllers/api/users.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import * as express from 'express'
22
import 'multer'
33
import { extname, join } from 'path'
44
import * as uuidv4 from 'uuid/v4'
5+
import * as RateLimit from 'express-rate-limit'
56
import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
67
import { retryTransactionWrapper } from '../../helpers/database-utils'
78
import { processImage } from '../../helpers/image-utils'
89
import { logger } from '../../helpers/logger'
910
import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
10-
import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
11+
import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
1112
import { updateActorAvatarInstance } from '../../lib/activitypub'
1213
import { sendUpdateActor } from '../../lib/activitypub/send'
1314
import { Emailer } from '../../lib/emailer'
@@ -43,6 +44,11 @@ import { OAuthTokenModel } from '../../models/oauth/oauth-token'
4344
import { VideoModel } from '../../models/video/video'
4445

4546
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
47+
const loginRateLimiter = new RateLimit({
48+
windowMs: RATES_LIMIT.LOGIN.WINDOW_MS,
49+
max: RATES_LIMIT.LOGIN.MAX,
50+
delayMs: 0
51+
})
4652

4753
const usersRouter = express.Router()
4854

@@ -136,7 +142,11 @@ usersRouter.post('/:id/reset-password',
136142
asyncMiddleware(resetUserPassword)
137143
)
138144

139-
usersRouter.post('/token', token, success)
145+
usersRouter.post('/token',
146+
loginRateLimiter,
147+
token,
148+
success
149+
)
140150
// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
141151

142152
// ---------------------------------------------------------------------------

server/controllers/api/videos/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ function getVideo (req: express.Request, res: express.Response) {
353353
async function viewVideo (req: express.Request, res: express.Response) {
354354
const videoInstance = res.locals.video
355355

356-
const ip = req.headers['x-real-ip'] as string || req.ip
356+
const ip = req.ip
357357
const exists = await Redis.Instance.isViewExists(ip, videoInstance.uuid)
358358
if (exists) {
359359
logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid)

server/initializers/checker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function checkConfig () {
2020
function checkMissedConfig () {
2121
const required = [ 'listen.port',
2222
'webserver.https', 'webserver.hostname', 'webserver.port',
23+
'trust_proxy',
2324
'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password',
2425
'redis.hostname', 'redis.port', 'redis.auth',
2526
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',

0 commit comments

Comments
 (0)