ニジカ投稿局 https://tv.nizika.tv
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

abuse.ts 7.8 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import express from 'express'
  2. import { logger } from '@server/helpers/logger.js'
  3. import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation.js'
  4. import { Notifier } from '@server/lib/notifier/index.js'
  5. import { AbuseMessageModel } from '@server/models/abuse/abuse-message.js'
  6. import { AbuseModel } from '@server/models/abuse/abuse.js'
  7. import { getServerActor } from '@server/models/application/application.js'
  8. import { abusePredefinedReasonsMap } from '@peertube/peertube-core-utils'
  9. import { AbuseCreate, AbuseState, HttpStatusCode, UserRight } from '@peertube/peertube-models'
  10. import { getFormattedObjects } from '../../helpers/utils.js'
  11. import { sequelizeTypescript } from '../../initializers/database.js'
  12. import {
  13. abuseGetValidator,
  14. abuseListForAdminsValidator,
  15. abuseReportValidator,
  16. abusesSortValidator,
  17. abuseUpdateValidator,
  18. addAbuseMessageValidator,
  19. apiRateLimiter,
  20. asyncMiddleware,
  21. asyncRetryTransactionMiddleware,
  22. authenticate,
  23. checkAbuseValidForMessagesValidator,
  24. deleteAbuseMessageValidator,
  25. ensureUserHasRight,
  26. getAbuseValidator,
  27. openapiOperationDoc,
  28. paginationValidator,
  29. setDefaultPagination,
  30. setDefaultSort
  31. } from '../../middlewares/index.js'
  32. import { AccountModel } from '../../models/account/account.js'
  33. const abuseRouter = express.Router()
  34. abuseRouter.use(apiRateLimiter)
  35. abuseRouter.get('/',
  36. openapiOperationDoc({ operationId: 'getAbuses' }),
  37. authenticate,
  38. ensureUserHasRight(UserRight.MANAGE_ABUSES),
  39. paginationValidator,
  40. abusesSortValidator,
  41. setDefaultSort,
  42. setDefaultPagination,
  43. abuseListForAdminsValidator,
  44. asyncMiddleware(listAbusesForAdmins)
  45. )
  46. abuseRouter.put('/:id',
  47. authenticate,
  48. ensureUserHasRight(UserRight.MANAGE_ABUSES),
  49. asyncMiddleware(abuseUpdateValidator),
  50. asyncRetryTransactionMiddleware(updateAbuse)
  51. )
  52. abuseRouter.post('/',
  53. authenticate,
  54. asyncMiddleware(abuseReportValidator),
  55. asyncRetryTransactionMiddleware(reportAbuse)
  56. )
  57. abuseRouter.delete('/:id',
  58. authenticate,
  59. ensureUserHasRight(UserRight.MANAGE_ABUSES),
  60. asyncMiddleware(abuseGetValidator),
  61. asyncRetryTransactionMiddleware(deleteAbuse)
  62. )
  63. abuseRouter.get('/:id/messages',
  64. authenticate,
  65. asyncMiddleware(getAbuseValidator),
  66. checkAbuseValidForMessagesValidator,
  67. asyncRetryTransactionMiddleware(listAbuseMessages)
  68. )
  69. abuseRouter.post('/:id/messages',
  70. authenticate,
  71. asyncMiddleware(getAbuseValidator),
  72. checkAbuseValidForMessagesValidator,
  73. addAbuseMessageValidator,
  74. asyncRetryTransactionMiddleware(addAbuseMessage)
  75. )
  76. abuseRouter.delete('/:id/messages/:messageId',
  77. authenticate,
  78. asyncMiddleware(getAbuseValidator),
  79. checkAbuseValidForMessagesValidator,
  80. asyncMiddleware(deleteAbuseMessageValidator),
  81. asyncRetryTransactionMiddleware(deleteAbuseMessage)
  82. )
  83. // ---------------------------------------------------------------------------
  84. export {
  85. abuseRouter
  86. }
  87. // ---------------------------------------------------------------------------
  88. async function listAbusesForAdmins (req: express.Request, res: express.Response) {
  89. const user = res.locals.oauth.token.user
  90. const serverActor = await getServerActor()
  91. const resultList = await AbuseModel.listForAdminApi({
  92. start: req.query.start,
  93. count: req.query.count,
  94. sort: req.query.sort,
  95. id: req.query.id,
  96. filter: req.query.filter,
  97. predefinedReason: req.query.predefinedReason,
  98. search: req.query.search,
  99. state: req.query.state,
  100. videoIs: req.query.videoIs,
  101. searchReporter: req.query.searchReporter,
  102. searchReportee: req.query.searchReportee,
  103. searchVideo: req.query.searchVideo,
  104. searchVideoChannel: req.query.searchVideoChannel,
  105. serverAccountId: serverActor.Account.id,
  106. user
  107. })
  108. return res.json({
  109. total: resultList.total,
  110. data: resultList.data.map(d => d.toFormattedAdminJSON())
  111. })
  112. }
  113. async function updateAbuse (req: express.Request, res: express.Response) {
  114. const abuse = res.locals.abuse
  115. let stateUpdated = false
  116. if (req.body.moderationComment !== undefined) abuse.moderationComment = req.body.moderationComment
  117. if (req.body.state !== undefined) {
  118. abuse.state = req.body.state
  119. // We consider the abuse has been processed when its state change
  120. if (!abuse.processedAt) abuse.processedAt = new Date()
  121. stateUpdated = true
  122. }
  123. await sequelizeTypescript.transaction(t => {
  124. return abuse.save({ transaction: t })
  125. })
  126. if (stateUpdated === true) {
  127. AbuseModel.loadFull(abuse.id)
  128. .then(abuseFull => Notifier.Instance.notifyOnAbuseStateChange(abuseFull))
  129. .catch(err => logger.error('Cannot notify on abuse state change', { err }))
  130. }
  131. // Do not send the delete to other instances, we updated OUR copy of this abuse
  132. return res.status(HttpStatusCode.NO_CONTENT_204).end()
  133. }
  134. async function deleteAbuse (req: express.Request, res: express.Response) {
  135. const abuse = res.locals.abuse
  136. await sequelizeTypescript.transaction(t => {
  137. return abuse.destroy({ transaction: t })
  138. })
  139. // Do not send the delete to other instances, we delete OUR copy of this abuse
  140. return res.status(HttpStatusCode.NO_CONTENT_204).end()
  141. }
  142. async function reportAbuse (req: express.Request, res: express.Response) {
  143. const videoInstance = res.locals.videoAll
  144. const commentInstance = res.locals.videoCommentFull
  145. const accountInstance = res.locals.account
  146. const body: AbuseCreate = req.body
  147. const { id } = await sequelizeTypescript.transaction(async t => {
  148. const user = res.locals.oauth.token.User
  149. // Don't send abuse notification if reporter is an admin/moderator
  150. const skipNotification = user.hasRight(UserRight.MANAGE_ABUSES)
  151. const reporterAccount = await AccountModel.load(user.Account.id, t)
  152. const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r])
  153. const baseAbuse = {
  154. reporterAccountId: reporterAccount.id,
  155. reason: body.reason,
  156. state: AbuseState.PENDING,
  157. predefinedReasons
  158. }
  159. if (body.video) {
  160. return createVideoAbuse({
  161. baseAbuse,
  162. videoInstance,
  163. reporterAccount,
  164. transaction: t,
  165. startAt: body.video.startAt,
  166. endAt: body.video.endAt,
  167. skipNotification
  168. })
  169. }
  170. if (body.comment) {
  171. return createVideoCommentAbuse({
  172. baseAbuse,
  173. commentInstance,
  174. reporterAccount,
  175. transaction: t,
  176. skipNotification
  177. })
  178. }
  179. // Account report
  180. return createAccountAbuse({
  181. baseAbuse,
  182. accountInstance,
  183. reporterAccount,
  184. transaction: t,
  185. skipNotification
  186. })
  187. })
  188. return res.json({ abuse: { id } })
  189. }
  190. async function listAbuseMessages (req: express.Request, res: express.Response) {
  191. const abuse = res.locals.abuse
  192. const resultList = await AbuseMessageModel.listForApi(abuse.id)
  193. return res.json(getFormattedObjects(resultList.data, resultList.total))
  194. }
  195. async function addAbuseMessage (req: express.Request, res: express.Response) {
  196. const abuse = res.locals.abuse
  197. const user = res.locals.oauth.token.user
  198. const byModerator = abuse.reporterAccountId !== user.Account.id
  199. const abuseMessage = await AbuseMessageModel.create({
  200. message: req.body.message,
  201. byModerator,
  202. accountId: user.Account.id,
  203. abuseId: abuse.id
  204. })
  205. // If a moderator created an abuse message, we consider it as processed
  206. if (byModerator && !abuse.processedAt) {
  207. abuse.processedAt = new Date()
  208. await abuse.save()
  209. }
  210. AbuseModel.loadFull(abuse.id)
  211. .then(abuseFull => Notifier.Instance.notifyOnAbuseMessage(abuseFull, abuseMessage))
  212. .catch(err => logger.error('Cannot notify on new abuse message', { err }))
  213. return res.json({
  214. abuseMessage: {
  215. id: abuseMessage.id
  216. }
  217. })
  218. }
  219. async function deleteAbuseMessage (req: express.Request, res: express.Response) {
  220. const abuseMessage = res.locals.abuseMessage
  221. await sequelizeTypescript.transaction(t => {
  222. return abuseMessage.destroy({ transaction: t })
  223. })
  224. return res.status(HttpStatusCode.NO_CONTENT_204).end()
  225. }