audit-logger.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import * as path from 'path'
  2. import * as express from 'express'
  3. import { diff } from 'deep-object-diff'
  4. import { chain } from 'lodash'
  5. import * as flatten from 'flat'
  6. import * as winston from 'winston'
  7. import { jsonLoggerFormat, labelFormatter } from './logger'
  8. import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../../shared'
  9. import { VideoComment } from '../../shared/models/videos/video-comment.model'
  10. import { CustomConfig } from '../../shared/models/server/custom-config.model'
  11. import { CONFIG } from '../initializers/config'
  12. function getAuditIdFromRes (res: express.Response) {
  13. return res.locals.oauth.token.User.username
  14. }
  15. enum AUDIT_TYPE {
  16. CREATE = 'create',
  17. UPDATE = 'update',
  18. DELETE = 'delete'
  19. }
  20. const colors = winston.config.npm.colors
  21. colors.audit = winston.config.npm.colors.info
  22. winston.addColors(colors)
  23. const auditLogger = winston.createLogger({
  24. levels: { audit: 0 },
  25. transports: [
  26. new winston.transports.File({
  27. filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube-audit.log'),
  28. level: 'audit',
  29. maxsize: 5242880,
  30. maxFiles: 5,
  31. format: winston.format.combine(
  32. winston.format.timestamp(),
  33. labelFormatter,
  34. winston.format.splat(),
  35. jsonLoggerFormat
  36. )
  37. })
  38. ],
  39. exitOnError: true
  40. })
  41. function auditLoggerWrapper (domain: string, user: string, action: AUDIT_TYPE, entity: EntityAuditView, oldEntity: EntityAuditView = null) {
  42. let entityInfos: object
  43. if (action === AUDIT_TYPE.UPDATE && oldEntity) {
  44. const oldEntityKeys = oldEntity.toLogKeys()
  45. const diffObject = diff(oldEntityKeys, entity.toLogKeys())
  46. const diffKeys = Object.entries(diffObject).reduce((newKeys, entry) => {
  47. newKeys[`new-${entry[0]}`] = entry[1]
  48. return newKeys
  49. }, {})
  50. entityInfos = { ...oldEntityKeys, ...diffKeys }
  51. } else {
  52. entityInfos = { ...entity.toLogKeys() }
  53. }
  54. auditLogger.log('audit', JSON.stringify({
  55. user,
  56. domain,
  57. action,
  58. ...entityInfos
  59. }))
  60. }
  61. function auditLoggerFactory (domain: string) {
  62. return {
  63. create (user: string, entity: EntityAuditView) {
  64. auditLoggerWrapper(domain, user, AUDIT_TYPE.CREATE, entity)
  65. },
  66. update (user: string, entity: EntityAuditView, oldEntity: EntityAuditView) {
  67. auditLoggerWrapper(domain, user, AUDIT_TYPE.UPDATE, entity, oldEntity)
  68. },
  69. delete (user: string, entity: EntityAuditView) {
  70. auditLoggerWrapper(domain, user, AUDIT_TYPE.DELETE, entity)
  71. }
  72. }
  73. }
  74. abstract class EntityAuditView {
  75. constructor (private keysToKeep: Array<string>, private prefix: string, private entityInfos: object) { }
  76. toLogKeys (): object {
  77. return chain(flatten(this.entityInfos, { delimiter: '-', safe: true }))
  78. .pick(this.keysToKeep)
  79. .mapKeys((value, key) => `${this.prefix}-${key}`)
  80. .value()
  81. }
  82. }
  83. const videoKeysToKeep = [
  84. 'tags',
  85. 'uuid',
  86. 'id',
  87. 'uuid',
  88. 'createdAt',
  89. 'updatedAt',
  90. 'publishedAt',
  91. 'category',
  92. 'licence',
  93. 'language',
  94. 'privacy',
  95. 'description',
  96. 'duration',
  97. 'isLocal',
  98. 'name',
  99. 'thumbnailPath',
  100. 'previewPath',
  101. 'nsfw',
  102. 'waitTranscoding',
  103. 'account-id',
  104. 'account-uuid',
  105. 'account-name',
  106. 'channel-id',
  107. 'channel-uuid',
  108. 'channel-name',
  109. 'support',
  110. 'commentsEnabled',
  111. 'downloadEnabled'
  112. ]
  113. class VideoAuditView extends EntityAuditView {
  114. constructor (private video: VideoDetails) {
  115. super(videoKeysToKeep, 'video', video)
  116. }
  117. }
  118. const videoImportKeysToKeep = [
  119. 'id',
  120. 'targetUrl',
  121. 'video-name'
  122. ]
  123. class VideoImportAuditView extends EntityAuditView {
  124. constructor (private videoImport: VideoImport) {
  125. super(videoImportKeysToKeep, 'video-import', videoImport)
  126. }
  127. }
  128. const commentKeysToKeep = [
  129. 'id',
  130. 'text',
  131. 'threadId',
  132. 'inReplyToCommentId',
  133. 'videoId',
  134. 'createdAt',
  135. 'updatedAt',
  136. 'totalReplies',
  137. 'account-id',
  138. 'account-uuid',
  139. 'account-name'
  140. ]
  141. class CommentAuditView extends EntityAuditView {
  142. constructor (private comment: VideoComment) {
  143. super(commentKeysToKeep, 'comment', comment)
  144. }
  145. }
  146. const userKeysToKeep = [
  147. 'id',
  148. 'username',
  149. 'email',
  150. 'nsfwPolicy',
  151. 'autoPlayVideo',
  152. 'role',
  153. 'videoQuota',
  154. 'createdAt',
  155. 'account-id',
  156. 'account-uuid',
  157. 'account-name',
  158. 'account-followingCount',
  159. 'account-followersCount',
  160. 'account-createdAt',
  161. 'account-updatedAt',
  162. 'account-avatar-path',
  163. 'account-avatar-createdAt',
  164. 'account-avatar-updatedAt',
  165. 'account-displayName',
  166. 'account-description',
  167. 'videoChannels'
  168. ]
  169. class UserAuditView extends EntityAuditView {
  170. constructor (private user: User) {
  171. super(userKeysToKeep, 'user', user)
  172. }
  173. }
  174. const channelKeysToKeep = [
  175. 'id',
  176. 'uuid',
  177. 'name',
  178. 'followingCount',
  179. 'followersCount',
  180. 'createdAt',
  181. 'updatedAt',
  182. 'avatar-path',
  183. 'avatar-createdAt',
  184. 'avatar-updatedAt',
  185. 'displayName',
  186. 'description',
  187. 'support',
  188. 'isLocal',
  189. 'ownerAccount-id',
  190. 'ownerAccount-uuid',
  191. 'ownerAccount-name',
  192. 'ownerAccount-displayedName'
  193. ]
  194. class VideoChannelAuditView extends EntityAuditView {
  195. constructor (private channel: VideoChannel) {
  196. super(channelKeysToKeep, 'channel', channel)
  197. }
  198. }
  199. const videoAbuseKeysToKeep = [
  200. 'id',
  201. 'reason',
  202. 'reporterAccount',
  203. 'video-id',
  204. 'video-name',
  205. 'video-uuid',
  206. 'createdAt'
  207. ]
  208. class VideoAbuseAuditView extends EntityAuditView {
  209. constructor (private videoAbuse: VideoAbuse) {
  210. super(videoAbuseKeysToKeep, 'abuse', videoAbuse)
  211. }
  212. }
  213. const customConfigKeysToKeep = [
  214. 'instance-name',
  215. 'instance-shortDescription',
  216. 'instance-description',
  217. 'instance-terms',
  218. 'instance-defaultClientRoute',
  219. 'instance-defaultNSFWPolicy',
  220. 'instance-customizations-javascript',
  221. 'instance-customizations-css',
  222. 'services-twitter-username',
  223. 'services-twitter-whitelisted',
  224. 'cache-previews-size',
  225. 'cache-captions-size',
  226. 'signup-enabled',
  227. 'signup-limit',
  228. 'signup-requiresEmailVerification',
  229. 'admin-email',
  230. 'user-videoQuota',
  231. 'transcoding-enabled',
  232. 'transcoding-threads',
  233. 'transcoding-resolutions'
  234. ]
  235. class CustomConfigAuditView extends EntityAuditView {
  236. constructor (customConfig: CustomConfig) {
  237. const infos: any = customConfig
  238. const resolutionsDict = infos.transcoding.resolutions
  239. const resolutionsArray = []
  240. Object.entries(resolutionsDict).forEach(([resolution, isEnabled]) => {
  241. if (isEnabled) resolutionsArray.push(resolution)
  242. })
  243. Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } })
  244. super(customConfigKeysToKeep, 'config', infos)
  245. }
  246. }
  247. export {
  248. getAuditIdFromRes,
  249. auditLoggerFactory,
  250. VideoImportAuditView,
  251. VideoChannelAuditView,
  252. CommentAuditView,
  253. UserAuditView,
  254. VideoAuditView,
  255. VideoAbuseAuditView,
  256. CustomConfigAuditView
  257. }