Add ability to disable tracker

pull/1765/head
Chocobozzz 2019-04-10 09:23:18 +02:00
parent 22834691ab
commit 31b6ddf866
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
16 changed files with 123 additions and 29 deletions

View File

@ -105,6 +105,9 @@ export class ServerService {
enabled: false enabled: false
} }
} }
},
tracker: {
enabled: true
} }
} }
private videoCategories: Array<VideoConstant<number>> = [] private videoCategories: Array<VideoConstant<number>> = []

View File

@ -1,7 +1,6 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { ServerService } from '@app/core' import { ServerService } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill' import { I18n } from '@ngx-translate/i18n-polyfill'
import { ServerConfig } from '../../../../../shared'
@Component({ @Component({
selector: 'my-instance-features-table', selector: 'my-instance-features-table',
@ -65,6 +64,10 @@ export class InstanceFeaturesTableComponent implements OnInit {
{ {
label: this.i18n('Torrent import'), label: this.i18n('Torrent import'),
value: config.import.videos.torrent.enabled value: config.import.videos.torrent.enabled
},
{
label: this.i18n('P2P enabled'),
value: config.tracker.enabled
} }
] ]
} }

View File

@ -427,6 +427,7 @@ my-video-comments {
// If the view is not expanded, take into account the menu // If the view is not expanded, take into account the menu
.privacy-concerns { .privacy-concerns {
width: calc(100% - #{$menu-width}); width: calc(100% - #{$menu-width});
margin-left: -15px;
} }
@media screen and (max-width: $small-view) { @media screen and (max-width: $small-view) {

View File

@ -29,6 +29,7 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model' import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Video } from '@app/shared/video/video.model' import { Video } from '@app/shared/video/video.model'
import { isWebRTCDisabled } from '../../../assets/player/utils'
@Component({ @Component({
selector: 'my-video-watch', selector: 'my-video-watch',
@ -71,6 +72,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private currentTime: number private currentTime: number
private paramsSub: Subscription private paramsSub: Subscription
private queryParamsSub: Subscription private queryParamsSub: Subscription
private configSub: Subscription
constructor ( constructor (
private elementRef: ElementRef, private elementRef: ElementRef,
@ -100,12 +102,16 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
} }
ngOnInit () { ngOnInit () {
if ( this.configSub = this.serverService.configLoaded
!!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false || .subscribe(() => {
peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' if (
) { isWebRTCDisabled() ||
this.hasAlreadyAcceptedPrivacyConcern = true this.serverService.getConfig().tracker.enabled === false ||
} peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true'
) {
this.hasAlreadyAcceptedPrivacyConcern = true
}
})
this.paramsSub = this.route.params.subscribe(routeParams => { this.paramsSub = this.route.params.subscribe(routeParams => {
const videoId = routeParams[ 'videoId' ] const videoId = routeParams[ 'videoId' ]

View File

@ -4,6 +4,10 @@ function toTitleCase (str: string) {
return str.charAt(0).toUpperCase() + str.slice(1) return str.charAt(0).toUpperCase() + str.slice(1)
} }
function isWebRTCDisabled () {
return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false
}
// https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts // https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts
// Don't import all Angular stuff, just copy the code with shame // Don't import all Angular stuff, just copy the code with shame
const dictionaryBytes: Array<{max: number, type: string}> = [ const dictionaryBytes: Array<{max: number, type: string}> = [
@ -141,6 +145,7 @@ export {
toTitleCase, toTitleCase,
timeToInt, timeToInt,
secondsToTime, secondsToTime,
isWebRTCDisabled,
buildVideoLink, buildVideoLink,
buildVideoEmbed, buildVideoEmbed,
videoFileMaxByResolution, videoFileMaxByResolution,

View File

@ -21,16 +21,16 @@
.vjs-dock-description { .vjs-dock-description {
font-size: 11px; font-size: 11px;
&::before, &::after { .text::before, .text::after {
display: inline-block; display: inline-block;
content: '\1F308'; content: '\1F308';
} }
&::before { .text::before {
margin-right: 4px; margin-right: 4px;
} }
&::after { .text::after {
margin-left: 4px; margin-left: 4px;
transform: scale(-1, 1); transform: scale(-1, 1);
} }

View File

@ -2,7 +2,7 @@ import './embed.scss'
import * as Channel from 'jschannel' import * as Channel from 'jschannel'
import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared'
import { PeerTubeResolution } from '../player/definitions' import { PeerTubeResolution } from '../player/definitions'
import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
@ -177,6 +177,10 @@ class PeerTubeEmbed {
return fetch(this.getVideoUrl(videoId) + '/captions') return fetch(this.getVideoUrl(videoId) + '/captions')
} }
loadConfig (): Promise<Response> {
return fetch('/api/v1/config')
}
removeElement (element: HTMLElement) { removeElement (element: HTMLElement) {
element.parentElement.removeChild(element) element.parentElement.removeChild(element)
} }
@ -237,10 +241,10 @@ class PeerTubeEmbed {
try { try {
const params = new URL(window.location.toString()).searchParams const params = new URL(window.location.toString()).searchParams
this.autoplay = this.getParamToggle(params, 'autoplay') this.autoplay = this.getParamToggle(params, 'autoplay', false)
this.controls = this.getParamToggle(params, 'controls') this.controls = this.getParamToggle(params, 'controls', true)
this.muted = this.getParamToggle(params, 'muted') this.muted = this.getParamToggle(params, 'muted', false)
this.loop = this.getParamToggle(params, 'loop') this.loop = this.getParamToggle(params, 'loop', false)
this.enableApi = this.getParamToggle(params, 'api', this.enableApi) this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
this.scope = this.getParamString(params, 'scope', this.scope) this.scope = this.getParamString(params, 'scope', this.scope)
@ -258,10 +262,11 @@ class PeerTubeEmbed {
const urlParts = window.location.pathname.split('/') const urlParts = window.location.pathname.split('/')
const videoId = urlParts[ urlParts.length - 1 ] const videoId = urlParts[ urlParts.length - 1 ]
const [ serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ const [ serverTranslations, videoResponse, captionsResponse, configResponse ] = await Promise.all([
PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language),
this.loadVideoInfo(videoId), this.loadVideoInfo(videoId),
this.loadVideoCaptions(videoId) this.loadVideoCaptions(videoId),
this.loadConfig()
]) ])
if (!videoResponse.ok) { if (!videoResponse.ok) {
@ -338,9 +343,14 @@ class PeerTubeEmbed {
window[ 'videojsPlayer' ] = this.player window[ 'videojsPlayer' ] = this.player
if (this.controls) { if (this.controls) {
const config: ServerConfig = await configResponse.json()
const description = config.tracker.enabled
? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
: undefined
this.player.dock({ this.player.dock({
title: videoInfo.name, title: videoInfo.name,
description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') description
}) })
} }

View File

@ -101,6 +101,16 @@ csp:
report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk! report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk!
report_uri: report_uri:
tracker:
# If you disable the tracker, you disable the P2P aspect of PeerTube
enabled: true
# Only handle requests on your videos.
# If you set this to false it means you have a public tracker.
# Then, it is possible that clients overload your instance with external torrents
private: true
# Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
reject_too_many_announces: false
cache: cache:
previews: previews:
size: 500 # Max number of previews you want to cache size: 500 # Max number of previews you want to cache

View File

@ -102,6 +102,16 @@ csp:
report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk! report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk!
report_uri: report_uri:
tracker:
# If you disable the tracker, you disable the P2P aspect of PeerTube
enabled: true
# Only handle requests on your videos.
# If you set this to false it means you have a public tracker.
# Then, it is possible that clients overload your instance with external torrents
private: true
# Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
reject_too_many_announces: false
############################################################################### ###############################################################################
# #

View File

@ -136,6 +136,9 @@ async function getConfig (req: express.Request, res: express.Response) {
videos: { videos: {
intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
} }
},
tracker: {
enabled: CONFIG.TRACKER.ENABLED
} }
} }

View File

@ -23,6 +23,10 @@ const trackerServer = new TrackerServer({
ws: false, ws: false,
dht: false, dht: false,
filter: async function (infoHash, params, cb) { filter: async function (infoHash, params, cb) {
if (CONFIG.TRACKER.ENABLED === false) {
return cb(new Error('Tracker is disabled on this instance.'))
}
let ip: string let ip: string
if (params.type === 'ws') { if (params.type === 'ws') {
@ -36,11 +40,13 @@ const trackerServer = new TrackerServer({
peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1 peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1
peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1 peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1
if (peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`)) return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`))
} }
try { try {
if (CONFIG.TRACKER.PRIVATE === false) return cb()
const videoFileExists = await VideoFileModel.doesInfohashExist(infoHash) const videoFileExists = await VideoFileModel.doesInfohashExist(infoHash)
if (videoFileExists === true) return cb() if (videoFileExists === true) return cb()
@ -55,13 +61,16 @@ const trackerServer = new TrackerServer({
} }
}) })
trackerServer.on('error', function (err) { if (CONFIG.TRACKER.ENABLED !== false) {
logger.error('Error in tracker.', { err })
})
trackerServer.on('warning', function (err) { trackerServer.on('error', function (err) {
logger.warn('Warning in tracker.', { err }) logger.error('Error in tracker.', { err })
}) })
trackerServer.on('warning', function (err) {
logger.warn('Warning in tracker.', { err })
})
}
const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer) const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer)
trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' })) trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' }))

View File

@ -25,7 +25,8 @@ function checkMissedConfig () {
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
'services.twitter.username', 'services.twitter.whitelisted', 'services.twitter.username', 'services.twitter.whitelisted',
'followers.instance.enabled', 'followers.instance.manual_approval' 'followers.instance.enabled', 'followers.instance.manual_approval',
'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces'
] ]
const requiredAlternatives = [ const requiredAlternatives = [
[ // set [ // set

View File

@ -243,6 +243,11 @@ const CONFIG = {
REPORT_ONLY: config.get<boolean>('csp.report_only'), REPORT_ONLY: config.get<boolean>('csp.report_only'),
REPORT_URI: config.get<boolean>('csp.report_uri') REPORT_URI: config.get<boolean>('csp.report_uri')
}, },
TRACKER: {
ENABLED: config.get<boolean>('tracker.enabled'),
PRIVATE: config.get<boolean>('tracker.private'),
REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
},
ADMIN: { ADMIN: {
get EMAIL () { return config.get<string>('admin.email') } get EMAIL () { return config.get<string>('admin.email') }
}, },

View File

@ -2,7 +2,7 @@
import * as magnetUtil from 'magnet-uri' import * as magnetUtil from 'magnet-uri'
import 'mocha' import 'mocha'
import { getVideo, killallServers, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils' import { getVideo, killallServers, reRunServer, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils'
import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index' import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index'
import { VideoDetails } from '../../../../shared/models/videos' import { VideoDetails } from '../../../../shared/models/videos'
import * as WebTorrent from 'webtorrent' import * as WebTorrent from 'webtorrent'
@ -34,7 +34,7 @@ describe('Test tracker', function () {
} }
}) })
it('Should return an error when adding an incorrect infohash', done => { it('Should return an error when adding an incorrect infohash', function (done) {
this.timeout(10000) this.timeout(10000)
const webtorrent = new WebTorrent() const webtorrent = new WebTorrent()
@ -49,7 +49,7 @@ describe('Test tracker', function () {
torrent.on('done', () => done(new Error('No error on infohash'))) torrent.on('done', () => done(new Error('No error on infohash')))
}) })
it('Should succeed with the correct infohash', done => { it('Should succeed with the correct infohash', function (done) {
this.timeout(10000) this.timeout(10000)
const webtorrent = new WebTorrent() const webtorrent = new WebTorrent()
@ -64,6 +64,26 @@ describe('Test tracker', function () {
torrent.on('done', done) torrent.on('done', done)
}) })
it('Should disable the tracker', function (done) {
this.timeout(20000)
killallServers([ server ])
reRunServer(server, { tracker: { enabled: false } })
.then(() => {
const webtorrent = new WebTorrent()
const torrent = webtorrent.add(goodMagnet)
torrent.on('error', done)
torrent.on('warning', warn => {
const message = typeof warn === 'string' ? warn : warn.message
if (message.indexOf('disabled ') !== -1) return done()
})
torrent.on('done', () => done(new Error('Tracker is enabled')))
})
})
after(async function () { after(async function () {
killallServers([ server ]) killallServers([ server ])
}) })

View File

@ -97,4 +97,8 @@ export interface ServerConfig {
intervalDays: number intervalDays: number
} }
} }
tracker: {
enabled: boolean
}
} }

View File

@ -46,5 +46,9 @@ storage:
log: log:
level: 'info' # debug/info/warning/error level: 'info' # debug/info/warning/error
tracker:
enabled: true
reject_too_many_announces: false # false because we have issues with traefik and ws ip/port forwarding
admin: admin:
email: null email: null