Avoids easy cheating on vidoe views
This commit is contained in:
parent
e3bb78a213
commit
b5c0e95544
5 changed files with 65 additions and 12 deletions
|
@ -22,6 +22,7 @@ import {
|
||||||
import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
|
import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
|
||||||
import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
|
import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
|
||||||
import { JobQueue } from '../../../lib/job-queue'
|
import { JobQueue } from '../../../lib/job-queue'
|
||||||
|
import { Redis } from '../../../lib/redis'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware,
|
asyncMiddleware,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -352,7 +353,16 @@ function getVideo (req: express.Request, res: express.Response) {
|
||||||
async function viewVideo (req: express.Request, res: express.Response) {
|
async function viewVideo (req: express.Request, res: express.Response) {
|
||||||
const videoInstance = res.locals.video
|
const videoInstance = res.locals.video
|
||||||
|
|
||||||
|
const ip = req.ip
|
||||||
|
const exists = await Redis.Instance.isViewExists(ip, videoInstance.uuid)
|
||||||
|
if (exists) {
|
||||||
|
logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid)
|
||||||
|
return res.status(204).end()
|
||||||
|
}
|
||||||
|
|
||||||
await videoInstance.increment('views')
|
await videoInstance.increment('views')
|
||||||
|
await Redis.Instance.setView(ip, videoInstance.uuid)
|
||||||
|
|
||||||
const serverAccount = await getServerActor()
|
const serverAccount = await getServerActor()
|
||||||
|
|
||||||
if (videoInstance.isOwned()) {
|
if (videoInstance.isOwned()) {
|
||||||
|
|
|
@ -231,6 +231,8 @@ const CONSTRAINTS_FIELDS = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
|
||||||
|
|
||||||
const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
|
const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
|
||||||
LIKE: 'like',
|
LIKE: 'like',
|
||||||
DISLIKE: 'dislike'
|
DISLIKE: 'dislike'
|
||||||
|
@ -400,6 +402,7 @@ if (isTestInstance() === true) {
|
||||||
ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
|
ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
|
||||||
CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
|
CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
|
||||||
SCHEDULER_INTERVAL = 10000
|
SCHEDULER_INTERVAL = 10000
|
||||||
|
VIDEO_VIEW_LIFETIME = 1000 // 1 second
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWebserverConfig()
|
updateWebserverConfig()
|
||||||
|
@ -442,7 +445,8 @@ export {
|
||||||
USER_PASSWORD_RESET_LIFETIME,
|
USER_PASSWORD_RESET_LIFETIME,
|
||||||
IMAGE_MIMETYPE_EXT,
|
IMAGE_MIMETYPE_EXT,
|
||||||
SCHEDULER_INTERVAL,
|
SCHEDULER_INTERVAL,
|
||||||
JOB_COMPLETED_LIFETIME
|
JOB_COMPLETED_LIFETIME,
|
||||||
|
VIDEO_VIEW_LIFETIME
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createClient, RedisClient } from 'redis'
|
import { createClient, RedisClient } from 'redis'
|
||||||
import { logger } from '../helpers/logger'
|
import { logger } from '../helpers/logger'
|
||||||
import { generateRandomString } from '../helpers/utils'
|
import { generateRandomString } from '../helpers/utils'
|
||||||
import { CONFIG, USER_PASSWORD_RESET_LIFETIME } from '../initializers'
|
import { CONFIG, USER_PASSWORD_RESET_LIFETIME, VIDEO_VIEW_LIFETIME } from '../initializers'
|
||||||
|
|
||||||
class Redis {
|
class Redis {
|
||||||
|
|
||||||
|
@ -46,6 +46,14 @@ class Redis {
|
||||||
return this.getValue(this.generateResetPasswordKey(userId))
|
return this.getValue(this.generateResetPasswordKey(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setView (ip: string, videoUUID: string) {
|
||||||
|
return this.setValue(this.buildViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME)
|
||||||
|
}
|
||||||
|
|
||||||
|
async isViewExists (ip: string, videoUUID: string) {
|
||||||
|
return this.exists(this.buildViewKey(ip, videoUUID))
|
||||||
|
}
|
||||||
|
|
||||||
private getValue (key: string) {
|
private getValue (key: string) {
|
||||||
return new Promise<string>((res, rej) => {
|
return new Promise<string>((res, rej) => {
|
||||||
this.client.get(this.prefix + key, (err, value) => {
|
this.client.get(this.prefix + key, (err, value) => {
|
||||||
|
@ -68,10 +76,24 @@ class Redis {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private exists (key: string) {
|
||||||
|
return new Promise<boolean>((res, rej) => {
|
||||||
|
this.client.exists(this.prefix + key, (err, existsNumber) => {
|
||||||
|
if (err) return rej(err)
|
||||||
|
|
||||||
|
return res(existsNumber === 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private generateResetPasswordKey (userId: number) {
|
private generateResetPasswordKey (userId: number) {
|
||||||
return 'reset-password-' + userId
|
return 'reset-password-' + userId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildViewKey (ip: string, videoUUID: string) {
|
||||||
|
return videoUUID + '-' + ip
|
||||||
|
}
|
||||||
|
|
||||||
static get Instance () {
|
static get Instance () {
|
||||||
return this.instance || (this.instance = new this())
|
return this.instance || (this.instance = new this())
|
||||||
}
|
}
|
||||||
|
|
|
@ -421,15 +421,22 @@ describe('Test multiple servers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should view multiple videos on owned servers', async function () {
|
it('Should view multiple videos on owned servers', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(15000)
|
||||||
|
|
||||||
const tasks: Promise<any>[] = []
|
const tasks: Promise<any>[] = []
|
||||||
tasks.push(viewVideo(servers[2].url, localVideosServer3[0]))
|
await viewVideo(servers[2].url, localVideosServer3[0])
|
||||||
tasks.push(viewVideo(servers[2].url, localVideosServer3[0]))
|
await viewVideo(servers[2].url, localVideosServer3[0])
|
||||||
tasks.push(viewVideo(servers[2].url, localVideosServer3[0]))
|
await viewVideo(servers[2].url, localVideosServer3[0])
|
||||||
tasks.push(viewVideo(servers[2].url, localVideosServer3[1]))
|
await viewVideo(servers[2].url, localVideosServer3[1])
|
||||||
|
|
||||||
await Promise.all(tasks)
|
await Promise.all(tasks)
|
||||||
|
await wait(1500)
|
||||||
|
|
||||||
|
await viewVideo(servers[2].url, localVideosServer3[0])
|
||||||
|
|
||||||
|
await wait(1500)
|
||||||
|
|
||||||
|
await viewVideo(servers[2].url, localVideosServer3[0])
|
||||||
|
|
||||||
await wait(5000)
|
await wait(5000)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
checkVideoFilesWereRemoved, completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences,
|
checkVideoFilesWereRemoved, completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences,
|
||||||
getVideoPrivacies, getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer,
|
getVideoPrivacies, getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer,
|
||||||
searchVideo, searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testImage, updateVideo, uploadVideo,
|
searchVideo, searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testImage, updateVideo, uploadVideo,
|
||||||
viewVideo
|
viewVideo, wait
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
@ -149,8 +149,7 @@ describe('Test a single server', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should get and seed the uploaded video', async function () {
|
it('Should get and seed the uploaded video', async function () {
|
||||||
// Yes, this could be long
|
this.timeout(5000)
|
||||||
this.timeout(60000)
|
|
||||||
|
|
||||||
const res = await getVideosList(server.url)
|
const res = await getVideosList(server.url)
|
||||||
|
|
||||||
|
@ -163,8 +162,7 @@ describe('Test a single server', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should get the video by UUID', async function () {
|
it('Should get the video by UUID', async function () {
|
||||||
// Yes, this could be long
|
this.timeout(5000)
|
||||||
this.timeout(60000)
|
|
||||||
|
|
||||||
const res = await getVideo(server.url, videoUUID)
|
const res = await getVideo(server.url, videoUUID)
|
||||||
|
|
||||||
|
@ -173,10 +171,22 @@ describe('Test a single server', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have the views updated', async function () {
|
it('Should have the views updated', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
await viewVideo(server.url, videoId)
|
await viewVideo(server.url, videoId)
|
||||||
await viewVideo(server.url, videoId)
|
await viewVideo(server.url, videoId)
|
||||||
await viewVideo(server.url, videoId)
|
await viewVideo(server.url, videoId)
|
||||||
|
|
||||||
|
await wait(1500)
|
||||||
|
|
||||||
|
await viewVideo(server.url, videoId)
|
||||||
|
await viewVideo(server.url, videoId)
|
||||||
|
|
||||||
|
await wait(1500)
|
||||||
|
|
||||||
|
await viewVideo(server.url, videoId)
|
||||||
|
await viewVideo(server.url, videoId)
|
||||||
|
|
||||||
const res = await getVideo(server.url, videoId)
|
const res = await getVideo(server.url, videoId)
|
||||||
|
|
||||||
const video = res.body
|
const video = res.body
|
||||||
|
|
Loading…
Reference in a new issue