1
0
Fork 0
peertube/server/initializers/constants.ts
Rigel Kent ff2c1fe813 feature: IP filtering on signup page
disable registration form on IP not in range
checking the CIDR list before filtering with it
placing the cidr filters as an attribute object in the config
2018-05-22 19:44:34 +02:00

580 lines
16 KiB
TypeScript

import { IConfig } from 'config'
import { dirname, join } from 'path'
import { JobType, VideoRateType } from '../../shared/models'
import { ActivityPubActorType } from '../../shared/models/activitypub'
import { FollowState } from '../../shared/models/actors'
import { VideoPrivacy } from '../../shared/models/videos'
// Do not use barrels, remain constants as independent as possible
import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
// Use a variable to reload the configuration if we need
let config: IConfig = require('config')
// ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 215
// ---------------------------------------------------------------------------
// API version
const API_VERSION = 'v1'
// Number of results by default for the pagination
const PAGINATION_COUNT_DEFAULT = 15
// Sortable columns per schema
const SORTABLE_COLUMNS = {
USERS: [ 'id', 'username', 'createdAt' ],
ACCOUNTS: [ 'createdAt' ],
JOBS: [ 'createdAt' ],
VIDEO_ABUSES: [ 'id', 'createdAt' ],
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ],
VIDEO_COMMENT_THREADS: [ 'createdAt' ],
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
FOLLOWERS: [ 'createdAt' ],
FOLLOWING: [ 'createdAt' ]
}
const OAUTH_LIFETIME = {
ACCESS_TOKEN: 3600 * 4, // 4 hours
REFRESH_TOKEN: 1209600 // 2 weeks
}
const ROUTE_CACHE_LIFETIME = {
FEEDS: 1000 * 60 * 15, // 15 minutes
ACTIVITY_PUB: {
VIDEOS: 1000 // 1 second, cache concurrent requests after a broadcast for example
}
}
// ---------------------------------------------------------------------------
// Number of points we add/remove after a successful/bad request
const ACTOR_FOLLOW_SCORE = {
PENALTY: -10,
BONUS: 10,
BASE: 1000,
MAX: 10000
}
const FOLLOW_STATES: { [ id: string ]: FollowState } = {
PENDING: 'pending',
ACCEPTED: 'accepted'
}
const REMOTE_SCHEME = {
HTTP: 'https',
WS: 'wss'
}
const JOB_ATTEMPTS: { [ id in JobType ]: number } = {
'activitypub-http-broadcast': 5,
'activitypub-http-unicast': 5,
'activitypub-http-fetcher': 5,
'activitypub-follow': 5,
'video-file': 1,
'email': 5
}
const JOB_CONCURRENCY: { [ id in JobType ]: number } = {
'activitypub-http-broadcast': 1,
'activitypub-http-unicast': 5,
'activitypub-http-fetcher': 1,
'activitypub-follow': 3,
'video-file': 1,
'email': 5
}
const BROADCAST_CONCURRENCY = 10 // How many requests in parallel we do in activitypub-http-broadcast job
const JOB_REQUEST_TIMEOUT = 3000 // 3 seconds
const JOB_REQUEST_TTL = 60000 * 10 // 10 minutes
const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days
// 1 hour
let SCHEDULER_INTERVAL = 60000 * 60
// ---------------------------------------------------------------------------
const CONFIG = {
CUSTOM_FILE: getLocalConfigFilePath(),
LISTEN: {
PORT: config.get<number>('listen.port'),
HOSTNAME: config.get<string>('listen.hostname')
},
DATABASE: {
DBNAME: 'peertube' + config.get<string>('database.suffix'),
HOSTNAME: config.get<string>('database.hostname'),
PORT: config.get<number>('database.port'),
USERNAME: config.get<string>('database.username'),
PASSWORD: config.get<string>('database.password')
},
REDIS: {
HOSTNAME: config.get<string>('redis.hostname'),
PORT: config.get<number>('redis.port'),
AUTH: config.get<string>('redis.auth'),
DB: config.get<number>('redis.db')
},
SMTP: {
HOSTNAME: config.get<string>('smtp.hostname'),
PORT: config.get<number>('smtp.port'),
USERNAME: config.get<string>('smtp.username'),
PASSWORD: config.get<string>('smtp.password'),
TLS: config.get<boolean>('smtp.tls'),
DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'),
CA_FILE: config.get<string>('smtp.ca_file'),
FROM_ADDRESS: config.get<string>('smtp.from_address')
},
STORAGE: {
AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
LOG_DIR: buildPath(config.get<string>('storage.logs')),
VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
CACHE_DIR: buildPath(config.get<string>('storage.cache'))
},
WEBSERVER: {
SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
HOSTNAME: config.get<string>('webserver.hostname'),
PORT: config.get<number>('webserver.port'),
URL: '',
HOST: ''
},
TRUST_PROXY: config.get<string[]>('trust_proxy'),
LOG: {
LEVEL: config.get<string>('log.level')
},
ADMIN: {
get EMAIL () { return config.get<string>('admin.email') }
},
SIGNUP: {
get ENABLED () { return config.get<boolean>('signup.enabled') },
get LIMIT () { return config.get<number>('signup.limit') },
FILTERS: {
CIDR: {
get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') },
get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') }
}
}
},
USER: {
get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
},
TRANSCODING: {
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
get THREADS () { return config.get<number>('transcoding.threads') },
RESOLUTIONS: {
get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
}
},
CACHE: {
PREVIEWS: {
get SIZE () { return config.get<number>('cache.previews.size') }
}
},
INSTANCE: {
get NAME () { return config.get<string>('instance.name') },
get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
get DESCRIPTION () { return config.get<string>('instance.description') },
get TERMS () { return config.get<string>('instance.terms') },
get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
CUSTOMIZATIONS: {
get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
get CSS () { return config.get<string>('instance.customizations.css') }
},
get ROBOTS () { return config.get<string>('instance.robots') }
},
SERVICES: {
TWITTER: {
get USERNAME () { return config.get<string>('services.twitter.username') },
get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') }
}
}
}
// ---------------------------------------------------------------------------
const CONSTRAINTS_FIELDS = {
USERS: {
NAME: { min: 3, max: 120 }, // Length
DESCRIPTION: { min: 3, max: 250 }, // Length
USERNAME: { min: 3, max: 20 }, // Length
PASSWORD: { min: 6, max: 255 }, // Length
VIDEO_QUOTA: { min: -1 }
},
VIDEO_ABUSES: {
REASON: { min: 2, max: 300 } // Length
},
VIDEO_CHANNELS: {
NAME: { min: 3, max: 120 }, // Length
DESCRIPTION: { min: 3, max: 500 }, // Length
SUPPORT: { min: 3, max: 500 }, // Length
URL: { min: 3, max: 2000 } // Length
},
VIDEOS: {
NAME: { min: 3, max: 120 }, // Length
LANGUAGE: { min: 1, max: 10 }, // Length
TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
DESCRIPTION: { min: 3, max: 10000 }, // Length
SUPPORT: { min: 3, max: 500 }, // Length
IMAGE: {
EXTNAME: [ '.jpg', '.jpeg' ],
FILE_SIZE: {
max: 2 * 1024 * 1024 // 2MB
}
},
EXTNAME: [ '.mp4', '.ogv', '.webm' ],
INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
DURATION: { min: 1 }, // Number
TAGS: { min: 0, max: 5 }, // Number of total tags
TAG: { min: 2, max: 30 }, // Length
THUMBNAIL: { min: 2, max: 30 },
THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes
VIEWS: { min: 0 },
LIKES: { min: 0 },
DISLIKES: { min: 0 },
FILE_SIZE: { min: 10 },
URL: { min: 3, max: 2000 } // Length
},
ACTORS: {
PUBLIC_KEY: { min: 10, max: 5000 }, // Length
PRIVATE_KEY: { min: 10, max: 5000 }, // Length
URL: { min: 3, max: 2000 }, // Length
AVATAR: {
EXTNAME: [ '.png', '.jpeg', '.jpg' ],
FILE_SIZE: {
max: 2 * 1024 * 1024 // 2MB
}
}
},
VIDEO_EVENTS: {
COUNT: { min: 0 }
},
VIDEO_COMMENTS: {
TEXT: { min: 1, max: 3000 }, // Length
URL: { min: 3, max: 2000 } // Length
},
VIDEO_SHARE: {
URL: { min: 3, max: 2000 } // Length
}
}
const RATES_LIMIT = {
LOGIN: {
WINDOW_MS: 5 * 60 * 1000, // 5 minutes
MAX: 15 // 15 attempts
}
}
let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
const VIDEO_TRANSCODING_FPS = {
MIN: 10,
MAX: 30
}
const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
LIKE: 'like',
DISLIKE: 'dislike'
}
const VIDEO_CATEGORIES = {
1: 'Music',
2: 'Films',
3: 'Vehicles',
4: 'Art',
5: 'Sports',
6: 'Travels',
7: 'Gaming',
8: 'People',
9: 'Comedy',
10: 'Entertainment',
11: 'News',
12: 'How To',
13: 'Education',
14: 'Activism',
15: 'Science & Technology',
16: 'Animals',
17: 'Kids',
18: 'Food'
}
// See https://creativecommons.org/licenses/?lang=en
const VIDEO_LICENCES = {
1: 'Attribution',
2: 'Attribution - Share Alike',
3: 'Attribution - No Derivatives',
4: 'Attribution - Non Commercial',
5: 'Attribution - Non Commercial - Share Alike',
6: 'Attribution - Non Commercial - No Derivatives',
7: 'Public Domain Dedication'
}
const VIDEO_LANGUAGES = buildLanguages()
const VIDEO_PRIVACIES = {
[VideoPrivacy.PUBLIC]: 'Public',
[VideoPrivacy.UNLISTED]: 'Unlisted',
[VideoPrivacy.PRIVATE]: 'Private'
}
const VIDEO_MIMETYPE_EXT = {
'video/webm': '.webm',
'video/ogg': '.ogv',
'video/mp4': '.mp4'
}
const IMAGE_MIMETYPE_EXT = {
'image/png': '.png',
'image/jpg': '.jpg',
'image/jpeg': '.jpg'
}
// ---------------------------------------------------------------------------
const SERVER_ACTOR_NAME = 'peertube'
const ACTIVITY_PUB = {
POTENTIAL_ACCEPT_HEADERS: [
'application/activity+json',
'application/ld+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
],
ACCEPT_HEADER: 'application/activity+json, application/ld+json',
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
COLLECTION_ITEMS_PER_PAGE: 10,
FETCH_PAGE_LIMIT: 100,
URL_MIME_TYPES: {
VIDEO: Object.keys(VIDEO_MIMETYPE_EXT),
TORRENT: [ 'application/x-bittorrent' ],
MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
},
MAX_RECURSION_COMMENTS: 100,
ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000 // 1 day
}
const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
GROUP: 'Group',
PERSON: 'Person',
APPLICATION: 'Application'
}
// ---------------------------------------------------------------------------
const PRIVATE_RSA_KEY_SIZE = 2048
// Password encryption
const BCRYPT_SALT_SIZE = 10
const USER_PASSWORD_RESET_LIFETIME = 60000 * 5 // 5 minutes
const NSFW_POLICY_TYPES: { [ id: string]: NSFWPolicyType } = {
DO_NOT_LIST: 'do_not_list',
BLUR: 'blur',
DISPLAY: 'display'
}
// ---------------------------------------------------------------------------
// Express static paths (router)
const STATIC_PATHS = {
PREVIEWS: '/static/previews/',
THUMBNAILS: '/static/thumbnails/',
TORRENTS: '/static/torrents/',
WEBSEED: '/static/webseed/',
AVATARS: '/static/avatars/'
}
// Cache control
let STATIC_MAX_AGE = '30d'
// Videos thumbnail size
const THUMBNAILS_SIZE = {
width: 200,
height: 110
}
const PREVIEWS_SIZE = {
width: 560,
height: 315
}
const AVATARS_SIZE = {
width: 120,
height: 120
}
const EMBED_SIZE = {
width: 560,
height: 315
}
// Sub folders of cache directory
const CACHE = {
DIRECTORIES: {
PREVIEWS: join(CONFIG.STORAGE.CACHE_DIR, 'previews')
}
}
const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
// ---------------------------------------------------------------------------
const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
// ---------------------------------------------------------------------------
const FEEDS = {
COUNT: 20
}
// ---------------------------------------------------------------------------
// Special constants for a test instance
if (isTestInstance() === true) {
ACTOR_FOLLOW_SCORE.BASE = 20
REMOTE_SCHEME.HTTP = 'http'
REMOTE_SCHEME.WS = 'ws'
STATIC_MAX_AGE = '0'
ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
SCHEDULER_INTERVAL = 10000
VIDEO_VIEW_LIFETIME = 1000 // 1 second
}
updateWebserverConfig()
// ---------------------------------------------------------------------------
export {
API_VERSION,
AVATARS_SIZE,
ACCEPT_HEADERS,
BCRYPT_SALT_SIZE,
CACHE,
CONFIG,
CONSTRAINTS_FIELDS,
EMBED_SIZE,
JOB_CONCURRENCY,
JOB_ATTEMPTS,
LAST_MIGRATION_VERSION,
OAUTH_LIFETIME,
OPENGRAPH_AND_OEMBED_COMMENT,
BROADCAST_CONCURRENCY,
PAGINATION_COUNT_DEFAULT,
ACTOR_FOLLOW_SCORE,
PREVIEWS_SIZE,
REMOTE_SCHEME,
FOLLOW_STATES,
SERVER_ACTOR_NAME,
PRIVATE_RSA_KEY_SIZE,
ROUTE_CACHE_LIFETIME,
SORTABLE_COLUMNS,
FEEDS,
NSFW_POLICY_TYPES,
STATIC_MAX_AGE,
STATIC_PATHS,
ACTIVITY_PUB,
ACTIVITY_PUB_ACTOR_TYPES,
THUMBNAILS_SIZE,
VIDEO_CATEGORIES,
VIDEO_LANGUAGES,
VIDEO_PRIVACIES,
VIDEO_LICENCES,
VIDEO_RATE_TYPES,
VIDEO_MIMETYPE_EXT,
VIDEO_TRANSCODING_FPS,
JOB_REQUEST_TIMEOUT,
JOB_REQUEST_TTL,
USER_PASSWORD_RESET_LIFETIME,
IMAGE_MIMETYPE_EXT,
SCHEDULER_INTERVAL,
RATES_LIMIT,
JOB_COMPLETED_LIFETIME,
VIDEO_VIEW_LIFETIME
}
// ---------------------------------------------------------------------------
function getLocalConfigFilePath () {
const configSources = config.util.getConfigSources()
if (configSources.length === 0) throw new Error('Invalid config source.')
let filename = 'local'
if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
return join(dirname(configSources[ 0 ].name), filename + '.json')
}
function updateWebserverConfig () {
CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
}
function buildLanguages () {
const iso639 = require('iso-639-3')
const languages: { [ id: string ]: string } = {}
const signLanguages = [
'sgn', // Sign languages (macro language)
'ase', // American
'sdl', // Arabian
'bfi', // British
'bzs', // Brazilian
'csl', // Chinese
'cse', // Czech
'dsl', // Danish
'fsl', // French
'gsg', // German
'pks', // Pakistan
'jsl', // Japanese
'sfs', // South African
'swl', // Swedish
'rsl' // Russian
]
// Only add ISO639-1 languages and some sign languages (ISO639-3)
iso639
.filter(l => {
return (l.iso6391 !== null && l.type === 'living') ||
signLanguages.indexOf(l.iso6393) !== -1
})
.forEach(l => languages[l.iso6391 || l.iso6393] = l.name)
return languages
}
export function reloadConfig () {
function directory () {
if (process.env.NODE_CONFIG_DIR) {
return process.env.NODE_CONFIG_DIR
}
return join(root(), 'config')
}
function purge () {
for (const fileName in require.cache) {
if (-1 === fileName.indexOf(directory())) {
continue
}
delete require.cache[fileName]
}
delete require.cache[require.resolve('config')]
}
purge()
config = require('config')
updateWebserverConfig()
}