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
This commit is contained in:
Rigel Kent 2018-05-22 19:43:13 +02:00 committed by Rigel Kent
parent e2f1dad836
commit ff2c1fe813
13 changed files with 105 additions and 9 deletions

View File

@ -34,7 +34,8 @@ export class ServerService {
},
serverVersion: 'Unknown',
signup: {
allowed: false
allowed: false,
allowedForCurrentIP: false
},
transcoding: {
enabledResolutions: []

View File

@ -52,7 +52,8 @@ export class MenuComponent implements OnInit {
}
isRegistrationAllowed () {
return this.serverService.getConfig().signup.allowed
return this.serverService.getConfig().signup.allowed &&
this.serverService.getConfig().signup.allowedForCurrentIP
}
getFirstAdminRightAvailable () {

View File

@ -60,6 +60,10 @@ admin:
signup:
enabled: false
limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited
filters:
cidr: # You can specify CIDR ranges to whitelist (empty = no filtering) or blacklist
whitelist: []
blacklist: []
user:
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).

View File

@ -76,6 +76,10 @@ admin:
signup:
enabled: false
limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited
filters:
cidr: # You can specify CIDR ranges to whitelist (empty = no filtering) or blacklist
whitelist: []
blacklist: []
user:
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).

View File

@ -84,6 +84,8 @@
"express-rate-limit": "^2.11.0",
"express-validator": "^5.0.0",
"fluent-ffmpeg": "^2.1.0",
"ipaddr.js": "https://github.com/whitequark/ipaddr.js.git#8e69afeb4053ee32447a101845f860848280eca5",
"is-cidr": "^2.0.5",
"iso-639-3": "^1.0.1",
"js-yaml": "^3.5.4",
"jsonld": "^1.0.1",

View File

