aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
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/controllers/api
parentbc48e33b80f357767b98c1d310b04bdda24c6d46 (diff)
downloadPeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.gz
PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.zst
PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.zip
Implement signup approval in server
Diffstat (limited to 'server/controllers/api')
-rw-r--r--server/controllers/api/config.ts1
-rw-r--r--server/controllers/api/users/email-verification.ts72
-rw-r--r--server/controllers/api/users/index.ts99
-rw-r--r--server/controllers/api/users/registrations.ts236
4 files changed, 316 insertions, 92 deletions
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index f0fb43071..86434f382 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -193,6 +193,7 @@ function customConfig (): CustomConfig {
193 signup: { 193 signup: {
194 enabled: CONFIG.SIGNUP.ENABLED, 194 enabled: CONFIG.SIGNUP.ENABLED,
195 limit: CONFIG.SIGNUP.LIMIT, 195 limit: CONFIG.SIGNUP.LIMIT,
196 requiresApproval: CONFIG.SIGNUP.REQUIRES_APPROVAL,
196 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION, 197 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION,
197 minimumAge: CONFIG.SIGNUP.MINIMUM_AGE 198 minimumAge: CONFIG.SIGNUP.MINIMUM_AGE
198 }, 199 },
diff --git a/server/controllers/api/users/email-verification.ts b/server/controllers/api/users/email-verification.ts
new file mode 100644
index 000000000..230aaa9af
--- /dev/null
+++ b/server/controllers/api/users/email-verification.ts
@@ -0,0 +1,72 @@
1import express from 'express'
2import { HttpStatusCode } from '@shared/models'
3import { CONFIG } from '../../../initializers/config'
4import { sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user'
5import { asyncMiddleware, buildRateLimiter } from '../../../middlewares'
6import {
7 registrationVerifyEmailValidator,
8 usersAskSendVerifyEmailValidator,
9 usersVerifyEmailValidator
10} from '../../../middlewares/validators'
11
12const askSendEmailLimiter = buildRateLimiter({
13 windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
14 max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
15})
16
17const emailVerificationRouter = express.Router()
18
19emailVerificationRouter.post([ '/ask-send-verify-email', '/registrations/ask-send-verify-email' ],
20 askSendEmailLimiter,
21 asyncMiddleware(usersAskSendVerifyEmailValidator),
22 asyncMiddleware(reSendVerifyUserEmail)
23)
24
25emailVerificationRouter.post('/:id/verify-email',
26 asyncMiddleware(usersVerifyEmailValidator),
27 asyncMiddleware(verifyUserEmail)
28)
29
30emailVerificationRouter.post('/registrations/:registrationId/verify-email',
31 asyncMiddleware(registrationVerifyEmailValidator),
32 asyncMiddleware(verifyRegistrationEmail)
33)
34
35// ---------------------------------------------------------------------------
36
37export {
38 emailVerificationRouter
39}
40
41async function reSendVerifyUserEmail (req: express.Request, res: express.Response) {
42 const user = res.locals.user
43 const registration = res.locals.userRegistration
44
45 if (user) await sendVerifyUserEmail(user)
46 else if (registration) await sendVerifyRegistrationEmail(registration)
47
48 return res.status(HttpStatusCode.NO_CONTENT_204).end()
49}
50
51async function verifyUserEmail (req: express.Request, res: express.Response) {
52 const user = res.locals.user
53 user.emailVerified = true
54
55 if (req.body.isPendingEmail === true) {
56 user.email = user.pendingEmail
57 user.pendingEmail = null
58 }
59
60 await user.save()
61
62 return res.status(HttpStatusCode.NO_CONTENT_204).end()
63}
64
65async function verifyRegistrationEmail (req: express.Request, res: express.Response) {
66 const registration = res.locals.userRegistration
67 registration.emailVerified = true
68
69 await registration.save()
70
71 return res.status(HttpStatusCode.NO_CONTENT_204).end()
72}
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index a8677a1d3..5a5a12e82 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -4,26 +4,21 @@ import { Hooks } from '@server/lib/plugins/hooks'
4import { OAuthTokenModel } from '@server/models/oauth/oauth-token' 4import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
5import { MUserAccountDefault } from '@server/types/models' 5import { MUserAccountDefault } from '@server/types/models'
6import { pick } from '@shared/core-utils' 6import { pick } from '@shared/core-utils'
7import { HttpStatusCode, UserCreate, UserCreateResult, UserRegister, UserRight, UserUpdate } from '@shared/models' 7import { HttpStatusCode, UserCreate, UserCreateResult, UserRight, UserUpdate } from '@shared/models'
8import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 8import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
10import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' 10import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
11import { CONFIG } from '../../../initializers/config'
12import { WEBSERVER } from '../../../initializers/constants' 11import { WEBSERVER } from '../../../initializers/constants'
13import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
14import { Emailer } from '../../../lib/emailer' 13import { Emailer } from '../../../lib/emailer'
15import { Notifier } from '../../../lib/notifier'
16import { Redis } from '../../../lib/redis' 14import { Redis } from '../../../lib/redis'
17import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' 15import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
18import { 16import {
19 adminUsersSortValidator, 17 adminUsersSortValidator,
20 asyncMiddleware, 18 asyncMiddleware,
21 asyncRetryTransactionMiddleware, 19 asyncRetryTransactionMiddleware,
22 authenticate, 20 authenticate,
23 buildRateLimiter,
24 ensureUserHasRight, 21 ensureUserHasRight,
25 ensureUserRegistrationAllowed,
26 ensureUserRegistrationAllowedForIP,
27 paginationValidator, 22 paginationValidator,
28 setDefaultPagination, 23 setDefaultPagination,
29 setDefaultSort, 24 setDefaultSort,
@@ -31,19 +26,17 @@ import {
31 usersAddValidator, 26 usersAddValidator,
32 usersGetValidator, 27 usersGetValidator,
33 usersListValidator, 28 usersListValidator,
34 usersRegisterValidator,
35 usersRemoveValidator, 29 usersRemoveValidator,
36 usersUpdateValidator 30 usersUpdateValidator
37} from '../../../middlewares' 31} from '../../../middlewares'
38import { 32import {
39 ensureCanModerateUser, 33 ensureCanModerateUser,
40 usersAskResetPasswordValidator, 34 usersAskResetPasswordValidator,
41 usersAskSendVerifyEmailValidator,
42 usersBlockingValidator, 35 usersBlockingValidator,
43 usersResetPasswordValidator, 36 usersResetPasswordValidator
44 usersVerifyEmailValidator
45} from '../../../middlewares/validators' 37} from '../../../middlewares/validators'
46import { UserModel } from '../../../models/user/user' 38import { UserModel } from '../../../models/user/user'
39import { emailVerificationRouter } from './email-verification'
47import { meRouter } from './me' 40import { meRouter } from './me'
48import { myAbusesRouter } from './my-abuses' 41import { myAbusesRouter } from './my-abuses'
49import { myBlocklistRouter } from './my-blocklist' 42import { myBlocklistRouter } from './my-blocklist'
@@ -51,22 +44,14 @@ import { myVideosHistoryRouter } from './my-history'
51import { myNotificationsRouter } from './my-notifications' 44import { myNotificationsRouter } from './my-notifications'
52import { mySubscriptionsRouter } from './my-subscriptions' 45import { mySubscriptionsRouter } from './my-subscriptions'
53import { myVideoPlaylistsRouter } from './my-video-playlists' 46import { myVideoPlaylistsRouter } from './my-video-playlists'
47import { registrationsRouter } from './registrations'
54import { twoFactorRouter } from './two-factor' 48import { twoFactorRouter } from './two-factor'
55 49
56const auditLogger = auditLoggerFactory('users') 50const auditLogger = auditLoggerFactory('users')
57 51
58const signupRateLimiter = buildRateLimiter({
59 windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
60 max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
61 skipFailedRequests: true
62})
63
64const askSendEmailLimiter = buildRateLimiter({
65 windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
66 max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
67})
68
69const usersRouter = express.Router() 52const usersRouter = express.Router()
53usersRouter.use('/', emailVerificationRouter)
54usersRouter.use('/', registrationsRouter)
70usersRouter.use('/', twoFactorRouter) 55usersRouter.use('/', twoFactorRouter)
71usersRouter.use('/', tokensRouter) 56usersRouter.use('/', tokensRouter)
72usersRouter.use('/', myNotificationsRouter) 57usersRouter.use('/', myNotificationsRouter)
@@ -122,14 +107,6 @@ usersRouter.post('/',
122 asyncRetryTransactionMiddleware(createUser) 107 asyncRetryTransactionMiddleware(createUser)
123) 108)
124 109
125usersRouter.post('/register',
126 signupRateLimiter,
127 asyncMiddleware(ensureUserRegistrationAllowed),
128 ensureUserRegistrationAllowedForIP,
129 asyncMiddleware(usersRegisterValidator),
130 asyncRetryTransactionMiddleware(registerUser)
131)
132
133usersRouter.put('/:id', 110usersRouter.put('/:id',
134 authenticate, 111 authenticate,
135 ensureUserHasRight(UserRight.MANAGE_USERS), 112 ensureUserHasRight(UserRight.MANAGE_USERS),
@@ -156,17 +133,6 @@ usersRouter.post('/:id/reset-password',
156 asyncMiddleware(resetUserPassword) 133 asyncMiddleware(resetUserPassword)
157) 134)
158 135
159usersRouter.post('/ask-send-verify-email',
160 askSendEmailLimiter,
161 asyncMiddleware(usersAskSendVerifyEmailValidator),
162 asyncMiddleware(reSendVerifyUserEmail)
163)
164
165usersRouter.post('/:id/verify-email',
166 asyncMiddleware(usersVerifyEmailValidator),
167 asyncMiddleware(verifyUserEmail)
168)
169
170// --------------------------------------------------------------------------- 136// ---------------------------------------------------------------------------
171 137
172export { 138export {
@@ -218,35 +184,6 @@ async function createUser (req: express.Request, res: express.Response) {
218 }) 184 })
219} 185}
220 186
221async function registerUser (req: express.Request, res: express.Response) {
222 const body: UserRegister = req.body
223
224 const userToCreate = buildUser({
225 ...pick(body, [ 'username', 'password', 'email' ]),
226
227 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
228 })
229
230 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
231 userToCreate,
232 userDisplayName: body.displayName || undefined,
233 channelNames: body.channel
234 })
235
236 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
237 logger.info('User %s with its channel and account registered.', body.username)
238
239 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
240 await sendVerifyUserEmail(user)
241 }
242
243 Notifier.Instance.notifyOnNewUserRegistration(user)
244
245 Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel, req, res })
246
247 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
248}
249
250async function unblockUser (req: express.Request, res: express.Response) { 187async function unblockUser (req: express.Request, res: express.Response) {
251 const user = res.locals.user 188 const user = res.locals.user
252 189
@@ -360,28 +297,6 @@ async function resetUserPassword (req: express.Request, res: express.Response) {
360 return res.status(HttpStatusCode.NO_CONTENT_204).end() 297 return res.status(HttpStatusCode.NO_CONTENT_204).end()
361} 298}
362 299
363async function reSendVerifyUserEmail (req: express.Request, res: express.Response) {
364 const user = res.locals.user
365
366 await sendVerifyUserEmail(user)
367
368 return res.status(HttpStatusCode.NO_CONTENT_204).end()
369}
370
371async function verifyUserEmail (req: express.Request, res: express.Response) {
372 const user = res.locals.user
373 user.emailVerified = true
374
375 if (req.body.isPendingEmail === true) {
376 user.email = user.pendingEmail
377 user.pendingEmail = null
378 }
379
380 await user.save()
381
382 return res.status(HttpStatusCode.NO_CONTENT_204).end()
383}
384
385async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { 300async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
386 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 301 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
387 302
diff --git a/server/controllers/api/users/registrations.ts b/server/controllers/api/users/registrations.ts
new file mode 100644
index 000000000..3d4e0aa18
--- /dev/null
+++ b/server/controllers/api/users/registrations.ts
@@ -0,0 +1,236 @@
1import express from 'express'
2import { Emailer } from '@server/lib/emailer'
3import { Hooks } from '@server/lib/plugins/hooks'
4import { UserRegistrationModel } from '@server/models/user/user-registration'
5import { pick } from '@shared/core-utils'
6import { HttpStatusCode, UserRegister, UserRegistrationRequest, UserRegistrationState, UserRight } from '@shared/models'
7import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger'
8import { logger } from '../../../helpers/logger'
9import { CONFIG } from '../../../initializers/config'
10import { Notifier } from '../../../lib/notifier'
11import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user'
12import {
13 acceptOrRejectRegistrationValidator,
14 asyncMiddleware,
15 asyncRetryTransactionMiddleware,
16 authenticate,
17 buildRateLimiter,
18 ensureUserHasRight,
19 ensureUserRegistrationAllowedFactory,
20 ensureUserRegistrationAllowedForIP,
21 getRegistrationValidator,
22 listRegistrationsValidator,
23 paginationValidator,
24 setDefaultPagination,
25 setDefaultSort,
26 userRegistrationsSortValidator,
27 usersDirectRegistrationValidator,
28 usersRequestRegistrationValidator
29} from '../../../middlewares'
30
31const auditLogger = auditLoggerFactory('users')
32
33const registrationRateLimiter = buildRateLimiter({
34 windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
35 max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
36 skipFailedRequests: true
37})
38
39const registrationsRouter = express.Router()
40
41registrationsRouter.post('/registrations/request',
42 registrationRateLimiter,
43 asyncMiddleware(ensureUserRegistrationAllowedFactory('request-registration')),
44 ensureUserRegistrationAllowedForIP,
45 asyncMiddleware(usersRequestRegistrationValidator),
46 asyncRetryTransactionMiddleware(requestRegistration)
47)
48
49registrationsRouter.post('/registrations/:registrationId/accept',
50 authenticate,
51 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
52 asyncMiddleware(acceptOrRejectRegistrationValidator),
53 asyncRetryTransactionMiddleware(acceptRegistration)
54)
55registrationsRouter.post('/registrations/:registrationId/reject',
56 authenticate,
57 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
58 asyncMiddleware(acceptOrRejectRegistrationValidator),
59 asyncRetryTransactionMiddleware(rejectRegistration)
60)
61
62registrationsRouter.delete('/registrations/:registrationId',
63 authenticate,
64 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
65 asyncMiddleware(getRegistrationValidator),
66 asyncRetryTransactionMiddleware(deleteRegistration)
67)
68
69registrationsRouter.get('/registrations',
70 authenticate,
71 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
72 paginationValidator,
73 userRegistrationsSortValidator,
74 setDefaultSort,
75 setDefaultPagination,
76 listRegistrationsValidator,
77 asyncMiddleware(listRegistrations)
78)
79
80registrationsRouter.post('/register',
81 registrationRateLimiter,
82 asyncMiddleware(ensureUserRegistrationAllowedFactory('direct-registration')),
83 ensureUserRegistrationAllowedForIP,
84 asyncMiddleware(usersDirectRegistrationValidator),
85 asyncRetryTransactionMiddleware(registerUser)
86)
87
88// ---------------------------------------------------------------------------
89
90export {
91 registrationsRouter
92}
93
94// ---------------------------------------------------------------------------
95
96async function requestRegistration (req: express.Request, res: express.Response) {
97 const body: UserRegistrationRequest = req.body
98
99 const registration = new UserRegistrationModel({
100 ...pick(body, [ 'username', 'password', 'email', 'registrationReason' ]),
101
102 accountDisplayName: body.displayName,
103 channelDisplayName: body.channel?.displayName,
104 channelHandle: body.channel?.name,
105
106 state: UserRegistrationState.PENDING,
107
108 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
109 })
110
111 await registration.save()
112
113 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
114 await sendVerifyRegistrationEmail(registration)
115 }
116
117 Notifier.Instance.notifyOnNewRegistrationRequest(registration)
118
119 Hooks.runAction('action:api.user.requested-registration', { body, registration, req, res })
120
121 return res.json(registration.toFormattedJSON())
122}
123
124// ---------------------------------------------------------------------------
125
126async function acceptRegistration (req: express.Request, res: express.Response) {
127 const registration = res.locals.userRegistration
128
129 const userToCreate = buildUser({
130 username: registration.username,
131 password: registration.password,
132 email: registration.email,
133 emailVerified: registration.emailVerified
134 })
135 // We already encrypted password in registration model
136 userToCreate.skipPasswordEncryption = true
137
138 // TODO: handle conflicts if someone else created a channel handle/user handle/user email between registration and approval
139
140 const { user } = await createUserAccountAndChannelAndPlaylist({
141 userToCreate,
142 userDisplayName: registration.accountDisplayName,
143 channelNames: registration.channelHandle && registration.channelDisplayName
144 ? {
145 name: registration.channelHandle,
146 displayName: registration.channelDisplayName
147 }
148 : undefined
149 })
150
151 registration.userId = user.id
152 registration.state = UserRegistrationState.ACCEPTED
153 registration.moderationResponse = req.body.moderationResponse
154
155 await registration.save()
156
157 logger.info('Registration of %s accepted', registration.username)
158
159 Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
160
161 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
162}
163
164async function rejectRegistration (req: express.Request, res: express.Response) {
165 const registration = res.locals.userRegistration
166
167 registration.state = UserRegistrationState.REJECTED
168 registration.moderationResponse = req.body.moderationResponse
169
170 await registration.save()
171
172 Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
173
174 logger.info('Registration of %s rejected', registration.username)
175
176 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
177}
178
179// ---------------------------------------------------------------------------
180
181async function deleteRegistration (req: express.Request, res: express.Response) {
182 const registration = res.locals.userRegistration
183
184 await registration.destroy()
185
186 logger.info('Registration of %s deleted', registration.username)
187
188 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
189}
190
191// ---------------------------------------------------------------------------
192
193async function listRegistrations (req: express.Request, res: express.Response) {
194 const resultList = await UserRegistrationModel.listForApi({
195 start: req.query.start,
196 count: req.query.count,
197 sort: req.query.sort,
198 search: req.query.search
199 })
200
201 return res.json({
202 total: resultList.total,
203 data: resultList.data.map(d => d.toFormattedJSON())
204 })
205}
206
207// ---------------------------------------------------------------------------
208
209async function registerUser (req: express.Request, res: express.Response) {
210 const body: UserRegister = req.body
211
212 const userToCreate = buildUser({
213 ...pick(body, [ 'username', 'password', 'email' ]),
214
215 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
216 })
217
218 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
219 userToCreate,
220 userDisplayName: body.displayName || undefined,
221 channelNames: body.channel
222 })
223
224 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
225 logger.info('User %s with its channel and account registered.', body.username)
226
227 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
228 await sendVerifyUserEmail(user)
229 }
230
231 Notifier.Instance.notifyOnNewDirectRegistration(user)
232
233 Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel, req, res })
234
235 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
236}