diff options
Diffstat (limited to 'server/controllers')
-rw-r--r-- | server/controllers/activitypub/client.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/config.ts | 1 | ||||
-rw-r--r-- | server/controllers/api/users/email-verification.ts | 72 | ||||
-rw-r--r-- | server/controllers/api/users/index.ts | 99 | ||||
-rw-r--r-- | server/controllers/api/users/registrations.ts | 249 | ||||
-rw-r--r-- | server/controllers/api/video-playlist.ts | 7 | ||||
-rw-r--r-- | server/controllers/api/videos/comment.ts | 7 | ||||
-rw-r--r-- | server/controllers/api/videos/token.ts | 2 | ||||
-rw-r--r-- | server/controllers/feeds.ts | 4 | ||||
-rw-r--r-- | server/controllers/tracker.ts | 34 |
10 files changed, 356 insertions, 121 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 8e064fb5b..def320730 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -309,7 +309,7 @@ async function videoCommentsController (req: express.Request, res: express.Respo | |||
309 | if (redirectIfNotOwned(video.url, res)) return | 309 | if (redirectIfNotOwned(video.url, res)) return |
310 | 310 | ||
311 | const handler = async (start: number, count: number) => { | 311 | const handler = async (start: number, count: number) => { |
312 | const result = await VideoCommentModel.listAndCountByVideoForAP(video, start, count) | 312 | const result = await VideoCommentModel.listAndCountByVideoForAP({ video, start, count }) |
313 | 313 | ||
314 | return { | 314 | return { |
315 | total: result.total, | 315 | total: result.total, |
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 @@ | |||
1 | import express from 'express' | ||
2 | import { HttpStatusCode } from '@shared/models' | ||
3 | import { CONFIG } from '../../../initializers/config' | ||
4 | import { sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user' | ||
5 | import { asyncMiddleware, buildRateLimiter } from '../../../middlewares' | ||
6 | import { | ||
7 | registrationVerifyEmailValidator, | ||
8 | usersAskSendVerifyEmailValidator, | ||
9 | usersVerifyEmailValidator | ||
10 | } from '../../../middlewares/validators' | ||
11 | |||
12 | const askSendEmailLimiter = buildRateLimiter({ | ||
13 | windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, | ||
14 | max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX | ||
15 | }) | ||
16 | |||
17 | const emailVerificationRouter = express.Router() | ||
18 | |||
19 | emailVerificationRouter.post([ '/ask-send-verify-email', '/registrations/ask-send-verify-email' ], | ||
20 | askSendEmailLimiter, | ||
21 | asyncMiddleware(usersAskSendVerifyEmailValidator), | ||
22 | asyncMiddleware(reSendVerifyUserEmail) | ||
23 | ) | ||
24 | |||
25 | emailVerificationRouter.post('/:id/verify-email', | ||
26 | asyncMiddleware(usersVerifyEmailValidator), | ||
27 | asyncMiddleware(verifyUserEmail) | ||
28 | ) | ||
29 | |||
30 | emailVerificationRouter.post('/registrations/:registrationId/verify-email', | ||
31 | asyncMiddleware(registrationVerifyEmailValidator), | ||
32 | asyncMiddleware(verifyRegistrationEmail) | ||
33 | ) | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | export { | ||
38 | emailVerificationRouter | ||
39 | } | ||
40 | |||
41 | async 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 | |||
51 | async 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 | |||
65 | async 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' | |||
4 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' | 4 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' |
5 | import { MUserAccountDefault } from '@server/types/models' | 5 | import { MUserAccountDefault } from '@server/types/models' |
6 | import { pick } from '@shared/core-utils' | 6 | import { pick } from '@shared/core-utils' |
7 | import { HttpStatusCode, UserCreate, UserCreateResult, UserRegister, UserRight, UserUpdate } from '@shared/models' | 7 | import { HttpStatusCode, UserCreate, UserCreateResult, UserRight, UserUpdate } from '@shared/models' |
8 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | 8 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' |
9 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
10 | import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' | 10 | import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' |
11 | import { CONFIG } from '../../../initializers/config' | ||
12 | import { WEBSERVER } from '../../../initializers/constants' | 11 | import { WEBSERVER } from '../../../initializers/constants' |
13 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
14 | import { Emailer } from '../../../lib/emailer' | 13 | import { Emailer } from '../../../lib/emailer' |
15 | import { Notifier } from '../../../lib/notifier' | ||
16 | import { Redis } from '../../../lib/redis' | 14 | import { Redis } from '../../../lib/redis' |
17 | import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' | 15 | import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user' |
18 | import { | 16 | import { |
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' |
38 | import { | 32 | import { |
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' |
46 | import { UserModel } from '../../../models/user/user' | 38 | import { UserModel } from '../../../models/user/user' |
39 | import { emailVerificationRouter } from './email-verification' | ||
47 | import { meRouter } from './me' | 40 | import { meRouter } from './me' |
48 | import { myAbusesRouter } from './my-abuses' | 41 | import { myAbusesRouter } from './my-abuses' |
49 | import { myBlocklistRouter } from './my-blocklist' | 42 | import { myBlocklistRouter } from './my-blocklist' |
@@ -51,22 +44,14 @@ import { myVideosHistoryRouter } from './my-history' | |||
51 | import { myNotificationsRouter } from './my-notifications' | 44 | import { myNotificationsRouter } from './my-notifications' |
52 | import { mySubscriptionsRouter } from './my-subscriptions' | 45 | import { mySubscriptionsRouter } from './my-subscriptions' |
53 | import { myVideoPlaylistsRouter } from './my-video-playlists' | 46 | import { myVideoPlaylistsRouter } from './my-video-playlists' |
47 | import { registrationsRouter } from './registrations' | ||
54 | import { twoFactorRouter } from './two-factor' | 48 | import { twoFactorRouter } from './two-factor' |
55 | 49 | ||
56 | const auditLogger = auditLoggerFactory('users') | 50 | const auditLogger = auditLoggerFactory('users') |
57 | 51 | ||
58 | const signupRateLimiter = buildRateLimiter({ | ||
59 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, | ||
60 | max: CONFIG.RATES_LIMIT.SIGNUP.MAX, | ||
61 | skipFailedRequests: true | ||
62 | }) | ||
63 | |||
64 | const askSendEmailLimiter = buildRateLimiter({ | ||
65 | windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, | ||
66 | max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX | ||
67 | }) | ||
68 | |||
69 | const usersRouter = express.Router() | 52 | const usersRouter = express.Router() |
53 | usersRouter.use('/', emailVerificationRouter) | ||
54 | usersRouter.use('/', registrationsRouter) | ||
70 | usersRouter.use('/', twoFactorRouter) | 55 | usersRouter.use('/', twoFactorRouter) |
71 | usersRouter.use('/', tokensRouter) | 56 | usersRouter.use('/', tokensRouter) |
72 | usersRouter.use('/', myNotificationsRouter) | 57 | usersRouter.use('/', myNotificationsRouter) |
@@ -122,14 +107,6 @@ usersRouter.post('/', | |||
122 | asyncRetryTransactionMiddleware(createUser) | 107 | asyncRetryTransactionMiddleware(createUser) |
123 | ) | 108 | ) |
124 | 109 | ||
125 | usersRouter.post('/register', | ||
126 | signupRateLimiter, | ||
127 | asyncMiddleware(ensureUserRegistrationAllowed), | ||
128 | ensureUserRegistrationAllowedForIP, | ||
129 | asyncMiddleware(usersRegisterValidator), | ||
130 | asyncRetryTransactionMiddleware(registerUser) | ||
131 | ) | ||
132 | |||
133 | usersRouter.put('/:id', | 110 | usersRouter.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 | ||
159 | usersRouter.post('/ask-send-verify-email', | ||
160 | askSendEmailLimiter, | ||
161 | asyncMiddleware(usersAskSendVerifyEmailValidator), | ||
162 | asyncMiddleware(reSendVerifyUserEmail) | ||
163 | ) | ||
164 | |||
165 | usersRouter.post('/:id/verify-email', | ||
166 | asyncMiddleware(usersVerifyEmailValidator), | ||
167 | asyncMiddleware(verifyUserEmail) | ||
168 | ) | ||
169 | |||
170 | // --------------------------------------------------------------------------- | 136 | // --------------------------------------------------------------------------- |
171 | 137 | ||
172 | export { | 138 | export { |
@@ -218,35 +184,6 @@ async function createUser (req: express.Request, res: express.Response) { | |||
218 | }) | 184 | }) |
219 | } | 185 | } |
220 | 186 | ||
221 | async 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 | |||
250 | async function unblockUser (req: express.Request, res: express.Response) { | 187 | async 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 | ||
363 | async 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 | |||
371 | async 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 | |||
385 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { | 300 | async 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..5e213d6cc --- /dev/null +++ b/server/controllers/api/users/registrations.ts | |||
@@ -0,0 +1,249 @@ | |||
1 | import express from 'express' | ||
2 | import { Emailer } from '@server/lib/emailer' | ||
3 | import { Hooks } from '@server/lib/plugins/hooks' | ||
4 | import { UserRegistrationModel } from '@server/models/user/user-registration' | ||
5 | import { pick } from '@shared/core-utils' | ||
6 | import { | ||
7 | HttpStatusCode, | ||
8 | UserRegister, | ||
9 | UserRegistrationRequest, | ||
10 | UserRegistrationState, | ||
11 | UserRegistrationUpdateState, | ||
12 | UserRight | ||
13 | } from '@shared/models' | ||
14 | import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger' | ||
15 | import { logger } from '../../../helpers/logger' | ||
16 | import { CONFIG } from '../../../initializers/config' | ||
17 | import { Notifier } from '../../../lib/notifier' | ||
18 | import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user' | ||
19 | import { | ||
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 | |||
38 | const auditLogger = auditLoggerFactory('users') | ||
39 | |||
40 | const registrationRateLimiter = buildRateLimiter({ | ||
41 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, | ||
42 | max: CONFIG.RATES_LIMIT.SIGNUP.MAX, | ||
43 | skipFailedRequests: true | ||
44 | }) | ||
45 | |||
46 | const registrationsRouter = express.Router() | ||
47 | |||
48 | registrationsRouter.post('/registrations/request', | ||
49 | registrationRateLimiter, | ||
50 | asyncMiddleware(ensureUserRegistrationAllowedFactory('request-registration')), | ||
51 | ensureUserRegistrationAllowedForIP, | ||
52 | asyncMiddleware(usersRequestRegistrationValidator), | ||
53 | asyncRetryTransactionMiddleware(requestRegistration) | ||
54 | ) | ||
55 | |||
56 | registrationsRouter.post('/registrations/:registrationId/accept', | ||
57 | authenticate, | ||
58 | ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS), | ||
59 | asyncMiddleware(acceptOrRejectRegistrationValidator), | ||
60 | asyncRetryTransactionMiddleware(acceptRegistration) | ||
61 | ) | ||
62 | registrationsRouter.post('/registrations/:registrationId/reject', | ||
63 | authenticate, | ||
64 | ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS), | ||
65 | asyncMiddleware(acceptOrRejectRegistrationValidator), | ||
66 | asyncRetryTransactionMiddleware(rejectRegistration) | ||
67 | ) | ||
68 | |||
69 | registrationsRouter.delete('/registrations/:registrationId', | ||
70 | authenticate, | ||
71 | ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS), | ||
72 | asyncMiddleware(getRegistrationValidator), | ||
73 | asyncRetryTransactionMiddleware(deleteRegistration) | ||
74 | ) | ||
75 | |||
76 | registrationsRouter.get('/registrations', | ||
77 | authenticate, | ||
78 | ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS), | ||
79 | paginationValidator, | ||
80 | userRegistrationsSortValidator, | ||
81 | setDefaultSort, | ||
82 | setDefaultPagination, | ||
83 | listRegistrationsValidator, | ||
84 | asyncMiddleware(listRegistrations) | ||
85 | ) | ||
86 | |||
87 | registrationsRouter.post('/register', | ||
88 | registrationRateLimiter, | ||
89 | asyncMiddleware(ensureUserRegistrationAllowedFactory('direct-registration')), | ||
90 | ensureUserRegistrationAllowedForIP, | ||
91 | asyncMiddleware(usersDirectRegistrationValidator), | ||
92 | asyncRetryTransactionMiddleware(registerUser) | ||
93 | ) | ||
94 | |||
95 | // --------------------------------------------------------------------------- | ||
96 | |||
97 | export { | ||
98 | registrationsRouter | ||
99 | } | ||
100 | |||
101 | // --------------------------------------------------------------------------- | ||
102 | |||
103 | async 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 | |||
133 | async 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 | |||
174 | async 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 | |||
194 | async 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 | |||
206 | async 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 | |||
222 | async 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/video-playlist.ts b/server/controllers/api/video-playlist.ts index f8a607170..947f7ca77 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -15,7 +15,7 @@ import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/vid | |||
15 | import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/video-playlist-reorder.model' | 15 | import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/video-playlist-reorder.model' |
16 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' | 16 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' |
17 | import { resetSequelizeInstance } from '../../helpers/database-utils' | 17 | import { resetSequelizeInstance } from '../../helpers/database-utils' |
18 | import { buildNSFWFilter, createReqFiles } from '../../helpers/express-utils' | 18 | import { createReqFiles } from '../../helpers/express-utils' |
19 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
20 | import { getFormattedObjects } from '../../helpers/utils' | 20 | import { getFormattedObjects } from '../../helpers/utils' |
21 | import { CONFIG } from '../../initializers/config' | 21 | import { CONFIG } from '../../initializers/config' |
@@ -474,10 +474,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon | |||
474 | 'filter:api.video-playlist.videos.list.result' | 474 | 'filter:api.video-playlist.videos.list.result' |
475 | ) | 475 | ) |
476 | 476 | ||
477 | const options = { | 477 | const options = { accountId: user?.Account?.id } |
478 | displayNSFW: buildNSFWFilter(res, req.query.nsfw), | ||
479 | accountId: user ? user.Account.id : undefined | ||
480 | } | ||
481 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) | 478 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) |
482 | } | 479 | } |
483 | 480 | ||
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index 44d64776c..70ca21500 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import { MCommentFormattable } from '@server/types/models' | ||
1 | import express from 'express' | 2 | import express from 'express' |
3 | |||
2 | import { ResultList, ThreadsResultList, UserRight, VideoCommentCreate } from '../../../../shared/models' | 4 | import { ResultList, ThreadsResultList, UserRight, VideoCommentCreate } from '../../../../shared/models' |
3 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 5 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
4 | import { VideoCommentThreads } from '../../../../shared/models/videos/comment/video-comment.model' | 6 | import { VideoCommentThreads } from '../../../../shared/models/videos/comment/video-comment.model' |
@@ -109,7 +111,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
109 | const video = res.locals.onlyVideo | 111 | const video = res.locals.onlyVideo |
110 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 112 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
111 | 113 | ||
112 | let resultList: ThreadsResultList<VideoCommentModel> | 114 | let resultList: ThreadsResultList<MCommentFormattable> |
113 | 115 | ||
114 | if (video.commentsEnabled === true) { | 116 | if (video.commentsEnabled === true) { |
115 | const apiOptions = await Hooks.wrapObject({ | 117 | const apiOptions = await Hooks.wrapObject({ |
@@ -144,12 +146,11 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo | |||
144 | const video = res.locals.onlyVideo | 146 | const video = res.locals.onlyVideo |
145 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 147 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
146 | 148 | ||
147 | let resultList: ResultList<VideoCommentModel> | 149 | let resultList: ResultList<MCommentFormattable> |
148 | 150 | ||
149 | if (video.commentsEnabled === true) { | 151 | if (video.commentsEnabled === true) { |
150 | const apiOptions = await Hooks.wrapObject({ | 152 | const apiOptions = await Hooks.wrapObject({ |
151 | videoId: video.id, | 153 | videoId: video.id, |
152 | isVideoOwned: video.isOwned(), | ||
153 | threadId: res.locals.videoCommentThread.id, | 154 | threadId: res.locals.videoCommentThread.id, |
154 | user | 155 | user |
155 | }, 'filter:api.video-thread-comments.list.params') | 156 | }, 'filter:api.video-thread-comments.list.params') |
diff --git a/server/controllers/api/videos/token.ts b/server/controllers/api/videos/token.ts index 009b6dfb6..22387c3e8 100644 --- a/server/controllers/api/videos/token.ts +++ b/server/controllers/api/videos/token.ts | |||
@@ -22,7 +22,7 @@ export { | |||
22 | function generateToken (req: express.Request, res: express.Response) { | 22 | function generateToken (req: express.Request, res: express.Response) { |
23 | const video = res.locals.onlyVideo | 23 | const video = res.locals.onlyVideo |
24 | 24 | ||
25 | const { token, expires } = VideoTokensManager.Instance.create(video.uuid) | 25 | const { token, expires } = VideoTokensManager.Instance.create({ videoUUID: video.uuid, user: res.locals.oauth.token.User }) |
26 | 26 | ||
27 | return res.json({ | 27 | return res.json({ |
28 | files: { | 28 | files: { |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 772fe734d..ef810a842 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -285,8 +285,8 @@ function addVideosToFeed (feed: Feed, videos: VideoModel[]) { | |||
285 | content: toSafeHtml(video.description), | 285 | content: toSafeHtml(video.description), |
286 | author: [ | 286 | author: [ |
287 | { | 287 | { |
288 | name: video.VideoChannel.Account.getDisplayName(), | 288 | name: video.VideoChannel.getDisplayName(), |
289 | link: video.VideoChannel.Account.Actor.url | 289 | link: video.VideoChannel.Actor.url |
290 | } | 290 | } |
291 | ], | 291 | ], |
292 | date: video.publishedAt, | 292 | date: video.publishedAt, |
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts index 19a8b2bc9..c4f3a8889 100644 --- a/server/controllers/tracker.ts +++ b/server/controllers/tracker.ts | |||
@@ -1,17 +1,22 @@ | |||
1 | import { Server as TrackerServer } from 'bittorrent-tracker' | 1 | import { Server as TrackerServer } from 'bittorrent-tracker' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { createServer } from 'http' | 3 | import { createServer } from 'http' |
4 | import LRUCache from 'lru-cache' | ||
4 | import proxyAddr from 'proxy-addr' | 5 | import proxyAddr from 'proxy-addr' |
5 | import { WebSocketServer } from 'ws' | 6 | import { WebSocketServer } from 'ws' |
6 | import { Redis } from '@server/lib/redis' | ||
7 | import { logger } from '../helpers/logger' | 7 | import { logger } from '../helpers/logger' |
8 | import { CONFIG } from '../initializers/config' | 8 | import { CONFIG } from '../initializers/config' |
9 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' | 9 | import { LRU_CACHE, TRACKER_RATE_LIMITS } from '../initializers/constants' |
10 | import { VideoFileModel } from '../models/video/video-file' | 10 | import { VideoFileModel } from '../models/video/video-file' |
11 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 11 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
12 | 12 | ||
13 | const trackerRouter = express.Router() | 13 | const trackerRouter = express.Router() |
14 | 14 | ||
15 | const blockedIPs = new LRUCache<string, boolean>({ | ||
16 | max: LRU_CACHE.TRACKER_IPS.MAX_SIZE, | ||
17 | ttl: TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME | ||
18 | }) | ||
19 | |||
15 | let peersIps = {} | 20 | let peersIps = {} |
16 | let peersIpInfoHash = {} | 21 | let peersIpInfoHash = {} |
17 | runPeersChecker() | 22 | runPeersChecker() |
@@ -55,8 +60,7 @@ const trackerServer = new TrackerServer({ | |||
55 | 60 | ||
56 | // Close socket connection and block IP for a few time | 61 | // Close socket connection and block IP for a few time |
57 | if (params.type === 'ws') { | 62 | if (params.type === 'ws') { |
58 | Redis.Instance.setTrackerBlockIP(ip) | 63 | blockedIPs.set(ip, true) |
59 | .catch(err => logger.error('Cannot set tracker block ip.', { err })) | ||
60 | 64 | ||
61 | // setTimeout to wait filter response | 65 | // setTimeout to wait filter response |
62 | setTimeout(() => params.socket.close(), 0) | 66 | setTimeout(() => params.socket.close(), 0) |
@@ -102,26 +106,22 @@ function createWebsocketTrackerServer (app: express.Application) { | |||
102 | if (request.url === '/tracker/socket') { | 106 | if (request.url === '/tracker/socket') { |
103 | const ip = proxyAddr(request, CONFIG.TRUST_PROXY) | 107 | const ip = proxyAddr(request, CONFIG.TRUST_PROXY) |
104 | 108 | ||
105 | Redis.Instance.doesTrackerBlockIPExist(ip) | 109 | if (blockedIPs.has(ip)) { |
106 | .then(result => { | 110 | logger.debug('Blocking IP %s from tracker.', ip) |
107 | if (result === true) { | ||
108 | logger.debug('Blocking IP %s from tracker.', ip) | ||
109 | 111 | ||
110 | socket.write('HTTP/1.1 403 Forbidden\r\n\r\n') | 112 | socket.write('HTTP/1.1 403 Forbidden\r\n\r\n') |
111 | socket.destroy() | 113 | socket.destroy() |
112 | return | 114 | return |
113 | } | 115 | } |
114 | 116 | ||
115 | // FIXME: typings | 117 | // FIXME: typings |
116 | return wss.handleUpgrade(request, socket as any, head, ws => wss.emit('connection', ws, request)) | 118 | return wss.handleUpgrade(request, socket as any, head, ws => wss.emit('connection', ws, request)) |
117 | }) | ||
118 | .catch(err => logger.error('Cannot check if tracker block ip exists.', { err })) | ||
119 | } | 119 | } |
120 | 120 | ||
121 | // Don't destroy socket, we have Socket.IO too | 121 | // Don't destroy socket, we have Socket.IO too |
122 | }) | 122 | }) |
123 | 123 | ||
124 | return server | 124 | return { server, trackerServer } |
125 | } | 125 | } |
126 | 126 | ||
127 | // --------------------------------------------------------------------------- | 127 | // --------------------------------------------------------------------------- |