aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares/validators
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-01-19 09:27:16 +0100
committerChocobozzz <chocobozzz@cpy.re>2023-01-19 13:53:40 +0100
commite364e31e25bd1d4b8d801c845a96d6be708f0a18 (patch)
tree220785a42af361706eb8243960c5da9cddf4d2be /server/middlewares/validators
parentbc48e33b80f357767b98c1d310b04bdda24c6d46 (diff)
downloadPeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.gz
PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.zst
PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.zip
Implement signup approval in server
Diffstat (limited to 'server/middlewares/validators')
-rw-r--r--server/middlewares/validators/config.ts1
-rw-r--r--server/middlewares/validators/index.ts2
-rw-r--r--server/middlewares/validators/shared/user-registrations.ts60
-rw-r--r--server/middlewares/validators/shared/users.ts4
-rw-r--r--server/middlewares/validators/sort.ts95
-rw-r--r--server/middlewares/validators/user-email-verification.ts94
-rw-r--r--server/middlewares/validators/user-registrations.ts203
-rw-r--r--server/middlewares/validators/users.ts151
8 files changed, 399 insertions, 211 deletions
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts
index 3a7daa573..c2dbfadb7 100644
--- a/server/middlewares/validators/config.ts
+++ b/server/middlewares/validators/config.ts
@@ -29,6 +29,7 @@ const customConfigUpdateValidator = [
29 body('signup.enabled').isBoolean(), 29 body('signup.enabled').isBoolean(),
30 body('signup.limit').isInt(), 30 body('signup.limit').isInt(),
31 body('signup.requiresEmailVerification').isBoolean(), 31 body('signup.requiresEmailVerification').isBoolean(),
32 body('signup.requiresApproval').isBoolean(),
32 body('signup.minimumAge').isInt(), 33 body('signup.minimumAge').isInt(),
33 34
34 body('admin.email').isEmail(), 35 body('admin.email').isEmail(),
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index 9bc8887ff..1d0964667 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -21,8 +21,10 @@ export * from './server'
21export * from './sort' 21export * from './sort'
22export * from './static' 22export * from './static'
23export * from './themes' 23export * from './themes'
24export * from './user-email-verification'
24export * from './user-history' 25export * from './user-history'
25export * from './user-notifications' 26export * from './user-notifications'
27export * from './user-registrations'
26export * from './user-subscriptions' 28export * from './user-subscriptions'
27export * from './users' 29export * from './users'
28export * from './videos' 30export * from './videos'
diff --git a/server/middlewares/validators/shared/user-registrations.ts b/server/middlewares/validators/shared/user-registrations.ts
new file mode 100644
index 000000000..dbc7dda06
--- /dev/null
+++ b/server/middlewares/validators/shared/user-registrations.ts
@@ -0,0 +1,60 @@
1import express from 'express'
2import { UserRegistrationModel } from '@server/models/user/user-registration'
3import { MRegistration } from '@server/types/models'
4import { forceNumber, pick } from '@shared/core-utils'
5import { HttpStatusCode } from '@shared/models'
6
7function checkRegistrationIdExist (idArg: number | string, res: express.Response) {
8 const id = forceNumber(idArg)
9 return checkRegistrationExist(() => UserRegistrationModel.load(id), res)
10}
11
12function checkRegistrationEmailExist (email: string, res: express.Response, abortResponse = true) {
13 return checkRegistrationExist(() => UserRegistrationModel.loadByEmail(email), res, abortResponse)
14}
15
16async function checkRegistrationHandlesDoNotAlreadyExist (options: {
17 username: string
18 channelHandle: string
19 email: string
20 res: express.Response
21}) {
22 const { res } = options
23
24 const registration = await UserRegistrationModel.loadByEmailOrHandle(pick(options, [ 'username', 'email', 'channelHandle' ]))
25
26 if (registration) {
27 res.fail({
28 status: HttpStatusCode.CONFLICT_409,
29 message: 'Registration with this username, channel name or email already exists.'
30 })
31 return false
32 }
33
34 return true
35}
36
37async function checkRegistrationExist (finder: () => Promise<MRegistration>, res: express.Response, abortResponse = true) {
38 const registration = await finder()
39
40 if (!registration) {
41 if (abortResponse === true) {
42 res.fail({
43 status: HttpStatusCode.NOT_FOUND_404,
44 message: 'User not found'
45 })
46 }
47
48 return false
49 }
50
51 res.locals.userRegistration = registration
52 return true
53}
54
55export {
56 checkRegistrationIdExist,
57 checkRegistrationEmailExist,
58 checkRegistrationHandlesDoNotAlreadyExist,
59 checkRegistrationExist
60}
diff --git a/server/middlewares/validators/shared/users.ts b/server/middlewares/validators/shared/users.ts
index b8f1436d3..030adc9f7 100644
--- a/server/middlewares/validators/shared/users.ts
+++ b/server/middlewares/validators/shared/users.ts
@@ -14,7 +14,7 @@ function checkUserEmailExist (email: string, res: express.Response, abortRespons
14 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse) 14 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
15} 15}
16 16
17async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) { 17async function checkUserNameOrEmailDoNotAlreadyExist (username: string, email: string, res: express.Response) {
18 const user = await UserModel.loadByUsernameOrEmail(username, email) 18 const user = await UserModel.loadByUsernameOrEmail(username, email)
19 19
20 if (user) { 20 if (user) {
@@ -58,6 +58,6 @@ async function checkUserExist (finder: () => Promise<MUserDefault>, res: express
58export { 58export {
59 checkUserIdExist, 59 checkUserIdExist,
60 checkUserEmailExist, 60 checkUserEmailExist,
61 checkUserNameOrEmailDoesNotAlreadyExist, 61 checkUserNameOrEmailDoNotAlreadyExist,
62 checkUserExist 62 checkUserExist
63} 63}
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index 7d0639107..e6cc46317 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -1,9 +1,41 @@
1import express from 'express' 1import express from 'express'
2import { query } from 'express-validator' 2import { query } from 'express-validator'
3
4import { SORTABLE_COLUMNS } from '../../initializers/constants' 3import { SORTABLE_COLUMNS } from '../../initializers/constants'
5import { areValidationErrors } from './shared' 4import { areValidationErrors } from './shared'
6 5
6export const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS)
7export const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS)
8export const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ])
9export const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES)
10export const videosSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS)
11export const videoImportsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_IMPORTS)
12export const videosSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS_SEARCH)
13export const videoChannelsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH)
14export const videoPlaylistsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS_SEARCH)
15export const videoCommentsValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENTS)
16export const videoCommentThreadsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
17export const videoRatesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_RATES)
18export const blacklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.BLACKLISTS)
19export const videoChannelsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS)
20export const instanceFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWERS)
21export const instanceFollowingSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWING)
22export const userSubscriptionsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_SUBSCRIPTIONS)
23export const accountsBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST)
24export const serversBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.SERVERS_BLOCKLIST)
25export const userNotificationsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_NOTIFICATIONS)
26export const videoPlaylistsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS)
27export const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS)
28export const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
29export const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES)
30export const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS)
31
32export const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS)
33export const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS)
34
35export const userRegistrationsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_REGISTRATIONS)
36
37// ---------------------------------------------------------------------------
38
7function checkSortFactory (columns: string[], tags: string[] = []) { 39function checkSortFactory (columns: string[], tags: string[] = []) {
8 return checkSort(createSortableColumns(columns), tags) 40 return checkSort(createSortableColumns(columns), tags)
9} 41}
@@ -27,64 +59,3 @@ function createSortableColumns (sortableColumns: string[]) {
27 59
28 return sortableColumns.concat(sortableColumnDesc) 60 return sortableColumns.concat(sortableColumnDesc)
29} 61}
30
31const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS)
32const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS)
33const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ])
34const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES)
35const videosSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS)
36const videoImportsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_IMPORTS)
37const videosSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS_SEARCH)
38const videoChannelsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH)
39const videoPlaylistsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS_SEARCH)
40const videoCommentsValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENTS)
41const videoCommentThreadsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
42const videoRatesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_RATES)
43const blacklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.BLACKLISTS)
44const videoChannelsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS)
45const instanceFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWERS)
46const instanceFollowingSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWING)
47const userSubscriptionsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_SUBSCRIPTIONS)
48const accountsBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST)
49const serversBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.SERVERS_BLOCKLIST)
50const userNotificationsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_NOTIFICATIONS)
51const videoPlaylistsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS)
52const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS)
53const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
54const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES)
55const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS)
56
57const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS)
58const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS)
59
60// ---------------------------------------------------------------------------
61
62export {
63 adminUsersSortValidator,
64 abusesSortValidator,
65 videoChannelsSortValidator,
66 videoImportsSortValidator,
67 videoCommentsValidator,
68 videosSearchSortValidator,
69 videosSortValidator,
70 blacklistSortValidator,
71 accountsSortValidator,
72 instanceFollowersSortValidator,
73 instanceFollowingSortValidator,
74 jobsSortValidator,
75 videoCommentThreadsSortValidator,
76 videoRatesSortValidator,
77 userSubscriptionsSortValidator,
78 availablePluginsSortValidator,
79 videoChannelsSearchSortValidator,
80 accountsBlocklistSortValidator,
81 serversBlocklistSortValidator,
82 userNotificationsSortValidator,
83 videoPlaylistsSortValidator,
84 videoRedundanciesSortValidator,
85 videoPlaylistsSearchSortValidator,
86 accountsFollowersSortValidator,
87 videoChannelsFollowersSortValidator,
88 videoChannelSyncsSortValidator,
89 pluginsSortValidator
90}
diff --git a/server/middlewares/validators/user-email-verification.ts b/server/middlewares/validators/user-email-verification.ts
new file mode 100644
index 000000000..74702a8f5
--- /dev/null
+++ b/server/middlewares/validators/user-email-verification.ts
@@ -0,0 +1,94 @@
1import express from 'express'
2import { body, param } from 'express-validator'
3import { toBooleanOrNull } from '@server/helpers/custom-validators/misc'
4import { HttpStatusCode } from '@shared/models'
5import { logger } from '../../helpers/logger'
6import { Redis } from '../../lib/redis'
7import { areValidationErrors, checkUserEmailExist, checkUserIdExist } from './shared'
8import { checkRegistrationEmailExist, checkRegistrationIdExist } from './shared/user-registrations'
9
10const usersAskSendVerifyEmailValidator = [
11 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
12
13 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 if (areValidationErrors(req, res)) return
15
16 const [ userExists, registrationExists ] = await Promise.all([
17 checkUserEmailExist(req.body.email, res, false),
18 checkRegistrationEmailExist(req.body.email, res, false)
19 ])
20
21 if (!userExists && !registrationExists) {
22 logger.debug('User or registration with email %s does not exist (asking verify email).', req.body.email)
23 // Do not leak our emails
24 return res.status(HttpStatusCode.NO_CONTENT_204).end()
25 }
26
27 if (res.locals.user?.pluginAuth) {
28 return res.fail({
29 status: HttpStatusCode.CONFLICT_409,
30 message: 'Cannot ask verification email of a user that uses a plugin authentication.'
31 })
32 }
33
34 return next()
35 }
36]
37
38const usersVerifyEmailValidator = [
39 param('id')
40 .isInt().not().isEmpty().withMessage('Should have a valid id'),
41
42 body('verificationString')
43 .not().isEmpty().withMessage('Should have a valid verification string'),
44 body('isPendingEmail')
45 .optional()
46 .customSanitizer(toBooleanOrNull),
47
48 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
49 if (areValidationErrors(req, res)) return
50 if (!await checkUserIdExist(req.params.id, res)) return
51
52 const user = res.locals.user
53 const redisVerificationString = await Redis.Instance.getUserVerifyEmailLink(user.id)
54
55 if (redisVerificationString !== req.body.verificationString) {
56 return res.fail({ status: HttpStatusCode.FORBIDDEN_403, message: 'Invalid verification string.' })
57 }
58
59 return next()
60 }
61]
62
63// ---------------------------------------------------------------------------
64
65const registrationVerifyEmailValidator = [
66 param('registrationId')
67 .isInt().not().isEmpty().withMessage('Should have a valid registrationId'),
68
69 body('verificationString')
70 .not().isEmpty().withMessage('Should have a valid verification string'),
71
72 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
73 if (areValidationErrors(req, res)) return
74 if (!await checkRegistrationIdExist(req.params.registrationId, res)) return
75
76 const registration = res.locals.userRegistration
77 const redisVerificationString = await Redis.Instance.getRegistrationVerifyEmailLink(registration.id)
78
79 if (redisVerificationString !== req.body.verificationString) {
80 return res.fail({ status: HttpStatusCode.FORBIDDEN_403, message: 'Invalid verification string.' })
81 }
82
83 return next()
84 }
85]
86
87// ---------------------------------------------------------------------------
88
89export {
90 usersAskSendVerifyEmailValidator,
91 usersVerifyEmailValidator,
92
93 registrationVerifyEmailValidator
94}
diff --git a/server/middlewares/validators/user-registrations.ts b/server/middlewares/validators/user-registrations.ts
new file mode 100644
index 000000000..e263c27c5
--- /dev/null
+++ b/server/middlewares/validators/user-registrations.ts
@@ -0,0 +1,203 @@
1import express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator'
3import { exists, isIdValid } from '@server/helpers/custom-validators/misc'
4import { isRegistrationModerationResponseValid, isRegistrationReasonValid } from '@server/helpers/custom-validators/user-registration'
5import { CONFIG } from '@server/initializers/config'
6import { Hooks } from '@server/lib/plugins/hooks'
7import { HttpStatusCode, UserRegister, UserRegistrationRequest, UserRegistrationState } from '@shared/models'
8import { isUserDisplayNameValid, isUserPasswordValid, isUserUsernameValid } from '../../helpers/custom-validators/users'
9import { isVideoChannelDisplayNameValid, isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels'
10import { isSignupAllowed, isSignupAllowedForCurrentIP, SignupMode } from '../../lib/signup'
11import { ActorModel } from '../../models/actor/actor'
12import { areValidationErrors, checkUserNameOrEmailDoNotAlreadyExist } from './shared'
13import { checkRegistrationHandlesDoNotAlreadyExist, checkRegistrationIdExist } from './shared/user-registrations'
14
15const usersDirectRegistrationValidator = usersCommonRegistrationValidatorFactory()
16
17const usersRequestRegistrationValidator = [
18 ...usersCommonRegistrationValidatorFactory([
19 body('registrationReason')
20 .custom(isRegistrationReasonValid)
21 ]),
22
23 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
24 const body: UserRegistrationRequest = req.body
25
26 if (CONFIG.SIGNUP.REQUIRES_APPROVAL !== true) {
27 return res.fail({
28 status: HttpStatusCode.BAD_REQUEST_400,
29 message: 'Signup approval is not enabled on this instance'
30 })
31 }
32
33 const options = { username: body.username, email: body.email, channelHandle: body.channel?.name, res }
34 if (!await checkRegistrationHandlesDoNotAlreadyExist(options)) return
35
36 return next()
37 }
38]
39
40// ---------------------------------------------------------------------------
41
42function ensureUserRegistrationAllowedFactory (signupMode: SignupMode) {
43 return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
44 const allowedParams = {
45 body: req.body,
46 ip: req.ip,
47 signupMode
48 }
49
50 const allowedResult = await Hooks.wrapPromiseFun(
51 isSignupAllowed,
52 allowedParams,
53
54 signupMode === 'direct-registration'
55 ? 'filter:api.user.signup.allowed.result'
56 : 'filter:api.user.request-signup.allowed.result'
57 )
58
59 if (allowedResult.allowed === false) {
60 return res.fail({
61 status: HttpStatusCode.FORBIDDEN_403,
62 message: allowedResult.errorMessage || 'User registration is not enabled, user limit is reached or registration requires approval.'
63 })
64 }
65
66 return next()
67 }
68}
69
70const ensureUserRegistrationAllowedForIP = [
71 (req: express.Request, res: express.Response, next: express.NextFunction) => {
72 const allowed = isSignupAllowedForCurrentIP(req.ip)
73
74 if (allowed === false) {
75 return res.fail({
76 status: HttpStatusCode.FORBIDDEN_403,
77 message: 'You are not on a network authorized for registration.'
78 })
79 }
80
81 return next()
82 }
83]
84
85// ---------------------------------------------------------------------------
86
87const acceptOrRejectRegistrationValidator = [
88 param('registrationId')
89 .custom(isIdValid),
90
91 body('moderationResponse')
92 .custom(isRegistrationModerationResponseValid),
93
94 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
95 if (areValidationErrors(req, res)) return
96 if (!await checkRegistrationIdExist(req.params.registrationId, res)) return
97
98 if (res.locals.userRegistration.state !== UserRegistrationState.PENDING) {
99 return res.fail({
100 status: HttpStatusCode.CONFLICT_409,
101 message: 'This registration is already accepted or rejected.'
102 })
103 }
104
105 return next()
106 }
107]
108
109// ---------------------------------------------------------------------------
110
111const getRegistrationValidator = [
112 param('registrationId')
113 .custom(isIdValid),
114
115 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
116 if (areValidationErrors(req, res)) return
117 if (!await checkRegistrationIdExist(req.params.registrationId, res)) return
118
119 return next()
120 }
121]
122
123// ---------------------------------------------------------------------------
124
125const listRegistrationsValidator = [
126 query('search')
127 .optional()
128 .custom(exists),
129
130 (req: express.Request, res: express.Response, next: express.NextFunction) => {
131 if (areValidationErrors(req, res)) return
132
133 return next()
134 }
135]
136
137// ---------------------------------------------------------------------------
138
139export {
140 usersDirectRegistrationValidator,
141 usersRequestRegistrationValidator,
142
143 ensureUserRegistrationAllowedFactory,
144 ensureUserRegistrationAllowedForIP,
145
146 getRegistrationValidator,
147 listRegistrationsValidator,
148
149 acceptOrRejectRegistrationValidator
150}
151
152// ---------------------------------------------------------------------------
153
154function usersCommonRegistrationValidatorFactory (additionalValidationChain: ValidationChain[] = []) {
155 return [
156 body('username')
157 .custom(isUserUsernameValid),
158 body('password')
159 .custom(isUserPasswordValid),
160 body('email')
161 .isEmail(),
162 body('displayName')
163 .optional()
164 .custom(isUserDisplayNameValid),
165
166 body('channel.name')
167 .optional()
168 .custom(isVideoChannelUsernameValid),
169 body('channel.displayName')
170 .optional()
171 .custom(isVideoChannelDisplayNameValid),
172
173 ...additionalValidationChain,
174
175 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
176 if (areValidationErrors(req, res, { omitBodyLog: true })) return
177
178 const body: UserRegister | UserRegistrationRequest = req.body
179
180 if (!await checkUserNameOrEmailDoNotAlreadyExist(body.username, body.email, res)) return
181
182 if (body.channel) {
183 if (!body.channel.name || !body.channel.displayName) {
184 return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
185 }
186
187 if (body.channel.name === body.username) {
188 return res.fail({ message: 'Channel name cannot be the same as user username.' })
189 }
190
191 const existing = await ActorModel.loadLocalByName(body.channel.name)
192 if (existing) {
193 return res.fail({
194 status: HttpStatusCode.CONFLICT_409,
195 message: `Channel with name ${body.channel.name} already exists.`
196 })
197 }
198 }
199
200 return next()
201 }
202 ]
203}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 64bd9ca70..f7033f44a 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -1,8 +1,7 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { Hooks } from '@server/lib/plugins/hooks'
4import { forceNumber } from '@shared/core-utils' 3import { forceNumber } from '@shared/core-utils'
5import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' 4import { HttpStatusCode, UserRight, UserRole } from '@shared/models'
6import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 5import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
7import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 6import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
8import { 7import {
@@ -24,17 +23,16 @@ import {
24 isUserVideoQuotaValid, 23 isUserVideoQuotaValid,
25 isUserVideosHistoryEnabledValid 24 isUserVideosHistoryEnabledValid
26} from '../../helpers/custom-validators/users' 25} from '../../helpers/custom-validators/users'
27import { isVideoChannelDisplayNameValid, isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels' 26import { isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels'
28import { logger } from '../../helpers/logger' 27import { logger } from '../../helpers/logger'
29import { isThemeRegistered } from '../../lib/plugins/theme-utils' 28import { isThemeRegistered } from '../../lib/plugins/theme-utils'
30import { Redis } from '../../lib/redis' 29import { Redis } from '../../lib/redis'
31import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup'
32import { ActorModel } from '../../models/actor/actor' 30import { ActorModel } from '../../models/actor/actor'
33import { 31import {
34 areValidationErrors, 32 areValidationErrors,
35 checkUserEmailExist, 33 checkUserEmailExist,
36 checkUserIdExist, 34 checkUserIdExist,
37 checkUserNameOrEmailDoesNotAlreadyExist, 35 checkUserNameOrEmailDoNotAlreadyExist,
38 doesVideoChannelIdExist, 36 doesVideoChannelIdExist,
39 doesVideoExist, 37 doesVideoExist,
40 isValidVideoIdParam 38 isValidVideoIdParam
@@ -81,7 +79,7 @@ const usersAddValidator = [
81 79
82 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 80 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
83 if (areValidationErrors(req, res, { omitBodyLog: true })) return 81 if (areValidationErrors(req, res, { omitBodyLog: true })) return
84 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return 82 if (!await checkUserNameOrEmailDoNotAlreadyExist(req.body.username, req.body.email, res)) return
85 83
86 const authUser = res.locals.oauth.token.User 84 const authUser = res.locals.oauth.token.User
87 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { 85 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) {
@@ -109,51 +107,6 @@ const usersAddValidator = [
109 } 107 }
110] 108]
111 109
112const usersRegisterValidator = [
113 body('username')
114 .custom(isUserUsernameValid),
115 body('password')
116 .custom(isUserPasswordValid),
117 body('email')
118 .isEmail(),
119 body('displayName')
120 .optional()
121 .custom(isUserDisplayNameValid),
122
123 body('channel.name')
124 .optional()
125 .custom(isVideoChannelUsernameValid),
126 body('channel.displayName')
127 .optional()
128 .custom(isVideoChannelDisplayNameValid),
129
130 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
131 if (areValidationErrors(req, res, { omitBodyLog: true })) return
132 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
133
134 const body: UserRegister = req.body
135 if (body.channel) {
136 if (!body.channel.name || !body.channel.displayName) {
137 return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
138 }
139
140 if (body.channel.name === body.username) {
141 return res.fail({ message: 'Channel name cannot be the same as user username.' })
142 }
143
144 const existing = await ActorModel.loadLocalByName(body.channel.name)
145 if (existing) {
146 return res.fail({
147 status: HttpStatusCode.CONFLICT_409,
148 message: `Channel with name ${body.channel.name} already exists.`
149 })
150 }
151 }
152
153 return next()
154 }
155]
156
157const usersRemoveValidator = [ 110const usersRemoveValidator = [
158 param('id') 111 param('id')
159 .custom(isIdValid), 112 .custom(isIdValid),
@@ -365,45 +318,6 @@ const usersVideosValidator = [
365 } 318 }
366] 319]
367 320
368const ensureUserRegistrationAllowed = [
369 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
370 const allowedParams = {
371 body: req.body,
372 ip: req.ip
373 }
374
375 const allowedResult = await Hooks.wrapPromiseFun(
376 isSignupAllowed,
377 allowedParams,
378 'filter:api.user.signup.allowed.result'
379 )
380
381 if (allowedResult.allowed === false) {
382 return res.fail({
383 status: HttpStatusCode.FORBIDDEN_403,
384 message: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.'
385 })
386 }
387
388 return next()
389 }
390]
391
392const ensureUserRegistrationAllowedForIP = [
393 (req: express.Request, res: express.Response, next: express.NextFunction) => {
394 const allowed = isSignupAllowedForCurrentIP(req.ip)
395
396 if (allowed === false) {
397 return res.fail({
398 status: HttpStatusCode.FORBIDDEN_403,
399 message: 'You are not on a network authorized for registration.'
400 })
401 }
402
403 return next()
404 }
405]
406
407const usersAskResetPasswordValidator = [ 321const usersAskResetPasswordValidator = [
408 body('email') 322 body('email')
409 .isEmail(), 323 .isEmail(),
@@ -455,58 +369,6 @@ const usersResetPasswordValidator = [
455 } 369 }
456] 370]
457 371
458const usersAskSendVerifyEmailValidator = [
459 body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
460
461 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
462 if (areValidationErrors(req, res)) return
463
464 const exists = await checkUserEmailExist(req.body.email, res, false)
465 if (!exists) {
466 logger.debug('User with email %s does not exist (asking verify email).', req.body.email)
467 // Do not leak our emails
468 return res.status(HttpStatusCode.NO_CONTENT_204).end()
469 }
470
471 if (res.locals.user.pluginAuth) {
472 return res.fail({
473 status: HttpStatusCode.CONFLICT_409,
474 message: 'Cannot ask verification email of a user that uses a plugin authentication.'
475 })
476 }
477
478 return next()
479 }
480]
481
482const usersVerifyEmailValidator = [
483 param('id')
484 .isInt().not().isEmpty().withMessage('Should have a valid id'),
485
486 body('verificationString')
487 .not().isEmpty().withMessage('Should have a valid verification string'),
488 body('isPendingEmail')
489 .optional()
490 .customSanitizer(toBooleanOrNull),
491
492 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
493 if (areValidationErrors(req, res)) return
494 if (!await checkUserIdExist(req.params.id, res)) return
495
496 const user = res.locals.user
497 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
498
499 if (redisVerificationString !== req.body.verificationString) {
500 return res.fail({
501 status: HttpStatusCode.FORBIDDEN_403,
502 message: 'Invalid verification string.'
503 })
504 }
505
506 return next()
507 }
508]
509
510const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => { 372const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => {
511 return [ 373 return [
512 body('currentPassword').optional().custom(exists), 374 body('currentPassword').optional().custom(exists),
@@ -603,21 +465,16 @@ export {
603 usersListValidator, 465 usersListValidator,
604 usersAddValidator, 466 usersAddValidator,
605 deleteMeValidator, 467 deleteMeValidator,
606 usersRegisterValidator,
607 usersBlockingValidator, 468 usersBlockingValidator,
608 usersRemoveValidator, 469 usersRemoveValidator,
609 usersUpdateValidator, 470 usersUpdateValidator,
610 usersUpdateMeValidator, 471 usersUpdateMeValidator,
611 usersVideoRatingValidator, 472 usersVideoRatingValidator,
612 usersCheckCurrentPasswordFactory, 473 usersCheckCurrentPasswordFactory,
613 ensureUserRegistrationAllowed,
614 ensureUserRegistrationAllowedForIP,
615 usersGetValidator, 474 usersGetValidator,
616 usersVideosValidator, 475 usersVideosValidator,
617 usersAskResetPasswordValidator, 476 usersAskResetPasswordValidator,
618 usersResetPasswordValidator, 477 usersResetPasswordValidator,
619 usersAskSendVerifyEmailValidator,
620 usersVerifyEmailValidator,
621 userAutocompleteValidator, 478 userAutocompleteValidator,
622 ensureAuthUserOwnsAccountValidator, 479 ensureAuthUserOwnsAccountValidator,
623 ensureCanModerateUser, 480 ensureCanModerateUser,