aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/users
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/users')
-rw-r--r--server/controllers/api/users/email-verification.ts72
-rw-r--r--server/controllers/api/users/index.ts319
-rw-r--r--server/controllers/api/users/me.ts277
-rw-r--r--server/controllers/api/users/my-abuses.ts48
-rw-r--r--server/controllers/api/users/my-blocklist.ts149
-rw-r--r--server/controllers/api/users/my-history.ts75
-rw-r--r--server/controllers/api/users/my-notifications.ts116
-rw-r--r--server/controllers/api/users/my-subscriptions.ts193
-rw-r--r--server/controllers/api/users/my-video-playlists.ts51
-rw-r--r--server/controllers/api/users/registrations.ts249
-rw-r--r--server/controllers/api/users/token.ts131
-rw-r--r--server/controllers/api/users/two-factor.ts95
12 files changed, 0 insertions, 1775 deletions
diff --git a/server/controllers/api/users/email-verification.ts b/server/controllers/api/users/email-verification.ts
deleted file mode 100644
index 230aaa9af..000000000
--- a/server/controllers/api/users/email-verification.ts
+++ /dev/null
@@ -1,72 +0,0 @@
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
deleted file mode 100644
index 5eac6fd0f..000000000
--- a/server/controllers/api/users/index.ts
+++ /dev/null
@@ -1,319 +0,0 @@
1import express from 'express'
2import { tokensRouter } from '@server/controllers/api/users/token'
3import { Hooks } from '@server/lib/plugins/hooks'
4import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
5import { MUserAccountDefault } from '@server/types/models'
6import { pick } from '@shared/core-utils'
7import { HttpStatusCode, UserCreate, UserCreateResult, UserRight, UserUpdate } from '@shared/models'
8import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
9import { logger } from '../../../helpers/logger'
10import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
11import { WEBSERVER } from '../../../initializers/constants'
12import { sequelizeTypescript } from '../../../initializers/database'
13import { Emailer } from '../../../lib/emailer'
14import { Redis } from '../../../lib/redis'
15import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
16import {
17 adminUsersSortValidator,
18 apiRateLimiter,
19 asyncMiddleware,
20 asyncRetryTransactionMiddleware,
21 authenticate,
22 ensureUserHasRight,
23 paginationValidator,
24 setDefaultPagination,
25 setDefaultSort,
26 userAutocompleteValidator,
27 usersAddValidator,
28 usersGetValidator,
29 usersListValidator,
30 usersRemoveValidator,
31 usersUpdateValidator
32} from '../../../middlewares'
33import {
34 ensureCanModerateUser,
35 usersAskResetPasswordValidator,
36 usersBlockingValidator,
37 usersResetPasswordValidator
38} from '../../../middlewares/validators'
39import { UserModel } from '../../../models/user/user'
40import { emailVerificationRouter } from './email-verification'
41import { meRouter } from './me'
42import { myAbusesRouter } from './my-abuses'
43import { myBlocklistRouter } from './my-blocklist'
44import { myVideosHistoryRouter } from './my-history'
45import { myNotificationsRouter } from './my-notifications'
46import { mySubscriptionsRouter } from './my-subscriptions'
47import { myVideoPlaylistsRouter } from './my-video-playlists'
48import { registrationsRouter } from './registrations'
49import { twoFactorRouter } from './two-factor'
50
51const auditLogger = auditLoggerFactory('users')
52
53const usersRouter = express.Router()
54
55usersRouter.use(apiRateLimiter)
56
57usersRouter.use('/', emailVerificationRouter)
58usersRouter.use('/', registrationsRouter)
59usersRouter.use('/', twoFactorRouter)
60usersRouter.use('/', tokensRouter)
61usersRouter.use('/', myNotificationsRouter)
62usersRouter.use('/', mySubscriptionsRouter)
63usersRouter.use('/', myBlocklistRouter)
64usersRouter.use('/', myVideosHistoryRouter)
65usersRouter.use('/', myVideoPlaylistsRouter)
66usersRouter.use('/', myAbusesRouter)
67usersRouter.use('/', meRouter)
68
69usersRouter.get('/autocomplete',
70 userAutocompleteValidator,
71 asyncMiddleware(autocompleteUsers)
72)
73
74usersRouter.get('/',
75 authenticate,
76 ensureUserHasRight(UserRight.MANAGE_USERS),
77 paginationValidator,
78 adminUsersSortValidator,
79 setDefaultSort,
80 setDefaultPagination,
81 usersListValidator,
82 asyncMiddleware(listUsers)
83)
84
85usersRouter.post('/:id/block',
86 authenticate,
87 ensureUserHasRight(UserRight.MANAGE_USERS),
88 asyncMiddleware(usersBlockingValidator),
89 ensureCanModerateUser,
90 asyncMiddleware(blockUser)
91)
92usersRouter.post('/:id/unblock',
93 authenticate,
94 ensureUserHasRight(UserRight.MANAGE_USERS),
95 asyncMiddleware(usersBlockingValidator),
96 ensureCanModerateUser,
97 asyncMiddleware(unblockUser)
98)
99
100usersRouter.get('/:id',
101 authenticate,
102 ensureUserHasRight(UserRight.MANAGE_USERS),
103 asyncMiddleware(usersGetValidator),
104 getUser
105)
106
107usersRouter.post('/',
108 authenticate,
109 ensureUserHasRight(UserRight.MANAGE_USERS),
110 asyncMiddleware(usersAddValidator),
111 asyncRetryTransactionMiddleware(createUser)
112)
113
114usersRouter.put('/:id',
115 authenticate,
116 ensureUserHasRight(UserRight.MANAGE_USERS),
117 asyncMiddleware(usersUpdateValidator),
118 ensureCanModerateUser,
119 asyncMiddleware(updateUser)
120)
121
122usersRouter.delete('/:id',
123 authenticate,
124 ensureUserHasRight(UserRight.MANAGE_USERS),
125 asyncMiddleware(usersRemoveValidator),
126 ensureCanModerateUser,
127 asyncMiddleware(removeUser)
128)
129
130usersRouter.post('/ask-reset-password',
131 asyncMiddleware(usersAskResetPasswordValidator),
132 asyncMiddleware(askResetUserPassword)
133)
134
135usersRouter.post('/:id/reset-password',
136 asyncMiddleware(usersResetPasswordValidator),
137 asyncMiddleware(resetUserPassword)
138)
139
140// ---------------------------------------------------------------------------
141
142export {
143 usersRouter
144}
145
146// ---------------------------------------------------------------------------
147
148async function createUser (req: express.Request, res: express.Response) {
149 const body: UserCreate = req.body
150
151 const userToCreate = buildUser({
152 ...pick(body, [ 'username', 'password', 'email', 'role', 'videoQuota', 'videoQuotaDaily', 'adminFlags' ]),
153
154 emailVerified: null
155 })
156
157 // NB: due to the validator usersAddValidator, password==='' can only be true if we can send the mail.
158 const createPassword = userToCreate.password === ''
159 if (createPassword) {
160 userToCreate.password = await generateRandomString(20)
161 }
162
163 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
164 userToCreate,
165 channelNames: body.channelName && { name: body.channelName, displayName: body.channelName }
166 })
167
168 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
169 logger.info('User %s with its channel and account created.', body.username)
170
171 if (createPassword) {
172 // this will send an email for newly created users, so then can set their first password.
173 logger.info('Sending to user %s a create password email', body.username)
174 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id)
175 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
176 Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url)
177 }
178
179 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res })
180
181 return res.json({
182 user: {
183 id: user.id,
184 account: {
185 id: account.id
186 }
187 } as UserCreateResult
188 })
189}
190
191async function unblockUser (req: express.Request, res: express.Response) {
192 const user = res.locals.user
193
194 await changeUserBlock(res, user, false)
195
196 Hooks.runAction('action:api.user.unblocked', { user, req, res })
197
198 return res.status(HttpStatusCode.NO_CONTENT_204).end()
199}
200
201async function blockUser (req: express.Request, res: express.Response) {
202 const user = res.locals.user
203 const reason = req.body.reason
204
205 await changeUserBlock(res, user, true, reason)
206
207 Hooks.runAction('action:api.user.blocked', { user, req, res })
208
209 return res.status(HttpStatusCode.NO_CONTENT_204).end()
210}
211
212function getUser (req: express.Request, res: express.Response) {
213 return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
214}
215
216async function autocompleteUsers (req: express.Request, res: express.Response) {
217 const resultList = await UserModel.autoComplete(req.query.search as string)
218
219 return res.json(resultList)
220}
221
222async function listUsers (req: express.Request, res: express.Response) {
223 const resultList = await UserModel.listForAdminApi({
224 start: req.query.start,
225 count: req.query.count,
226 sort: req.query.sort,
227 search: req.query.search,
228 blocked: req.query.blocked
229 })
230
231 return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
232}
233
234async function removeUser (req: express.Request, res: express.Response) {
235 const user = res.locals.user
236
237 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
238
239 await sequelizeTypescript.transaction(async t => {
240 // Use a transaction to avoid inconsistencies with hooks (account/channel deletion & federation)
241 await user.destroy({ transaction: t })
242 })
243
244 Hooks.runAction('action:api.user.deleted', { user, req, res })
245
246 return res.status(HttpStatusCode.NO_CONTENT_204).end()
247}
248
249async function updateUser (req: express.Request, res: express.Response) {
250 const body: UserUpdate = req.body
251 const userToUpdate = res.locals.user
252 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
253 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
254
255 const keysToUpdate: (keyof UserUpdate)[] = [
256 'password',
257 'email',
258 'emailVerified',
259 'videoQuota',
260 'videoQuotaDaily',
261 'role',
262 'adminFlags',
263 'pluginAuth'
264 ]
265
266 for (const key of keysToUpdate) {
267 if (body[key] !== undefined) userToUpdate.set(key, body[key])
268 }
269
270 const user = await userToUpdate.save()
271
272 // Destroy user token to refresh rights
273 if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id)
274
275 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
276
277 Hooks.runAction('action:api.user.updated', { user, req, res })
278
279 // Don't need to send this update to followers, these attributes are not federated
280
281 return res.status(HttpStatusCode.NO_CONTENT_204).end()
282}
283
284async function askResetUserPassword (req: express.Request, res: express.Response) {
285 const user = res.locals.user
286
287 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
288 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
289 Emailer.Instance.addPasswordResetEmailJob(user.username, user.email, url)
290
291 return res.status(HttpStatusCode.NO_CONTENT_204).end()
292}
293
294async function resetUserPassword (req: express.Request, res: express.Response) {
295 const user = res.locals.user
296 user.password = req.body.password
297
298 await user.save()
299 await Redis.Instance.removePasswordVerificationString(user.id)
300
301 return res.status(HttpStatusCode.NO_CONTENT_204).end()
302}
303
304async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
305 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
306
307 user.blocked = block
308 user.blockedReason = reason || null
309
310 await sequelizeTypescript.transaction(async t => {
311 await OAuthTokenModel.deleteUserToken(user.id, t)
312
313 await user.save({ transaction: t })
314 })
315
316 Emailer.Instance.addUserBlockJob(user, block, reason)
317
318 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
319}
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
deleted file mode 100644
index 26811136e..000000000
--- a/server/controllers/api/users/me.ts
+++ /dev/null
@@ -1,277 +0,0 @@
1import 'multer'
2import express from 'express'
3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger'
4import { Hooks } from '@server/lib/plugins/hooks'
5import { pick } from '@shared/core-utils'
6import { ActorImageType, HttpStatusCode, UserUpdateMe, UserVideoQuota, UserVideoRate as FormattedUserVideoRate } from '@shared/models'
7import { AttributesOnly } from '@shared/typescript-utils'
8import { createReqFiles } from '../../../helpers/express-utils'
9import { getFormattedObjects } from '../../../helpers/utils'
10import { CONFIG } from '../../../initializers/config'
11import { MIMETYPES } from '../../../initializers/constants'
12import { sequelizeTypescript } from '../../../initializers/database'
13import { sendUpdateActor } from '../../../lib/activitypub/send'
14import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../../lib/local-actor'
15import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user'
16import {
17 asyncMiddleware,
18 asyncRetryTransactionMiddleware,
19 authenticate,
20 paginationValidator,
21 setDefaultPagination,
22 setDefaultSort,
23 setDefaultVideosSort,
24 usersUpdateMeValidator,
25 usersVideoRatingValidator
26} from '../../../middlewares'
27import {
28 deleteMeValidator,
29 getMyVideoImportsValidator,
30 usersVideosValidator,
31 videoImportsSortValidator,
32 videosSortValidator
33} from '../../../middlewares/validators'
34import { updateAvatarValidator } from '../../../middlewares/validators/actor-image'
35import { AccountModel } from '../../../models/account/account'
36import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
37import { UserModel } from '../../../models/user/user'
38import { VideoModel } from '../../../models/video/video'
39import { VideoImportModel } from '../../../models/video/video-import'
40
41const auditLogger = auditLoggerFactory('users')
42
43const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT)
44
45const meRouter = express.Router()
46
47meRouter.get('/me',
48 authenticate,
49 asyncMiddleware(getUserInformation)
50)
51meRouter.delete('/me',
52 authenticate,
53 deleteMeValidator,
54 asyncMiddleware(deleteMe)
55)
56
57meRouter.get('/me/video-quota-used',
58 authenticate,
59 asyncMiddleware(getUserVideoQuotaUsed)
60)
61
62meRouter.get('/me/videos/imports',
63 authenticate,
64 paginationValidator,
65 videoImportsSortValidator,
66 setDefaultSort,
67 setDefaultPagination,
68 getMyVideoImportsValidator,
69 asyncMiddleware(getUserVideoImports)
70)
71
72meRouter.get('/me/videos',
73 authenticate,
74 paginationValidator,
75 videosSortValidator,
76 setDefaultVideosSort,
77 setDefaultPagination,
78 asyncMiddleware(usersVideosValidator),
79 asyncMiddleware(getUserVideos)
80)
81
82meRouter.get('/me/videos/:videoId/rating',
83 authenticate,
84 asyncMiddleware(usersVideoRatingValidator),
85 asyncMiddleware(getUserVideoRating)
86)
87
88meRouter.put('/me',
89 authenticate,
90 asyncMiddleware(usersUpdateMeValidator),
91 asyncRetryTransactionMiddleware(updateMe)
92)
93
94meRouter.post('/me/avatar/pick',
95 authenticate,
96 reqAvatarFile,
97 updateAvatarValidator,
98 asyncRetryTransactionMiddleware(updateMyAvatar)
99)
100
101meRouter.delete('/me/avatar',
102 authenticate,
103 asyncRetryTransactionMiddleware(deleteMyAvatar)
104)
105
106// ---------------------------------------------------------------------------
107
108export {
109 meRouter
110}
111
112// ---------------------------------------------------------------------------
113
114async function getUserVideos (req: express.Request, res: express.Response) {
115 const user = res.locals.oauth.token.User
116
117 const apiOptions = await Hooks.wrapObject({
118 accountId: user.Account.id,
119 start: req.query.start,
120 count: req.query.count,
121 sort: req.query.sort,
122 search: req.query.search,
123 channelId: res.locals.videoChannel?.id,
124 isLive: req.query.isLive
125 }, 'filter:api.user.me.videos.list.params')
126
127 const resultList = await Hooks.wrapPromiseFun(
128 VideoModel.listUserVideosForApi,
129 apiOptions,
130 'filter:api.user.me.videos.list.result'
131 )
132
133 const additionalAttributes = {
134 waitTranscoding: true,
135 state: true,
136 scheduledUpdate: true,
137 blacklistInfo: true
138 }
139 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
140}
141
142async function getUserVideoImports (req: express.Request, res: express.Response) {
143 const user = res.locals.oauth.token.User
144 const resultList = await VideoImportModel.listUserVideoImportsForApi({
145 userId: user.id,
146
147 ...pick(req.query, [ 'targetUrl', 'start', 'count', 'sort', 'search', 'videoChannelSyncId' ])
148 })
149
150 return res.json(getFormattedObjects(resultList.data, resultList.total))
151}
152
153async function getUserInformation (req: express.Request, res: express.Response) {
154 // We did not load channels in res.locals.user
155 const user = await UserModel.loadForMeAPI(res.locals.oauth.token.user.id)
156
157 return res.json(user.toMeFormattedJSON())
158}
159
160async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
161 const user = res.locals.oauth.token.user
162 const videoQuotaUsed = await getOriginalVideoFileTotalFromUser(user)
163 const videoQuotaUsedDaily = await getOriginalVideoFileTotalDailyFromUser(user)
164
165 const data: UserVideoQuota = {
166 videoQuotaUsed,
167 videoQuotaUsedDaily
168 }
169 return res.json(data)
170}
171
172async function getUserVideoRating (req: express.Request, res: express.Response) {
173 const videoId = res.locals.videoId.id
174 const accountId = +res.locals.oauth.token.User.Account.id
175
176 const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
177 const rating = ratingObj ? ratingObj.type : 'none'
178
179 const json: FormattedUserVideoRate = {
180 videoId,
181 rating
182 }
183 return res.json(json)
184}
185
186async function deleteMe (req: express.Request, res: express.Response) {
187 const user = await UserModel.loadByIdWithChannels(res.locals.oauth.token.User.id)
188
189 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
190
191 await user.destroy()
192
193 return res.status(HttpStatusCode.NO_CONTENT_204).end()
194}
195
196async function updateMe (req: express.Request, res: express.Response) {
197 const body: UserUpdateMe = req.body
198 let sendVerificationEmail = false
199
200 const user = res.locals.oauth.token.user
201
202 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
203 'password',
204 'nsfwPolicy',
205 'p2pEnabled',
206 'autoPlayVideo',
207 'autoPlayNextVideo',
208 'autoPlayNextVideoPlaylist',
209 'videosHistoryEnabled',
210 'videoLanguages',
211 'theme',
212 'noInstanceConfigWarningModal',
213 'noAccountSetupWarningModal',
214 'noWelcomeModal',
215 'emailPublic',
216 'p2pEnabled'
217 ]
218
219 for (const key of keysToUpdate) {
220 if (body[key] !== undefined) user.set(key, body[key])
221 }
222
223 if (body.email !== undefined) {
224 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
225 user.pendingEmail = body.email
226 sendVerificationEmail = true
227 } else {
228 user.email = body.email
229 }
230 }
231
232 await sequelizeTypescript.transaction(async t => {
233 await user.save({ transaction: t })
234
235 if (body.displayName === undefined && body.description === undefined) return
236
237 const userAccount = await AccountModel.load(user.Account.id, t)
238
239 if (body.displayName !== undefined) userAccount.name = body.displayName
240 if (body.description !== undefined) userAccount.description = body.description
241 await userAccount.save({ transaction: t })
242
243 await sendUpdateActor(userAccount, t)
244 })
245
246 if (sendVerificationEmail === true) {
247 await sendVerifyUserEmail(user, true)
248 }
249
250 return res.status(HttpStatusCode.NO_CONTENT_204).end()
251}
252
253async function updateMyAvatar (req: express.Request, res: express.Response) {
254 const avatarPhysicalFile = req.files['avatarfile'][0]
255 const user = res.locals.oauth.token.user
256
257 const userAccount = await AccountModel.load(user.Account.id)
258
259 const avatars = await updateLocalActorImageFiles(
260 userAccount,
261 avatarPhysicalFile,
262 ActorImageType.AVATAR
263 )
264
265 return res.json({
266 avatars: avatars.map(avatar => avatar.toFormattedJSON())
267 })
268}
269
270async function deleteMyAvatar (req: express.Request, res: express.Response) {
271 const user = res.locals.oauth.token.user
272
273 const userAccount = await AccountModel.load(user.Account.id)
274 await deleteLocalActorImageFile(userAccount, ActorImageType.AVATAR)
275
276 return res.json({ avatars: [] })
277}
diff --git a/server/controllers/api/users/my-abuses.ts b/server/controllers/api/users/my-abuses.ts
deleted file mode 100644
index 103c3d332..000000000
--- a/server/controllers/api/users/my-abuses.ts
+++ /dev/null
@@ -1,48 +0,0 @@
1import express from 'express'
2import { AbuseModel } from '@server/models/abuse/abuse'
3import {
4 abuseListForUserValidator,
5 abusesSortValidator,
6 asyncMiddleware,
7 authenticate,
8 paginationValidator,
9 setDefaultPagination,
10 setDefaultSort
11} from '../../../middlewares'
12
13const myAbusesRouter = express.Router()
14
15myAbusesRouter.get('/me/abuses',
16 authenticate,
17 paginationValidator,
18 abusesSortValidator,
19 setDefaultSort,
20 setDefaultPagination,
21 abuseListForUserValidator,
22 asyncMiddleware(listMyAbuses)
23)
24
25// ---------------------------------------------------------------------------
26
27export {
28 myAbusesRouter
29}
30
31// ---------------------------------------------------------------------------
32
33async function listMyAbuses (req: express.Request, res: express.Response) {
34 const resultList = await AbuseModel.listForUserApi({
35 start: req.query.start,
36 count: req.query.count,
37 sort: req.query.sort,
38 id: req.query.id,
39 search: req.query.search,
40 state: req.query.state,
41 user: res.locals.oauth.token.User
42 })
43
44 return res.json({
45 total: resultList.total,
46 data: resultList.data.map(d => d.toFormattedUserJSON())
47 })
48}
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
deleted file mode 100644
index 0b56645cf..000000000
--- a/server/controllers/api/users/my-blocklist.ts
+++ /dev/null
@@ -1,149 +0,0 @@
1import 'multer'
2import express from 'express'
3import { logger } from '@server/helpers/logger'
4import { UserNotificationModel } from '@server/models/user/user-notification'
5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { getFormattedObjects } from '../../../helpers/utils'
7import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
8import {
9 asyncMiddleware,
10 asyncRetryTransactionMiddleware,
11 authenticate,
12 paginationValidator,
13 setDefaultPagination,
14 setDefaultSort,
15 unblockAccountByAccountValidator
16} from '../../../middlewares'
17import {
18 accountsBlocklistSortValidator,
19 blockAccountValidator,
20 blockServerValidator,
21 serversBlocklistSortValidator,
22 unblockServerByAccountValidator
23} from '../../../middlewares/validators'
24import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
25import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
26
27const myBlocklistRouter = express.Router()
28
29myBlocklistRouter.get('/me/blocklist/accounts',
30 authenticate,
31 paginationValidator,
32 accountsBlocklistSortValidator,
33 setDefaultSort,
34 setDefaultPagination,
35 asyncMiddleware(listBlockedAccounts)
36)
37
38myBlocklistRouter.post('/me/blocklist/accounts',
39 authenticate,
40 asyncMiddleware(blockAccountValidator),
41 asyncRetryTransactionMiddleware(blockAccount)
42)
43
44myBlocklistRouter.delete('/me/blocklist/accounts/:accountName',
45 authenticate,
46 asyncMiddleware(unblockAccountByAccountValidator),
47 asyncRetryTransactionMiddleware(unblockAccount)
48)
49
50myBlocklistRouter.get('/me/blocklist/servers',
51 authenticate,
52 paginationValidator,
53 serversBlocklistSortValidator,
54 setDefaultSort,
55 setDefaultPagination,
56 asyncMiddleware(listBlockedServers)
57)
58
59myBlocklistRouter.post('/me/blocklist/servers',
60 authenticate,
61 asyncMiddleware(blockServerValidator),
62 asyncRetryTransactionMiddleware(blockServer)
63)
64
65myBlocklistRouter.delete('/me/blocklist/servers/:host',
66 authenticate,
67 asyncMiddleware(unblockServerByAccountValidator),
68 asyncRetryTransactionMiddleware(unblockServer)
69)
70
71export {
72 myBlocklistRouter
73}
74
75// ---------------------------------------------------------------------------
76
77async function listBlockedAccounts (req: express.Request, res: express.Response) {
78 const user = res.locals.oauth.token.User
79
80 const resultList = await AccountBlocklistModel.listForApi({
81 start: req.query.start,
82 count: req.query.count,
83 sort: req.query.sort,
84 search: req.query.search,
85 accountId: user.Account.id
86 })
87
88 return res.json(getFormattedObjects(resultList.data, resultList.total))
89}
90
91async function blockAccount (req: express.Request, res: express.Response) {
92 const user = res.locals.oauth.token.User
93 const accountToBlock = res.locals.account
94
95 await addAccountInBlocklist(user.Account.id, accountToBlock.id)
96
97 UserNotificationModel.removeNotificationsOf({
98 id: accountToBlock.id,
99 type: 'account',
100 forUserId: user.id
101 }).catch(err => logger.error('Cannot remove notifications after an account mute.', { err }))
102
103 return res.status(HttpStatusCode.NO_CONTENT_204).end()
104}
105
106async function unblockAccount (req: express.Request, res: express.Response) {
107 const accountBlock = res.locals.accountBlock
108
109 await removeAccountFromBlocklist(accountBlock)
110
111 return res.status(HttpStatusCode.NO_CONTENT_204).end()
112}
113
114async function listBlockedServers (req: express.Request, res: express.Response) {
115 const user = res.locals.oauth.token.User
116
117 const resultList = await ServerBlocklistModel.listForApi({
118 start: req.query.start,
119 count: req.query.count,
120 sort: req.query.sort,
121 search: req.query.search,
122 accountId: user.Account.id
123 })
124
125 return res.json(getFormattedObjects(resultList.data, resultList.total))
126}
127
128async function blockServer (req: express.Request, res: express.Response) {
129 const user = res.locals.oauth.token.User
130 const serverToBlock = res.locals.server
131
132 await addServerInBlocklist(user.Account.id, serverToBlock.id)
133
134 UserNotificationModel.removeNotificationsOf({
135 id: serverToBlock.id,
136 type: 'server',
137 forUserId: user.id
138 }).catch(err => logger.error('Cannot remove notifications after a server mute.', { err }))
139
140 return res.status(HttpStatusCode.NO_CONTENT_204).end()
141}
142
143async function unblockServer (req: express.Request, res: express.Response) {
144 const serverBlock = res.locals.serverBlock
145
146 await removeServerFromBlocklist(serverBlock)
147
148 return res.status(HttpStatusCode.NO_CONTENT_204).end()
149}
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
deleted file mode 100644
index e6d3e86ac..000000000
--- a/server/controllers/api/users/my-history.ts
+++ /dev/null
@@ -1,75 +0,0 @@
1import { forceNumber } from '@shared/core-utils'
2import express from 'express'
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { getFormattedObjects } from '../../../helpers/utils'
5import { sequelizeTypescript } from '../../../initializers/database'
6import {
7 asyncMiddleware,
8 asyncRetryTransactionMiddleware,
9 authenticate,
10 paginationValidator,
11 setDefaultPagination,
12 userHistoryListValidator,
13 userHistoryRemoveAllValidator,
14 userHistoryRemoveElementValidator
15} from '../../../middlewares'
16import { UserVideoHistoryModel } from '../../../models/user/user-video-history'
17
18const myVideosHistoryRouter = express.Router()
19
20myVideosHistoryRouter.get('/me/history/videos',
21 authenticate,
22 paginationValidator,
23 setDefaultPagination,
24 userHistoryListValidator,
25 asyncMiddleware(listMyVideosHistory)
26)
27
28myVideosHistoryRouter.delete('/me/history/videos/:videoId',
29 authenticate,
30 userHistoryRemoveElementValidator,
31 asyncMiddleware(removeUserHistoryElement)
32)
33
34myVideosHistoryRouter.post('/me/history/videos/remove',
35 authenticate,
36 userHistoryRemoveAllValidator,
37 asyncRetryTransactionMiddleware(removeAllUserHistory)
38)
39
40// ---------------------------------------------------------------------------
41
42export {
43 myVideosHistoryRouter
44}
45
46// ---------------------------------------------------------------------------
47
48async function listMyVideosHistory (req: express.Request, res: express.Response) {
49 const user = res.locals.oauth.token.User
50
51 const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count, req.query.search)
52
53 return res.json(getFormattedObjects(resultList.data, resultList.total))
54}
55
56async function removeUserHistoryElement (req: express.Request, res: express.Response) {
57 const user = res.locals.oauth.token.User
58
59 await UserVideoHistoryModel.removeUserHistoryElement(user, forceNumber(req.params.videoId))
60
61 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
62}
63
64async function removeAllUserHistory (req: express.Request, res: express.Response) {
65 const user = res.locals.oauth.token.User
66 const beforeDate = req.body.beforeDate || null
67
68 await sequelizeTypescript.transaction(t => {
69 return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
70 })
71
72 return res.type('json')
73 .status(HttpStatusCode.NO_CONTENT_204)
74 .end()
75}
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
deleted file mode 100644
index 6014cdbbf..000000000
--- a/server/controllers/api/users/my-notifications.ts
+++ /dev/null
@@ -1,116 +0,0 @@
1import 'multer'
2import express from 'express'
3import { UserNotificationModel } from '@server/models/user/user-notification'
4import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
5import { UserNotificationSetting } from '../../../../shared/models/users'
6import {
7 asyncMiddleware,
8 asyncRetryTransactionMiddleware,
9 authenticate,
10 paginationValidator,
11 setDefaultPagination,
12 setDefaultSort,
13 userNotificationsSortValidator
14} from '../../../middlewares'
15import {
16 listUserNotificationsValidator,
17 markAsReadUserNotificationsValidator,
18 updateNotificationSettingsValidator
19} from '../../../middlewares/validators/user-notifications'
20import { UserNotificationSettingModel } from '../../../models/user/user-notification-setting'
21import { meRouter } from './me'
22import { getFormattedObjects } from '@server/helpers/utils'
23
24const myNotificationsRouter = express.Router()
25
26meRouter.put('/me/notification-settings',
27 authenticate,
28 updateNotificationSettingsValidator,
29 asyncRetryTransactionMiddleware(updateNotificationSettings)
30)
31
32myNotificationsRouter.get('/me/notifications',
33 authenticate,
34 paginationValidator,
35 userNotificationsSortValidator,
36 setDefaultSort,
37 setDefaultPagination,
38 listUserNotificationsValidator,
39 asyncMiddleware(listUserNotifications)
40)
41
42myNotificationsRouter.post('/me/notifications/read',
43 authenticate,
44 markAsReadUserNotificationsValidator,
45 asyncMiddleware(markAsReadUserNotifications)
46)
47
48myNotificationsRouter.post('/me/notifications/read-all',
49 authenticate,
50 asyncMiddleware(markAsReadAllUserNotifications)
51)
52
53export {
54 myNotificationsRouter
55}
56
57// ---------------------------------------------------------------------------
58
59async function updateNotificationSettings (req: express.Request, res: express.Response) {
60 const user = res.locals.oauth.token.User
61 const body = req.body as UserNotificationSetting
62
63 const query = {
64 where: {
65 userId: user.id
66 }
67 }
68
69 const values: UserNotificationSetting = {
70 newVideoFromSubscription: body.newVideoFromSubscription,
71 newCommentOnMyVideo: body.newCommentOnMyVideo,
72 abuseAsModerator: body.abuseAsModerator,
73 videoAutoBlacklistAsModerator: body.videoAutoBlacklistAsModerator,
74 blacklistOnMyVideo: body.blacklistOnMyVideo,
75 myVideoPublished: body.myVideoPublished,
76 myVideoImportFinished: body.myVideoImportFinished,
77 newFollow: body.newFollow,
78 newUserRegistration: body.newUserRegistration,
79 commentMention: body.commentMention,
80 newInstanceFollower: body.newInstanceFollower,
81 autoInstanceFollowing: body.autoInstanceFollowing,
82 abuseNewMessage: body.abuseNewMessage,
83 abuseStateChange: body.abuseStateChange,
84 newPeerTubeVersion: body.newPeerTubeVersion,
85 newPluginVersion: body.newPluginVersion,
86 myVideoStudioEditionFinished: body.myVideoStudioEditionFinished
87 }
88
89 await UserNotificationSettingModel.update(values, query)
90
91 return res.status(HttpStatusCode.NO_CONTENT_204).end()
92}
93
94async function listUserNotifications (req: express.Request, res: express.Response) {
95 const user = res.locals.oauth.token.User
96
97 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
98
99 return res.json(getFormattedObjects(resultList.data, resultList.total))
100}
101
102async function markAsReadUserNotifications (req: express.Request, res: express.Response) {
103 const user = res.locals.oauth.token.User
104
105 await UserNotificationModel.markAsRead(user.id, req.body.ids)
106
107 return res.status(HttpStatusCode.NO_CONTENT_204).end()
108}
109
110async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) {
111 const user = res.locals.oauth.token.User
112
113 await UserNotificationModel.markAllAsRead(user.id)
114
115 return res.status(HttpStatusCode.NO_CONTENT_204).end()
116}
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
deleted file mode 100644
index c4360f59d..000000000
--- a/server/controllers/api/users/my-subscriptions.ts
+++ /dev/null
@@ -1,193 +0,0 @@
1import 'multer'
2import express from 'express'
3import { handlesToNameAndHost } from '@server/helpers/actors'
4import { pickCommonVideoQuery } from '@server/helpers/query'
5import { sendUndoFollow } from '@server/lib/activitypub/send'
6import { Hooks } from '@server/lib/plugins/hooks'
7import { VideoChannelModel } from '@server/models/video/video-channel'
8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
9import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
10import { getFormattedObjects } from '../../../helpers/utils'
11import { sequelizeTypescript } from '../../../initializers/database'
12import { JobQueue } from '../../../lib/job-queue'
13import {
14 asyncMiddleware,
15 asyncRetryTransactionMiddleware,
16 authenticate,
17 commonVideosFiltersValidator,
18 paginationValidator,
19 setDefaultPagination,
20 setDefaultSort,
21 setDefaultVideosSort,
22 userSubscriptionAddValidator,
23 userSubscriptionGetValidator
24} from '../../../middlewares'
25import {
26 areSubscriptionsExistValidator,
27 userSubscriptionListValidator,
28 userSubscriptionsSortValidator,
29 videosSortValidator
30} from '../../../middlewares/validators'
31import { ActorFollowModel } from '../../../models/actor/actor-follow'
32import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter'
33import { VideoModel } from '../../../models/video/video'
34
35const mySubscriptionsRouter = express.Router()
36
37mySubscriptionsRouter.get('/me/subscriptions/videos',
38 authenticate,
39 paginationValidator,
40 videosSortValidator,
41 setDefaultVideosSort,
42 setDefaultPagination,
43 commonVideosFiltersValidator,
44 asyncMiddleware(getUserSubscriptionVideos)
45)
46
47mySubscriptionsRouter.get('/me/subscriptions/exist',
48 authenticate,
49 areSubscriptionsExistValidator,
50 asyncMiddleware(areSubscriptionsExist)
51)
52
53mySubscriptionsRouter.get('/me/subscriptions',
54 authenticate,
55 paginationValidator,
56 userSubscriptionsSortValidator,
57 setDefaultSort,
58 setDefaultPagination,
59 userSubscriptionListValidator,
60 asyncMiddleware(getUserSubscriptions)
61)
62
63mySubscriptionsRouter.post('/me/subscriptions',
64 authenticate,
65 userSubscriptionAddValidator,
66 addUserSubscription
67)
68
69mySubscriptionsRouter.get('/me/subscriptions/:uri',
70 authenticate,
71 userSubscriptionGetValidator,
72 asyncMiddleware(getUserSubscription)
73)
74
75mySubscriptionsRouter.delete('/me/subscriptions/:uri',
76 authenticate,
77 userSubscriptionGetValidator,
78 asyncRetryTransactionMiddleware(deleteUserSubscription)
79)
80
81// ---------------------------------------------------------------------------
82
83export {
84 mySubscriptionsRouter
85}
86
87// ---------------------------------------------------------------------------
88
89async function areSubscriptionsExist (req: express.Request, res: express.Response) {
90 const uris = req.query.uris as string[]
91 const user = res.locals.oauth.token.User
92
93 const sanitizedHandles = handlesToNameAndHost(uris)
94
95 const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, sanitizedHandles)
96
97 const existObject: { [id: string ]: boolean } = {}
98 for (const sanitizedHandle of sanitizedHandles) {
99 const obj = results.find(r => {
100 const server = r.ActorFollowing.Server
101
102 return r.ActorFollowing.preferredUsername.toLowerCase() === sanitizedHandle.name.toLowerCase() &&
103 (
104 (!server && !sanitizedHandle.host) ||
105 (server.host === sanitizedHandle.host)
106 )
107 })
108
109 existObject[sanitizedHandle.handle] = obj !== undefined
110 }
111
112 return res.json(existObject)
113}
114
115function addUserSubscription (req: express.Request, res: express.Response) {
116 const user = res.locals.oauth.token.User
117 const [ name, host ] = req.body.uri.split('@')
118
119 const payload = {
120 name,
121 host,
122 assertIsChannel: true,
123 followerActorId: user.Account.Actor.id
124 }
125
126 JobQueue.Instance.createJobAsync({ type: 'activitypub-follow', payload })
127
128 return res.status(HttpStatusCode.NO_CONTENT_204).end()
129}
130
131async function getUserSubscription (req: express.Request, res: express.Response) {
132 const subscription = res.locals.subscription
133 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(subscription.ActorFollowing.VideoChannel.id)
134
135 return res.json(videoChannel.toFormattedJSON())
136}
137
138async function deleteUserSubscription (req: express.Request, res: express.Response) {
139 const subscription = res.locals.subscription
140
141 await sequelizeTypescript.transaction(async t => {
142 if (subscription.state === 'accepted') {
143 sendUndoFollow(subscription, t)
144 }
145
146 return subscription.destroy({ transaction: t })
147 })
148
149 return res.type('json')
150 .status(HttpStatusCode.NO_CONTENT_204)
151 .end()
152}
153
154async function getUserSubscriptions (req: express.Request, res: express.Response) {
155 const user = res.locals.oauth.token.User
156 const actorId = user.Account.Actor.id
157
158 const resultList = await ActorFollowModel.listSubscriptionsForApi({
159 actorId,
160 start: req.query.start,
161 count: req.query.count,
162 sort: req.query.sort,
163 search: req.query.search
164 })
165
166 return res.json(getFormattedObjects(resultList.data, resultList.total))
167}
168
169async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
170 const user = res.locals.oauth.token.User
171 const countVideos = getCountVideos(req)
172 const query = pickCommonVideoQuery(req.query)
173
174 const apiOptions = await Hooks.wrapObject({
175 ...query,
176
177 displayOnlyForFollower: {
178 actorId: user.Account.Actor.id,
179 orLocalVideos: false
180 },
181 nsfw: buildNSFWFilter(res, query.nsfw),
182 user,
183 countVideos
184 }, 'filter:api.user.me.subscription-videos.list.params')
185
186 const resultList = await Hooks.wrapPromiseFun(
187 VideoModel.listForApi,
188 apiOptions,
189 'filter:api.user.me.subscription-videos.list.result'
190 )
191
192 return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
193}
diff --git a/server/controllers/api/users/my-video-playlists.ts b/server/controllers/api/users/my-video-playlists.ts
deleted file mode 100644
index fbdbb7e50..000000000
--- a/server/controllers/api/users/my-video-playlists.ts
+++ /dev/null
@@ -1,51 +0,0 @@
1import express from 'express'
2import { forceNumber } from '@shared/core-utils'
3import { uuidToShort } from '@shared/extra-utils'
4import { VideosExistInPlaylists } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
5import { asyncMiddleware, authenticate } from '../../../middlewares'
6import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists'
7import { VideoPlaylistModel } from '../../../models/video/video-playlist'
8
9const myVideoPlaylistsRouter = express.Router()
10
11myVideoPlaylistsRouter.get('/me/video-playlists/videos-exist',
12 authenticate,
13 doVideosInPlaylistExistValidator,
14 asyncMiddleware(doVideosInPlaylistExist)
15)
16
17// ---------------------------------------------------------------------------
18
19export {
20 myVideoPlaylistsRouter
21}
22
23// ---------------------------------------------------------------------------
24
25async function doVideosInPlaylistExist (req: express.Request, res: express.Response) {
26 const videoIds = req.query.videoIds.map(i => forceNumber(i))
27 const user = res.locals.oauth.token.User
28
29 const results = await VideoPlaylistModel.listPlaylistSummariesOf(user.Account.id, videoIds)
30
31 const existObject: VideosExistInPlaylists = {}
32
33 for (const videoId of videoIds) {
34 existObject[videoId] = []
35 }
36
37 for (const result of results) {
38 for (const element of result.VideoPlaylistElements) {
39 existObject[element.videoId].push({
40 playlistElementId: element.id,
41 playlistId: result.id,
42 playlistDisplayName: result.name,
43 playlistShortUUID: uuidToShort(result.uuid),
44 startTimestamp: element.startTimestamp,
45 stopTimestamp: element.stopTimestamp
46 })
47 }
48 }
49
50 return res.json(existObject)
51}
diff --git a/server/controllers/api/users/registrations.ts b/server/controllers/api/users/registrations.ts
deleted file mode 100644
index 5e213d6cc..000000000
--- a/server/controllers/api/users/registrations.ts
+++ /dev/null
@@ -1,249 +0,0 @@
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 {
7 HttpStatusCode,
8 UserRegister,
9 UserRegistrationRequest,
10 UserRegistrationState,
11 UserRegistrationUpdateState,
12 UserRight
13} from '@shared/models'
14import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger'
15import { logger } from '../../../helpers/logger'
16import { CONFIG } from '../../../initializers/config'
17import { Notifier } from '../../../lib/notifier'
18import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user'
19import {
20 acceptOrRejectRegistrationValidator,
21 asyncMiddleware,
22 asyncRetryTransactionMiddleware,
23 authenticate,
24 buildRateLimiter,
25 ensureUserHasRight,
26 ensureUserRegistrationAllowedFactory,
27 ensureUserRegistrationAllowedForIP,
28 getRegistrationValidator,
29 listRegistrationsValidator,
30 paginationValidator,
31 setDefaultPagination,
32 setDefaultSort,
33 userRegistrationsSortValidator,
34 usersDirectRegistrationValidator,
35 usersRequestRegistrationValidator
36} from '../../../middlewares'
37
38const auditLogger = auditLoggerFactory('users')
39
40const registrationRateLimiter = buildRateLimiter({
41 windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
42 max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
43 skipFailedRequests: true
44})
45
46const registrationsRouter = express.Router()
47
48registrationsRouter.post('/registrations/request',
49 registrationRateLimiter,
50 asyncMiddleware(ensureUserRegistrationAllowedFactory('request-registration')),
51 ensureUserRegistrationAllowedForIP,
52 asyncMiddleware(usersRequestRegistrationValidator),
53 asyncRetryTransactionMiddleware(requestRegistration)
54)
55
56registrationsRouter.post('/registrations/:registrationId/accept',
57 authenticate,
58 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
59 asyncMiddleware(acceptOrRejectRegistrationValidator),
60 asyncRetryTransactionMiddleware(acceptRegistration)
61)
62registrationsRouter.post('/registrations/:registrationId/reject',
63 authenticate,
64 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
65 asyncMiddleware(acceptOrRejectRegistrationValidator),
66 asyncRetryTransactionMiddleware(rejectRegistration)
67)
68
69registrationsRouter.delete('/registrations/:registrationId',
70 authenticate,
71 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
72 asyncMiddleware(getRegistrationValidator),
73 asyncRetryTransactionMiddleware(deleteRegistration)
74)
75
76registrationsRouter.get('/registrations',
77 authenticate,
78 ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
79 paginationValidator,
80 userRegistrationsSortValidator,
81 setDefaultSort,
82 setDefaultPagination,
83 listRegistrationsValidator,
84 asyncMiddleware(listRegistrations)
85)
86
87registrationsRouter.post('/register',
88 registrationRateLimiter,
89 asyncMiddleware(ensureUserRegistrationAllowedFactory('direct-registration')),
90 ensureUserRegistrationAllowedForIP,
91 asyncMiddleware(usersDirectRegistrationValidator),
92 asyncRetryTransactionMiddleware(registerUser)
93)
94
95// ---------------------------------------------------------------------------
96
97export {
98 registrationsRouter
99}
100
101// ---------------------------------------------------------------------------
102
103async function requestRegistration (req: express.Request, res: express.Response) {
104 const body: UserRegistrationRequest = req.body
105
106 const registration = new UserRegistrationModel({
107 ...pick(body, [ 'username', 'password', 'email', 'registrationReason' ]),
108
109 accountDisplayName: body.displayName,
110 channelDisplayName: body.channel?.displayName,
111 channelHandle: body.channel?.name,
112
113 state: UserRegistrationState.PENDING,
114
115 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
116 })
117
118 await registration.save()
119
120 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
121 await sendVerifyRegistrationEmail(registration)
122 }
123
124 Notifier.Instance.notifyOnNewRegistrationRequest(registration)
125
126 Hooks.runAction('action:api.user.requested-registration', { body, registration, req, res })
127
128 return res.json(registration.toFormattedJSON())
129}
130
131// ---------------------------------------------------------------------------
132
133async function acceptRegistration (req: express.Request, res: express.Response) {
134 const registration = res.locals.userRegistration
135 const body: UserRegistrationUpdateState = req.body
136
137 const userToCreate = buildUser({
138 username: registration.username,
139 password: registration.password,
140 email: registration.email,
141 emailVerified: registration.emailVerified
142 })
143 // We already encrypted password in registration model
144 userToCreate.skipPasswordEncryption = true
145
146 // TODO: handle conflicts if someone else created a channel handle/user handle/user email between registration and approval
147
148 const { user } = await createUserAccountAndChannelAndPlaylist({
149 userToCreate,
150 userDisplayName: registration.accountDisplayName,
151 channelNames: registration.channelHandle && registration.channelDisplayName
152 ? {
153 name: registration.channelHandle,
154 displayName: registration.channelDisplayName
155 }
156 : undefined
157 })
158
159 registration.userId = user.id
160 registration.state = UserRegistrationState.ACCEPTED
161 registration.moderationResponse = body.moderationResponse
162
163 await registration.save()
164
165 logger.info('Registration of %s accepted', registration.username)
166
167 if (body.preventEmailDelivery !== true) {
168 Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
169 }
170
171 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
172}
173
174async function rejectRegistration (req: express.Request, res: express.Response) {
175 const registration = res.locals.userRegistration
176 const body: UserRegistrationUpdateState = req.body
177
178 registration.state = UserRegistrationState.REJECTED
179 registration.moderationResponse = body.moderationResponse
180
181 await registration.save()
182
183 if (body.preventEmailDelivery !== true) {
184 Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
185 }
186
187 logger.info('Registration of %s rejected', registration.username)
188
189 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
190}
191
192// ---------------------------------------------------------------------------
193
194async function deleteRegistration (req: express.Request, res: express.Response) {
195 const registration = res.locals.userRegistration
196
197 await registration.destroy()
198
199 logger.info('Registration of %s deleted', registration.username)
200
201 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
202}
203
204// ---------------------------------------------------------------------------
205
206async function listRegistrations (req: express.Request, res: express.Response) {
207 const resultList = await UserRegistrationModel.listForApi({
208 start: req.query.start,
209 count: req.query.count,
210 sort: req.query.sort,
211 search: req.query.search
212 })
213
214 return res.json({
215 total: resultList.total,
216 data: resultList.data.map(d => d.toFormattedJSON())
217 })
218}
219
220// ---------------------------------------------------------------------------
221
222async function registerUser (req: express.Request, res: express.Response) {
223 const body: UserRegister = req.body
224
225 const userToCreate = buildUser({
226 ...pick(body, [ 'username', 'password', 'email' ]),
227
228 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
229 })
230
231 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
232 userToCreate,
233 userDisplayName: body.displayName || undefined,
234 channelNames: body.channel
235 })
236
237 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
238 logger.info('User %s with its channel and account registered.', body.username)
239
240 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
241 await sendVerifyUserEmail(user)
242 }
243
244 Notifier.Instance.notifyOnNewDirectRegistration(user)
245
246 Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel, req, res })
247
248 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
249}
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts
deleted file mode 100644
index c6afea67c..000000000
--- a/server/controllers/api/users/token.ts
+++ /dev/null
@@ -1,131 +0,0 @@
1import express from 'express'
2import { logger } from '@server/helpers/logger'
3import { CONFIG } from '@server/initializers/config'
4import { OTP } from '@server/initializers/constants'
5import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
6import { handleOAuthToken, MissingTwoFactorError } from '@server/lib/auth/oauth'
7import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
8import { Hooks } from '@server/lib/plugins/hooks'
9import { asyncMiddleware, authenticate, buildRateLimiter, openapiOperationDoc } from '@server/middlewares'
10import { buildUUID } from '@shared/extra-utils'
11import { ScopedToken } from '@shared/models/users/user-scoped-token'
12
13const tokensRouter = express.Router()
14
15const loginRateLimiter = buildRateLimiter({
16 windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
17 max: CONFIG.RATES_LIMIT.LOGIN.MAX
18})
19
20tokensRouter.post('/token',
21 loginRateLimiter,
22 openapiOperationDoc({ operationId: 'getOAuthToken' }),
23 asyncMiddleware(handleToken)
24)
25
26tokensRouter.post('/revoke-token',
27 openapiOperationDoc({ operationId: 'revokeOAuthToken' }),
28 authenticate,
29 asyncMiddleware(handleTokenRevocation)
30)
31
32tokensRouter.get('/scoped-tokens',
33 authenticate,
34 getScopedTokens
35)
36
37tokensRouter.post('/scoped-tokens',
38 authenticate,
39 asyncMiddleware(renewScopedTokens)
40)
41
42// ---------------------------------------------------------------------------
43
44export {
45 tokensRouter
46}
47// ---------------------------------------------------------------------------
48
49async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
50 const grantType = req.body.grant_type
51
52 try {
53 const bypassLogin = await buildByPassLogin(req, grantType)
54
55 const refreshTokenAuthName = grantType === 'refresh_token'
56 ? await getAuthNameFromRefreshGrant(req.body.refresh_token)
57 : undefined
58
59 const options = {
60 refreshTokenAuthName,
61 bypassLogin
62 }
63
64 const token = await handleOAuthToken(req, options)
65
66 res.set('Cache-Control', 'no-store')
67 res.set('Pragma', 'no-cache')
68
69 Hooks.runAction('action:api.user.oauth2-got-token', { username: token.user.username, ip: req.ip, req, res })
70
71 return res.json({
72 token_type: 'Bearer',
73
74 access_token: token.accessToken,
75 refresh_token: token.refreshToken,
76
77 expires_in: token.accessTokenExpiresIn,
78 refresh_token_expires_in: token.refreshTokenExpiresIn
79 })
80 } catch (err) {
81 logger.warn('Login error', { err })
82
83 if (err instanceof MissingTwoFactorError) {
84 res.set(OTP.HEADER_NAME, OTP.HEADER_REQUIRED_VALUE)
85 }
86
87 return res.fail({
88 status: err.code,
89 message: err.message,
90 type: err.name
91 })
92 }
93}
94
95async function handleTokenRevocation (req: express.Request, res: express.Response) {
96 const token = res.locals.oauth.token
97
98 const result = await revokeToken(token, { req, explicitLogout: true })
99
100 return res.json(result)
101}
102
103function getScopedTokens (req: express.Request, res: express.Response) {
104 const user = res.locals.oauth.token.user
105
106 return res.json({
107 feedToken: user.feedToken
108 } as ScopedToken)
109}
110
111async function renewScopedTokens (req: express.Request, res: express.Response) {
112 const user = res.locals.oauth.token.user
113
114 user.feedToken = buildUUID()
115 await user.save()
116
117 return res.json({
118 feedToken: user.feedToken
119 } as ScopedToken)
120}
121
122async function buildByPassLogin (req: express.Request, grantType: string): Promise<BypassLogin> {
123 if (grantType !== 'password') return undefined
124
125 if (req.body.externalAuthToken) {
126 // Consistency with the getBypassFromPasswordGrant promise
127 return getBypassFromExternalAuth(req.body.username, req.body.externalAuthToken)
128 }
129
130 return getBypassFromPasswordGrant(req.body.username, req.body.password)
131}
diff --git a/server/controllers/api/users/two-factor.ts b/server/controllers/api/users/two-factor.ts
deleted file mode 100644
index e6ae9e4dd..000000000
--- a/server/controllers/api/users/two-factor.ts
+++ /dev/null
@@ -1,95 +0,0 @@
1import express from 'express'
2import { generateOTPSecret, isOTPValid } from '@server/helpers/otp'
3import { encrypt } from '@server/helpers/peertube-crypto'
4import { CONFIG } from '@server/initializers/config'
5import { Redis } from '@server/lib/redis'
6import { asyncMiddleware, authenticate, usersCheckCurrentPasswordFactory } from '@server/middlewares'
7import {
8 confirmTwoFactorValidator,
9 disableTwoFactorValidator,
10 requestOrConfirmTwoFactorValidator
11} from '@server/middlewares/validators/two-factor'
12import { HttpStatusCode, TwoFactorEnableResult } from '@shared/models'
13
14const twoFactorRouter = express.Router()
15
16twoFactorRouter.post('/:id/two-factor/request',
17 authenticate,
18 asyncMiddleware(usersCheckCurrentPasswordFactory(req => req.params.id)),
19 asyncMiddleware(requestOrConfirmTwoFactorValidator),
20 asyncMiddleware(requestTwoFactor)
21)
22
23twoFactorRouter.post('/:id/two-factor/confirm-request',
24 authenticate,
25 asyncMiddleware(requestOrConfirmTwoFactorValidator),
26 confirmTwoFactorValidator,
27 asyncMiddleware(confirmRequestTwoFactor)
28)
29
30twoFactorRouter.post('/:id/two-factor/disable',
31 authenticate,
32 asyncMiddleware(usersCheckCurrentPasswordFactory(req => req.params.id)),
33 asyncMiddleware(disableTwoFactorValidator),
34 asyncMiddleware(disableTwoFactor)
35)
36
37// ---------------------------------------------------------------------------
38
39export {
40 twoFactorRouter
41}
42
43// ---------------------------------------------------------------------------
44
45async function requestTwoFactor (req: express.Request, res: express.Response) {
46 const user = res.locals.user
47
48 const { secret, uri } = generateOTPSecret(user.email)
49
50 const encryptedSecret = await encrypt(secret, CONFIG.SECRETS.PEERTUBE)
51 const requestToken = await Redis.Instance.setTwoFactorRequest(user.id, encryptedSecret)
52
53 return res.json({
54 otpRequest: {
55 requestToken,
56 secret,
57 uri
58 }
59 } as TwoFactorEnableResult)
60}
61
62async function confirmRequestTwoFactor (req: express.Request, res: express.Response) {
63 const requestToken = req.body.requestToken
64 const otpToken = req.body.otpToken
65 const user = res.locals.user
66
67 const encryptedSecret = await Redis.Instance.getTwoFactorRequestToken(user.id, requestToken)
68 if (!encryptedSecret) {
69 return res.fail({
70 message: 'Invalid request token',
71 status: HttpStatusCode.FORBIDDEN_403
72 })
73 }
74
75 if (await isOTPValid({ encryptedSecret, token: otpToken }) !== true) {
76 return res.fail({
77 message: 'Invalid OTP token',
78 status: HttpStatusCode.FORBIDDEN_403
79 })
80 }
81
82 user.otpSecret = encryptedSecret
83 await user.save()
84
85 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
86}
87
88async function disableTwoFactor (req: express.Request, res: express.Response) {
89 const user = res.locals.user
90
91 user.otpSecret = null
92 await user.save()
93
94 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
95}