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:
parent
e2f1dad836
commit
ff2c1fe813
|
@ -34,7 +34,8 @@ export class ServerService {
|
||||||
},
|
},
|
||||||
serverVersion: 'Unknown',
|
serverVersion: 'Unknown',
|
||||||
signup: {
|
signup: {
|
||||||
allowed: false
|
allowed: false,
|
||||||
|
allowedForCurrentIP: false
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabledResolutions: []
|
enabledResolutions: []
|
||||||
|
|
|
@ -52,7 +52,8 @@ export class MenuComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
isRegistrationAllowed () {
|
isRegistrationAllowed () {
|
||||||
return this.serverService.getConfig().signup.allowed
|
return this.serverService.getConfig().signup.allowed &&
|
||||||
|
this.serverService.getConfig().signup.allowedForCurrentIP
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstAdminRightAvailable () {
|
getFirstAdminRightAvailable () {
|
||||||
|
|
|
@ -60,6 +60,10 @@ admin:
|
||||||
signup:
|
signup:
|
||||||
enabled: false
|
enabled: false
|
||||||
limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited
|
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:
|
user:
|
||||||
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
||||||
|
|
|
@ -76,6 +76,10 @@ admin:
|
||||||
signup:
|
signup:
|
||||||
enabled: false
|
enabled: false
|
||||||
limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited
|
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:
|
user:
|
||||||
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
||||||
|
|
|
@ -84,6 +84,8 @@
|
||||||
"express-rate-limit": "^2.11.0",
|
"express-rate-limit": "^2.11.0",
|
||||||
"express-validator": "^5.0.0",
|
"express-validator": "^5.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.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",
|
"iso-639-3": "^1.0.1",
|
||||||
"js-yaml": "^3.5.4",
|
"js-yaml": "^3.5.4",
|
||||||
"jsonld": "^1.0.1",
|
"jsonld": "^1.0.1",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ServerConfig, UserRight } from '../../../shared'
|
||||||
import { About } from '../../../shared/models/server/about.model'
|
import { About } from '../../../shared/models/server/about.model'
|
||||||
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
||||||
import { unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
|
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 { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
|
||||||
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
|
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
|
||||||
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
|
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) {
|
async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const allowed = await isSignupAllowed()
|
const allowed = await isSignupAllowed()
|
||||||
|
const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
|
||||||
|
|
||||||
const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
|
const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
|
||||||
.filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
|
.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,
|
serverVersion: packageJSON.version,
|
||||||
signup: {
|
signup: {
|
||||||
allowed
|
allowed,
|
||||||
|
allowedForCurrentIP
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabledResolutions
|
enabledResolutions
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureUserHasRight,
|
ensureUserHasRight,
|
||||||
ensureUserRegistrationAllowed,
|
ensureUserRegistrationAllowed,
|
||||||
|
ensureUserRegistrationAllowedForIP,
|
||||||
paginationValidator,
|
paginationValidator,
|
||||||
setDefaultPagination,
|
setDefaultPagination,
|
||||||
setDefaultSort,
|
setDefaultSort,
|
||||||
|
@ -106,6 +107,7 @@ usersRouter.post('/',
|
||||||
|
|
||||||
usersRouter.post('/register',
|
usersRouter.post('/register',
|
||||||
asyncMiddleware(ensureUserRegistrationAllowed),
|
asyncMiddleware(ensureUserRegistrationAllowed),
|
||||||
|
ensureUserRegistrationAllowedForIP,
|
||||||
asyncMiddleware(usersRegisterValidator),
|
asyncMiddleware(usersRegisterValidator),
|
||||||
asyncMiddleware(registerUserRetryWrapper)
|
asyncMiddleware(registerUserRetryWrapper)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { Model } from 'sequelize-typescript'
|
import { Model } from 'sequelize-typescript'
|
||||||
|
import * as ipaddr from 'ipaddr.js'
|
||||||
|
const isCidr = require('is-cidr')
|
||||||
import { ResultList } from '../../shared'
|
import { ResultList } from '../../shared'
|
||||||
import { VideoResolution } from '../../shared/models/videos'
|
import { VideoResolution } from '../../shared/models/videos'
|
||||||
import { CONFIG } from '../initializers'
|
import { CONFIG } from '../initializers'
|
||||||
|
@ -48,6 +50,39 @@ async function isSignupAllowed () {
|
||||||
return totalUsers < CONFIG.SIGNUP.LIMIT
|
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) {
|
function computeResolutionsToTranscode (videoFileHeight: number) {
|
||||||
const resolutionsEnabled: number[] = []
|
const resolutionsEnabled: number[] = []
|
||||||
const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
|
const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
|
||||||
|
@ -99,6 +134,7 @@ export {
|
||||||
generateRandomString,
|
generateRandomString,
|
||||||
getFormattedObjects,
|
getFormattedObjects,
|
||||||
isSignupAllowed,
|
isSignupAllowed,
|
||||||
|
isSignupAllowedForCurrentIP,
|
||||||
computeResolutionsToTranscode,
|
computeResolutionsToTranscode,
|
||||||
resetSequelizeInstance,
|
resetSequelizeInstance,
|
||||||
getServerActor,
|
getServerActor,
|
||||||
|
|
|
@ -27,7 +27,9 @@ function checkMissedConfig () {
|
||||||
'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
|
'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
|
||||||
'log.level',
|
'log.level',
|
||||||
'user.video_quota',
|
'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.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
||||||
'instance.default_nsfw_policy', 'instance.robots',
|
'instance.default_nsfw_policy', 'instance.robots',
|
||||||
'services.twitter.username', 'services.twitter.whitelisted'
|
'services.twitter.username', 'services.twitter.whitelisted'
|
||||||
|
|
|
@ -150,7 +150,13 @@ const CONFIG = {
|
||||||
},
|
},
|
||||||
SIGNUP: {
|
SIGNUP: {
|
||||||
get ENABLED () { return config.get<boolean>('signup.enabled') },
|
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: {
|
USER: {
|
||||||
get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
|
get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
|
||||||
|
|
|
@ -16,8 +16,8 @@ import {
|
||||||
} from '../../helpers/custom-validators/users'
|
} from '../../helpers/custom-validators/users'
|
||||||
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { isSignupAllowed } from '../../helpers/utils'
|
import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/utils'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
import { Redis } from '../../lib/redis'
|
import { Redis } from '../../lib/redis'
|
||||||
import { UserModel } from '../../models/account/user'
|
import { UserModel } from '../../models/account/user'
|
||||||
import { areValidationErrors } from './utils'
|
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 = [
|
const usersAskResetPasswordValidator = [
|
||||||
body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
|
body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
|
||||||
|
|
||||||
|
@ -230,6 +244,7 @@ export {
|
||||||
usersUpdateMeValidator,
|
usersUpdateMeValidator,
|
||||||
usersVideoRatingValidator,
|
usersVideoRatingValidator,
|
||||||
ensureUserRegistrationAllowed,
|
ensureUserRegistrationAllowed,
|
||||||
|
ensureUserRegistrationAllowedForIP,
|
||||||
usersGetValidator,
|
usersGetValidator,
|
||||||
usersUpdateMyAvatarValidator,
|
usersUpdateMyAvatarValidator,
|
||||||
usersAskResetPasswordValidator,
|
usersAskResetPasswordValidator,
|
||||||
|
|
|
@ -15,7 +15,8 @@ export interface ServerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
signup: {
|
signup: {
|
||||||
allowed: boolean
|
allowed: boolean,
|
||||||
|
allowedForCurrentIP: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
transcoding: {
|
transcoding: {
|
||||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -1294,6 +1294,12 @@ ci-info@^1.0.0:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2"
|
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:
|
circular-json@^0.3.1:
|
||||||
version "0.3.3"
|
version "0.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
|
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"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
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:
|
ip-set@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ip-set/-/ip-set-1.0.1.tgz#633b66d0bd6c8d0de968d053263c9120d3b6727e"
|
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"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.7.0.tgz#2206ed334afc32e01fed3ee838b6b2521068b9d2"
|
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:
|
ipv6-normalize@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz#1b3258290d365fa83239e89907dde4592e7620a8"
|
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:
|
dependencies:
|
||||||
ci-info "^1.0.0"
|
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:
|
is-data-descriptor@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
|
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user