@ -4,7 +4,7 @@ import { ServerConfig, UserRight } from '../../../shared'
import { About } from '../../../shared/models/server/about.model'
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
import { unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
import { isSignupAllowed } from '../../helpers/utils'
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/utils'
import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
@ -36,6 +36,7 @@ configRouter.delete('/custom',
async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
const allowed = await isSignupAllowed()
const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
.filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
@ -54,7 +55,8 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
},
serverVersion: packageJSON.version,
signup: {
allowed
allowed,
allowedForCurrentIP
},
transcoding: {
enabledResolutions

View File

@ -19,6 +19,7 @@ import {
authenticate,
ensureUserHasRight,
ensureUserRegistrationAllowed,
ensureUserRegistrationAllowedForIP,
paginationValidator,
setDefaultPagination,
setDefaultSort,
@ -106,6 +107,7 @@ usersRouter.post('/',
usersRouter.post('/register',
asyncMiddleware(ensureUserRegistrationAllowed),
ensureUserRegistrationAllowedForIP,
asyncMiddleware(usersRegisterValidator),
asyncMiddleware(registerUserRetryWrapper)
)

View File

@ -1,4 +1,6 @@
import { Model } from 'sequelize-typescript'
import * as ipaddr from 'ipaddr.js'
const isCidr = require('is-cidr')
import { ResultList } from '../../shared'
import { VideoResolution } from '../../shared/models/videos'
import { CONFIG } from '../initializers'
@ -48,6 +50,39 @@ async function isSignupAllowed () {
return totalUsers < CONFIG.SIGNUP.LIMIT
}
function isSignupAllowedForCurrentIP (ip: string) {
const addr = ipaddr.parse(ip)
let excludeList = [ 'blacklist' ]
let matched: string
// if there is a valid, non-empty whitelist, we exclude all unknown adresses too
if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) {
excludeList.push('unknown')
}
if (addr.kind() === 'ipv4') {
const addrV4 = ipaddr.IPv4.parse(ip)
const rangeList = {
whitelist: CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr.v4(cidr))
.map(cidr => ipaddr.IPv4.parseCIDR(cidr)),
blacklist: CONFIG.SIGNUP.FILTERS.CIDR.BLACKLIST.filter(cidr => isCidr.v4(cidr))
.map(cidr => ipaddr.IPv4.parseCIDR(cidr))
}
matched = ipaddr.subnetMatch(addrV4, rangeList, 'unknown')
} else if (addr.kind() === 'ipv6') {
const addrV6 = ipaddr.IPv6.parse(ip)
const rangeList = {
whitelist: CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr.v6(cidr))
.map(cidr => ipaddr.IPv6.parseCIDR(cidr)),
blacklist: CONFIG.SIGNUP.FILTERS.CIDR.BLACKLIST.filter(cidr => isCidr.v6(cidr))
.map(cidr => ipaddr.IPv6.parseCIDR(cidr))
}
matched = ipaddr.subnetMatch(addrV6, rangeList, 'unknown')
}
return !excludeList.includes(matched)
}
function computeResolutionsToTranscode (videoFileHeight: number) {
const resolutionsEnabled: number[] = []
const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
@ -99,6 +134,7 @@ export {
generateRandomString,
getFormattedObjects,
isSignupAllowed,
isSignupAllowedForCurrentIP,
computeResolutionsToTranscode,
resetSequelizeInstance,
getServerActor,

View File

@ -27,7 +27,9 @@ function checkMissedConfig () {
'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
'log.level',
'user.video_quota',
'cache.previews.size', 'admin.email', 'signup.enabled', 'signup.limit', 'transcoding.enabled', 'transcoding.threads',
'cache.previews.size', 'admin.email',
'signup.enabled', 'signup.limit', 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
'transcoding.enabled', 'transcoding.threads',
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
'instance.default_nsfw_policy', 'instance.robots',
'services.twitter.username', 'services.twitter.whitelisted'

View File

@ -150,7 +150,13 @@ const CONFIG = {
},
SIGNUP: {
get ENABLED () { return config.get<boolean>('signup.enabled') },
get LIMIT () { return config.get<number>('signup.limit') }
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') }

View File

@ -16,8 +16,8 @@ import {
} from '../../helpers/custom-validators/users'
import { isVideoExist } from '../../helpers/custom-validators/videos'
import { logger } from '../../helpers/logger'
import { isSignupAllowed } from '../../helpers/utils'
import { CONSTRAINTS_FIELDS } from '../../initializers'
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/utils'
import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
import { Redis } from '../../lib/redis'
import { UserModel } from '../../models/account/user'
import { areValidationErrors } from './utils'
@ -177,6 +177,20 @@ const ensureUserRegistrationAllowed = [
}
]
const ensureUserRegistrationAllowedForIP = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
const allowed = isSignupAllowedForCurrentIP(req.ip)
if (allowed === false) {
return res.status(403)
.send({ error: 'You are not on a network authorized for registration.' })
.end()
}
return next()
}
]
const usersAskResetPasswordValidator = [
body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
@ -230,6 +244,7 @@ export {
usersUpdateMeValidator,
usersVideoRatingValidator,
ensureUserRegistrationAllowed,
ensureUserRegistrationAllowedForIP,
usersGetValidator,
usersUpdateMyAvatarValidator,
usersAskResetPasswordValidator,

View File

@ -15,7 +15,8 @@ export interface ServerConfig {
}
signup: {
allowed: boolean
allowed: boolean,
allowedForCurrentIP: boolean
}
transcoding: {

View File

@ -1294,6 +1294,12 @@ ci-info@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2"
cidr-regex@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-2.0.8.tgz#c79bae6223d241c0860d93bfde1fb1c1c4fdcab6"
dependencies:
ip-regex "^2.1.0"
circular-json@^0.3.1:
version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
@ -3671,6 +3677,10 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
ip-set@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ip-set/-/ip-set-1.0.1.tgz#633b66d0bd6c8d0de968d053263c9120d3b6727e"
@ -3693,6 +3703,10 @@ ipaddr.js@1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.7.0.tgz#2206ed334afc32e01fed3ee838b6b2521068b9d2"
"ipaddr.js@https://github.com/whitequark/ipaddr.js.git#8e69afeb4053ee32447a101845f860848280eca5":
version "1.7.0"
resolved "https://github.com/whitequark/ipaddr.js.git#8e69afeb4053ee32447a101845f860848280eca5"
ipv6-normalize@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz#1b3258290d365fa83239e89907dde4592e7620a8"
@ -3747,6 +3761,12 @@ is-ci@^1.0.10, is-ci@^1.1.0:
dependencies:
ci-info "^1.0.0"
is-cidr@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-2.0.5.tgz#13227927d71865d1177fe0e5b60e6ddd3dee0034"
dependencies:
cidr-regex "^2.0.8"
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"