aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-02-23 16:39:51 +0100
committerChocobozzz <me@florianbigard.com>2018-02-23 16:44:37 +0100
commitb5c0e95544cec5a33cee3df41c1607d2a0cd5403 (patch)
tree38a5db1faed107f7b75583c32125152812594821
parente3bb78a2134a5e5755b6dbd8987894572ca31269 (diff)
downloadPeerTube-b5c0e95544cec5a33cee3df41c1607d2a0cd5403.tar.gz
PeerTube-b5c0e95544cec5a33cee3df41c1607d2a0cd5403.tar.zst
PeerTube-b5c0e95544cec5a33cee3df41c1607d2a0cd5403.zip
Avoids easy cheating on vidoe views
-rw-r--r--server/controllers/api/videos/index.ts10
-rw-r--r--server/initializers/constants.ts6
-rw-r--r--server/lib/redis.ts24
-rw-r--r--server/tests/api/videos/multiple-servers.ts17
-rw-r--r--server/tests/api/videos/single-server.ts20
5 files changed, 65 insertions, 12 deletions
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index c9334676e..c3d3acd26 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -22,6 +22,7 @@ import {
22import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' 22import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
23import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' 23import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
24import { JobQueue } from '../../../lib/job-queue' 24import { JobQueue } from '../../../lib/job-queue'
25import { Redis } from '../../../lib/redis'
25import { 26import {
26 asyncMiddleware, 27 asyncMiddleware,
27 authenticate, 28 authenticate,
@@ -352,7 +353,16 @@ function getVideo (req: express.Request, res: express.Response) {
352async function viewVideo (req: express.Request, res: express.Response) { 353async function viewVideo (req: express.Request, res: express.Response) {
353 const videoInstance = res.locals.video 354 const videoInstance = res.locals.video
354 355
356 const ip = req.ip
357 const exists = await Redis.Instance.isViewExists(ip, videoInstance.uuid)
358 if (exists) {
359 logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid)
360 return res.status(204).end()
361 }
362
355 await videoInstance.increment('views') 363 await videoInstance.increment('views')
364 await Redis.Instance.setView(ip, videoInstance.uuid)
365
356 const serverAccount = await getServerActor() 366 const serverAccount = await getServerActor()
357 367
358 if (videoInstance.isOwned()) { 368 if (videoInstance.isOwned()) {
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 328a3e70a..2dc73770d 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -231,6 +231,8 @@ const CONSTRAINTS_FIELDS = {
231 } 231 }
232} 232}
233 233
234let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
235
234const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { 236const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
235 LIKE: 'like', 237 LIKE: 'like',
236 DISLIKE: 'dislike' 238 DISLIKE: 'dislike'
@@ -400,6 +402,7 @@ if (isTestInstance() === true) {
400 ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds 402 ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
401 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB 403 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
402 SCHEDULER_INTERVAL = 10000 404 SCHEDULER_INTERVAL = 10000
405 VIDEO_VIEW_LIFETIME = 1000 // 1 second
403} 406}
404 407
405updateWebserverConfig() 408updateWebserverConfig()
@@ -442,7 +445,8 @@ export {
442 USER_PASSWORD_RESET_LIFETIME, 445 USER_PASSWORD_RESET_LIFETIME,
443 IMAGE_MIMETYPE_EXT, 446 IMAGE_MIMETYPE_EXT,
444 SCHEDULER_INTERVAL, 447 SCHEDULER_INTERVAL,
445 JOB_COMPLETED_LIFETIME 448 JOB_COMPLETED_LIFETIME,
449 VIDEO_VIEW_LIFETIME
446} 450}
447 451
448// --------------------------------------------------------------------------- 452// ---------------------------------------------------------------------------
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index 4240cc162..b284cab8f 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -1,7 +1,7 @@
1import { createClient, RedisClient } from 'redis' 1import { createClient, RedisClient } from 'redis'
2import { logger } from '../helpers/logger' 2import { logger } from '../helpers/logger'
3import { generateRandomString } from '../helpers/utils' 3import { generateRandomString } from '../helpers/utils'
4import { CONFIG, USER_PASSWORD_RESET_LIFETIME } from '../initializers' 4import { CONFIG, USER_PASSWORD_RESET_LIFETIME, VIDEO_VIEW_LIFETIME } from '../initializers'
5 5
6class Redis { 6class Redis {
7 7
@@ -46,6 +46,14 @@ class Redis {
46 return this.getValue(this.generateResetPasswordKey(userId)) 46 return this.getValue(this.generateResetPasswordKey(userId))
47 } 47 }
48 48
49 setView (ip: string, videoUUID: string) {
50 return this.setValue(this.buildViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME)
51 }
52
53 async isViewExists (ip: string, videoUUID: string) {
54 return this.exists(this.buildViewKey(ip, videoUUID))
55 }
56
49 private getValue (key: string) { 57 private getValue (key: string) {
50 return new Promise<string>((res, rej) => { 58 return new Promise<string>((res, rej) => {
51 this.client.get(this.prefix + key, (err, value) => { 59 this.client.get(this.prefix + key, (err, value) => {
@@ -68,10 +76,24 @@ class Redis {
68 }) 76 })
69 } 77 }
70 78
79 private exists (key: string) {
80 return new Promise<boolean>((res, rej) => {
81 this.client.exists(this.prefix + key, (err, existsNumber) => {
82 if (err) return rej(err)
83
84 return res(existsNumber === 1)
85 })
86 })
87 }
88
71 private generateResetPasswordKey (userId: number) { 89 private generateResetPasswordKey (userId: number) {
72 return 'reset-password-' + userId 90 return 'reset-password-' + userId
73 } 91 }
74 92
93 private buildViewKey (ip: string, videoUUID: string) {
94 return videoUUID + '-' + ip
95 }
96
75 static get Instance () { 97 static get Instance () {
76 return this.instance || (this.instance = new this()) 98 return this.instance || (this.instance = new this())
77 } 99 }
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index c82ac1348..27c4c30b8 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -421,15 +421,22 @@ describe('Test multiple servers', function () {
421 }) 421 })
422 422
423 it('Should view multiple videos on owned servers', async function () { 423 it('Should view multiple videos on owned servers', async function () {
424 this.timeout(10000) 424 this.timeout(15000)
425 425
426 const tasks: Promise<any>[] = [] 426 const tasks: Promise<any>[] = []
427 tasks.push(viewVideo(servers[2].url, localVideosServer3[0])) 427 await viewVideo(servers[2].url, localVideosServer3[0])
428 tasks.push(viewVideo(servers[2].url, localVideosServer3[0])) 428 await viewVideo(servers[2].url, localVideosServer3[0])
429 tasks.push(viewVideo(servers[2].url, localVideosServer3[0])) 429 await viewVideo(servers[2].url, localVideosServer3[0])
430 tasks.push(viewVideo(servers[2].url, localVideosServer3[1])) 430 await viewVideo(servers[2].url, localVideosServer3[1])
431 431
432 await Promise.all(tasks) 432 await Promise.all(tasks)
433 await wait(1500)
434
435 await viewVideo(servers[2].url, localVideosServer3[0])
436
437 await wait(1500)
438
439 await viewVideo(servers[2].url, localVideosServer3[0])
433 440
434 await wait(5000) 441 await wait(5000)
435 442
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index 83b6a0e9a..cf2721838 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -8,7 +8,7 @@ import {
8 checkVideoFilesWereRemoved, completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, 8 checkVideoFilesWereRemoved, completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences,
9 getVideoPrivacies, getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer, 9 getVideoPrivacies, getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer,
10 searchVideo, searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testImage, updateVideo, uploadVideo, 10 searchVideo, searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testImage, updateVideo, uploadVideo,
11 viewVideo 11 viewVideo, wait
12} from '../../utils' 12} from '../../utils'
13 13
14const expect = chai.expect 14const expect = chai.expect
@@ -149,8 +149,7 @@ describe('Test a single server', function () {
149 }) 149 })
150 150
151 it('Should get and seed the uploaded video', async function () { 151 it('Should get and seed the uploaded video', async function () {
152 // Yes, this could be long 152 this.timeout(5000)
153 this.timeout(60000)
154 153
155 const res = await getVideosList(server.url) 154 const res = await getVideosList(server.url)
156 155
@@ -163,8 +162,7 @@ describe('Test a single server', function () {
163 }) 162 })
164 163
165 it('Should get the video by UUID', async function () { 164 it('Should get the video by UUID', async function () {
166 // Yes, this could be long 165 this.timeout(5000)
167 this.timeout(60000)
168 166
169 const res = await getVideo(server.url, videoUUID) 167 const res = await getVideo(server.url, videoUUID)
170 168
@@ -173,10 +171,22 @@ describe('Test a single server', function () {
173 }) 171 })
174 172
175 it('Should have the views updated', async function () { 173 it('Should have the views updated', async function () {
174 this.timeout(10000)
175
176 await viewVideo(server.url, videoId) 176 await viewVideo(server.url, videoId)
177 await viewVideo(server.url, videoId) 177 await viewVideo(server.url, videoId)
178 await viewVideo(server.url, videoId) 178 await viewVideo(server.url, videoId)
179 179
180 await wait(1500)
181
182 await viewVideo(server.url, videoId)
183 await viewVideo(server.url, videoId)
184
185 await wait(1500)
186
187 await viewVideo(server.url, videoId)
188 await viewVideo(server.url, videoId)
189
180 const res = await getVideo(server.url, videoId) 190 const res = await getVideo(server.url, videoId)
181 191
182 const video = res.body 192 const video = res.body