From a056ca4813c82f490dcd31ac97a64d6bf76d3dcc Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 28 Oct 2020 15:24:40 +0100 Subject: [PATCH] Add max lives limit --- .../edit-custom-config.component.html | 10 ++++++ .../edit-custom-config.component.ts | 2 ++ .../video-go-live.component.ts | 13 +++++-- client/src/app/core/server/server.service.ts | 2 ++ .../instance-features-table.component.html | 7 ++++ .../instance-features-table.component.ts | 14 ++++++++ config/default.yaml | 8 +++++ server/controllers/api/config.ts | 4 +++ server/initializers/checker-before-init.ts | 2 +- server/initializers/config.ts | 3 ++ server/middlewares/validators/config.ts | 2 ++ .../validators/videos/video-live.ts | 35 ++++++++++++++++++- server/models/video/video.ts | 31 ++++++++++++++++ server/tests/api/check-params/config.ts | 2 ++ server/tests/api/server/config.ts | 6 ++++ shared/extra-utils/server/config.ts | 2 ++ shared/models/server/custom-config.model.ts | 3 ++ shared/models/server/server-config.model.ts | 2 ++ .../models/server/server-error-code.enum.ts | 4 ++- 19 files changed, 147 insertions(+), 5 deletions(-) diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index 2f3202e06..686f3601b 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html @@ -739,6 +739,16 @@ +
+ + +
+ +
+ + +
+
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 745238647..de1cf46b1 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -216,6 +216,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A enabled: null, maxDuration: null, + maxInstanceLives: null, + maxUserLives: null, allowReplay: null, transcoding: { diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts index 9868c37d2..870a70d3d 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts @@ -7,7 +7,7 @@ import { scrollToTop } from '@app/helpers' import { FormValidatorService } from '@app/shared/shared-forms' import { LiveVideoService, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { LoadingBarService } from '@ngx-loading-bar/core' -import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoPrivacy } from '@shared/models' +import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, ServerErrorCode, VideoPrivacy } from '@shared/models' import { VideoSend } from './video-send' @Component({ @@ -81,7 +81,16 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, CanCompon err => { this.firstStepError.emit() - this.notifier.error(err.message) + + let message = err.message + + if (err.body?.code === ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED) { + message = $localize`Cannot create live because this instance have too many created lives` + } else if (err.body?.code) { + message = $localize`Cannot create live because you created too many lives` + } + + this.notifier.error(message) } ) } diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index c19c3c12e..1abf87118 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -78,6 +78,8 @@ export class ServerService { enabled: false, allowReplay: true, maxDuration: null, + maxInstanceLives: -1, + maxUserLives: -1, transcoding: { enabled: false, enabledResolutions: [] diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.html b/client/src/app/shared/shared-instance/instance-features-table.component.html index 002695238..ce2557147 100644 --- a/client/src/app/shared/shared-instance/instance-features-table.component.html +++ b/client/src/app/shared/shared-instance/instance-features-table.component.html @@ -81,6 +81,13 @@ + + Max parallel lives + + {{ maxUserLives }} per user / {{ maxInstanceLives }} per instance + + + Import diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.ts b/client/src/app/shared/shared-instance/instance-features-table.component.ts index 76b595c20..0166157f9 100644 --- a/client/src/app/shared/shared-instance/instance-features-table.component.ts +++ b/client/src/app/shared/shared-instance/instance-features-table.component.ts @@ -21,6 +21,20 @@ export class InstanceFeaturesTableComponent implements OnInit { return Math.min(this.initialUserVideoQuota, this.serverConfig.user.videoQuotaDaily) } + get maxInstanceLives () { + const value = this.serverConfig.live.maxInstanceLives + if (value === -1) return $localize`Unlimited` + + return value + } + + get maxUserLives () { + const value = this.serverConfig.live.maxUserLives + if (value === -1) return $localize`Unlimited` + + return value + } + ngOnInit () { this.serverConfig = this.serverService.getTmpConfig() this.serverService.getConfig() diff --git a/config/default.yaml b/config/default.yaml index d0937bfc8..af16f081f 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -250,6 +250,14 @@ live: # Set null to disable duration limit max_duration: 5 hours + # Limit max number of live videos created on your instance + # -1 == unlimited + max_instance_lives: 20 + + # Limit max number of live videos created by a user on your instance + # -1 == unlimited + max_user_lives: 3 + # Allow your users to save a replay of their live # PeerTube will transcode segments in a video file # If the user daily/total quota is reached, PeerTube will stop the live diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 99aabba62..eb9f5f4b4 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -120,6 +120,8 @@ async function getConfig (req: express.Request, res: express.Response) { allowReplay: CONFIG.LIVE.ALLOW_REPLAY, maxDuration: CONFIG.LIVE.MAX_DURATION, + maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES, + maxUserLives: CONFIG.LIVE.MAX_USER_LIVES, transcoding: { enabled: CONFIG.LIVE.TRANSCODING.ENABLED, @@ -430,6 +432,8 @@ function customConfig (): CustomConfig { enabled: CONFIG.LIVE.ENABLED, allowReplay: CONFIG.LIVE.ALLOW_REPLAY, maxDuration: CONFIG.LIVE.MAX_DURATION, + maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES, + maxUserLives: CONFIG.LIVE.MAX_USER_LIVES, transcoding: { enabled: CONFIG.LIVE.TRANSCODING.ENABLED, threads: CONFIG.LIVE.TRANSCODING.THREADS, diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index d4140e3fa..93b71a242 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts @@ -38,7 +38,7 @@ function checkMissedConfig () { 'federation.videos.federate_unlisted', 'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url', 'search.search_index.disable_local_search', 'search.search_index.is_default_search', - 'live.enabled', 'live.allow_replay', 'live.max_duration', + 'live.enabled', 'live.allow_replay', 'live.max_duration', 'live.max_user_lives', 'live.max_instance_lives', 'live.transcoding.enabled', 'live.transcoding.threads', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p', 'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p', 'live.transcoding.resolutions.2160p' diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 9e8927350..b70361aa9 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts @@ -202,6 +202,9 @@ const CONFIG = { get ENABLED () { return config.get('live.enabled') }, get MAX_DURATION () { return parseDurationToMs(config.get('live.max_duration')) }, + get MAX_INSTANCE_LIVES () { return config.get('live.max_instance_lives') }, + get MAX_USER_LIVES () { return config.get('live.max_user_lives') }, + get ALLOW_REPLAY () { return config.get('live.allow_replay') }, RTMP: { diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 41a6ae4f9..d0071ccc1 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts @@ -65,6 +65,8 @@ const customConfigUpdateValidator = [ body('live.enabled').isBoolean().withMessage('Should have a valid live enabled boolean'), body('live.allowReplay').isBoolean().withMessage('Should have a valid live allow replay boolean'), body('live.maxDuration').custom(isIntOrNull).withMessage('Should have a valid live max duration'), + body('live.maxInstanceLives').custom(isIntOrNull).withMessage('Should have a valid max instance lives'), + body('live.maxUserLives').custom(isIntOrNull).withMessage('Should have a valid max user lives'), body('live.transcoding.enabled').isBoolean().withMessage('Should have a valid live transcoding enabled boolean'), body('live.transcoding.threads').isInt().withMessage('Should have a valid live transcoding threads'), body('live.transcoding.resolutions.240p').isBoolean().withMessage('Should have a valid transcoding 240p resolution enabled boolean'), diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts index ab57e67bf..69200cb60 100644 --- a/server/middlewares/validators/videos/video-live.ts +++ b/server/middlewares/validators/videos/video-live.ts @@ -2,7 +2,7 @@ import * as express from 'express' import { body, param } from 'express-validator' import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '@server/helpers/middlewares/videos' import { VideoLiveModel } from '@server/models/video/video-live' -import { UserRight, VideoState } from '@shared/models' +import { ServerErrorCode, UserRight, VideoState } from '@shared/models' import { isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' import { isVideoNameValid } from '../../../helpers/custom-validators/videos' import { cleanUpReqFiles } from '../../../helpers/express-utils' @@ -10,6 +10,7 @@ import { logger } from '../../../helpers/logger' import { CONFIG } from '../../../initializers/config' import { areValidationErrors } from '../utils' import { getCommonVideoEditAttributes } from './videos' +import { VideoModel } from '@server/models/video/video' const videoLiveGetValidator = [ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), @@ -50,11 +51,15 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ logger.debug('Checking videoLiveAddValidator parameters', { parameters: req.body }) if (CONFIG.LIVE.ENABLED !== true) { + cleanUpReqFiles(req) + return res.status(403) .json({ error: 'Live is not enabled on this instance' }) } if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { + cleanUpReqFiles(req) + return res.status(403) .json({ error: 'Saving live replay is not allowed instance' }) } @@ -64,6 +69,34 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ const user = res.locals.oauth.token.User if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) + if (CONFIG.LIVE.MAX_INSTANCE_LIVES !== -1) { + const totalInstanceLives = await VideoModel.countLocalLives() + + if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { + cleanUpReqFiles(req) + + return res.status(403) + .json({ + code: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED, + error: 'Cannot create this live because the max instance lives limit is reached.' + }) + } + } + + if (CONFIG.LIVE.MAX_USER_LIVES !== -1) { + const totalUserLives = await VideoModel.countLivesOfAccount(user.Account.id) + + if (totalUserLives >= CONFIG.LIVE.MAX_USER_LIVES) { + cleanUpReqFiles(req) + + return res.status(403) + .json({ + code: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED, + error: 'Cannot create this live because the max user lives limit is reached.' + }) + } + } + return next() } ]) diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 78fec5585..d094f19b0 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -1142,6 +1142,37 @@ export class VideoModel extends Model { return VideoModel.getAvailableForApi(queryOptions) } + static countLocalLives () { + const options = { + where: { + remote: false, + isLive: true + } + } + + return VideoModel.count(options) + } + + static countLivesOfAccount (accountId: number) { + const options = { + where: { + remote: false, + isLive: true + }, + include: [ + { + required: true, + model: VideoChannelModel.unscoped(), + where: { + accountId + } + } + ] + } + + return VideoModel.count(options) + } + static load (id: number | string, t?: Transaction): Bluebird { const where = buildWhereIdOrUUID(id) const options = { diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 2882ceb7c..42ac5e1f9 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts @@ -105,6 +105,8 @@ describe('Test config API validators', function () { allowReplay: false, maxDuration: null, + maxInstanceLives: -1, + maxUserLives: 50, transcoding: { enabled: true, diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index a7f035362..6c37be113 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts @@ -81,6 +81,8 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { expect(data.live.enabled).to.be.false expect(data.live.allowReplay).to.be.true expect(data.live.maxDuration).to.equal(1000 * 3600 * 5) + expect(data.live.maxInstanceLives).to.equal(20) + expect(data.live.maxUserLives).to.equal(3) expect(data.live.transcoding.enabled).to.be.false expect(data.live.transcoding.threads).to.equal(2) expect(data.live.transcoding.resolutions['240p']).to.be.false @@ -166,6 +168,8 @@ function checkUpdatedConfig (data: CustomConfig) { expect(data.live.enabled).to.be.true expect(data.live.allowReplay).to.be.false expect(data.live.maxDuration).to.equal(5000) + expect(data.live.maxInstanceLives).to.equal(-1) + expect(data.live.maxUserLives).to.equal(10) expect(data.live.transcoding.enabled).to.be.true expect(data.live.transcoding.threads).to.equal(4) expect(data.live.transcoding.resolutions['240p']).to.be.true @@ -330,6 +334,8 @@ describe('Test config', function () { enabled: true, allowReplay: false, maxDuration: 5000, + maxInstanceLives: -1, + maxUserLives: 10, transcoding: { enabled: true, threads: 4, diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index bb7e23d54..7c1ad0a75 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts @@ -130,6 +130,8 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti enabled: true, allowReplay: false, maxDuration: null, + maxInstanceLives: -1, + maxUserLives: 50, transcoding: { enabled: true, threads: 4, diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index 11b2ef2eb..67e05e23f 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts @@ -99,7 +99,10 @@ export interface CustomConfig { enabled: boolean allowReplay: boolean + maxDuration: number + maxInstanceLives: number + maxUserLives: number transcoding: { enabled: boolean diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index 1563d848e..a01fcbe41 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts @@ -102,6 +102,8 @@ export interface ServerConfig { enabled: boolean maxDuration: number + maxInstanceLives: number + maxUserLives: number allowReplay: boolean transcoding: { diff --git a/shared/models/server/server-error-code.enum.ts b/shared/models/server/server-error-code.enum.ts index 0bfb2c470..c02b0e6c7 100644 --- a/shared/models/server/server-error-code.enum.ts +++ b/shared/models/server/server-error-code.enum.ts @@ -1,3 +1,5 @@ export const enum ServerErrorCode { - DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS = 1 + DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS = 1, + MAX_INSTANCE_LIVES_LIMIT_REACHED = 2, + MAX_USER_LIVES_LIMIT_REACHED = 3, } -- 2.41.0