import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
import { ClientHtml } from '../../lib/client-html'
+import { CustomConfigAuditView, auditLoggerFactory } from '../../helpers/audit-logger'
const packageJSON = require('../../../../package.json')
const configRouter = express.Router()
+const auditLogger = auditLoggerFactory('config')
+
configRouter.get('/about', getAbout)
configRouter.get('/',
asyncMiddleware(getConfig)
async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
await unlinkPromise(CONFIG.CUSTOM_FILE)
+ auditLogger.delete(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new CustomConfigAuditView(customConfig())
+ )
+
reloadConfig()
ClientHtml.invalidCache()
async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
const toUpdate: CustomConfig = req.body
+ const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
// Force number conversion
toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10)
ClientHtml.invalidCache()
const data = customConfig()
+
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new CustomConfigAuditView(data),
+ oldCustomConfigAuditKeys
+ )
+
return res.json(data).end()
}
import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.model'
import { updateAvatarValidator } from '../../middlewares/validators/avatar'
import { updateActorAvatarFile } from '../../lib/avatar'
+import { auditLoggerFactory, UserAuditView } from '../../helpers/audit-logger'
+
+const auditLogger = auditLoggerFactory('users')
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const loginRateLimiter = new RateLimit({
const { user, account } = await createUserAccountAndChannel(userToCreate)
+ auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON()))
logger.info('User %s with its channel and account created.', body.username)
return res.json({
async function registerUser (req: express.Request, res: express.Response) {
const body: UserCreate = req.body
- const user = new UserModel({
+ const userToCreate = new UserModel({
username: body.username,
password: body.password,
email: body.email,
videoQuota: CONFIG.USER.VIDEO_QUOTA
})
- await createUserAccountAndChannel(user)
+ const { user } = await createUserAccountAndChannel(userToCreate)
+ auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
logger.info('User %s with its channel and account registered.', body.username)
return res.type('json').status(204).end()
await user.destroy()
+ auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON()))
+
return res.sendStatus(204)
}
const body: UserUpdateMe = req.body
const user: UserModel = res.locals.oauth.token.user
+ const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
if (body.password !== undefined) user.password = body.password
if (body.email !== undefined) user.email = body.email
await user.Account.save({ transaction: t })
await sendUpdateActor(user.Account, t)
+
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new UserAuditView(user.toFormattedJSON()),
+ oldUserAuditView
+ )
})
return res.sendStatus(204)
async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
- const account = res.locals.oauth.token.user.Account
+ const user: UserModel = res.locals.oauth.token.user
+ const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
+ const account = user.Account
const avatar = await updateActorAvatarFile(avatarPhysicalFile, account.Actor, account)
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new UserAuditView(user.toFormattedJSON()),
+ oldUserAuditView
+ )
+
return res
.json({
avatar: avatar.toFormattedJSON()
async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
const body: UserUpdate = req.body
- const user = res.locals.user as UserModel
- const roleChanged = body.role !== undefined && body.role !== user.role
+ const userToUpdate = res.locals.user as UserModel
+ const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
+ const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
- if (body.email !== undefined) user.email = body.email
- if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
- if (body.role !== undefined) user.role = body.role
+ if (body.email !== undefined) userToUpdate.email = body.email
+ if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
+ if (body.role !== undefined) userToUpdate.role = body.role
- await user.save()
+ const user = await userToUpdate.save()
// Destroy user token to refresh rights
if (roleChanged) {
- await OAuthTokenModel.deleteUserToken(user.id)
+ await OAuthTokenModel.deleteUserToken(userToUpdate.id)
}
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new UserAuditView(user.toFormattedJSON()),
+ oldUserAuditView
+ )
+
// Don't need to send this update to followers, these attributes are not propagated
return res.sendStatus(204)
import { VideoModel } from '../../models/video/video'
import { updateAvatarValidator } from '../../middlewares/validators/avatar'
import { updateActorAvatarFile } from '../../lib/avatar'
+import { auditLoggerFactory, VideoChannelAuditView } from '../../helpers/audit-logger'
+const auditLogger = auditLoggerFactory('channels')
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const videoChannelRouter = express.Router()
async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
- const videoChannel = res.locals.videoChannel
+ const videoChannel = res.locals.videoChannel as VideoChannelModel
+ const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel)
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new VideoChannelAuditView(videoChannel.toFormattedJSON()),
+ oldVideoChannelAuditKeys
+ )
+
return res
.json({
avatar: avatar.toFormattedJSON()
setAsyncActorKeys(videoChannelCreated.Actor)
.catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err }))
+ auditLogger.create(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())
+ )
logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
return res.json({
async function updateVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
const videoChannelFieldsSave = videoChannelInstance.toJSON()
+ const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
try {
const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
await sendUpdateActor(videoChannelInstanceUpdated, t)
- })
- logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
+ oldVideoChannelAuditKeys
+ )
+ logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
+ })
} catch (err) {
logger.debug('Cannot update the video channel.', { err })
await sequelizeTypescript.transaction(async t => {
await videoChannelInstance.destroy({ transaction: t })
+ auditLogger.delete(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
+ )
logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
})
import { AccountModel } from '../../../models/account/account'
import { VideoModel } from '../../../models/video/video'
import { VideoAbuseModel } from '../../../models/video/video-abuse'
+import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
+const auditLogger = auditLoggerFactory('abuse')
const abuseVideoRouter = express.Router()
abuseVideoRouter.get('/abuse',
await sequelizeTypescript.transaction(async t => {
const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
videoAbuseInstance.Video = videoInstance
+ videoAbuseInstance.Account = reporterAccount
// We send the video abuse to the origin server
if (videoInstance.isOwned() === false) {
await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t)
}
- })
- logger.info('Abuse report for video %s created.', videoInstance.name)
+ auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON()))
+ logger.info('Abuse report for video %s created.', videoInstance.name)
+ })
return res.type('json').status(204).end()
}
} from '../../../middlewares/validators/video-comments'
import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment'
+import { auditLoggerFactory, CommentAuditView } from '../../../helpers/audit-logger'
+const auditLogger = auditLoggerFactory('comments')
const videoCommentRouter = express.Router()
videoCommentRouter.get('/:videoId/comment-threads',
}, t)
})
+ auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new CommentAuditView(comment.toFormattedJSON()))
+
return res.json({
comment: comment.toFormattedJSON()
}).end()
}, t)
})
+ auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new CommentAuditView(comment.toFormattedJSON()))
+
return res.json({
comment: comment.toFormattedJSON()
}).end()
await videoCommentInstance.destroy({ transaction: t })
})
+ auditLogger.delete(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new CommentAuditView(videoCommentInstance.toFormattedJSON())
+ )
logger.info('Video comment %d deleted.', videoCommentInstance.id)
return res.type('json').status(204).end()
import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
import { processImage } from '../../../helpers/image-utils'
import { logger } from '../../../helpers/logger'
+import { auditLoggerFactory, VideoAuditView } from '../../../helpers/audit-logger'
import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
import {
CONFIG,
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
import { videoCaptionsRouter } from './captions'
+const auditLogger = auditLoggerFactory('videos')
const videosRouter = express.Router()
const reqVideoFileAdd = createReqFiles(
await federateVideoIfNeeded(video, true, t)
+ auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
return videoCreated
async function updateVideo (req: express.Request, res: express.Response) {
const videoInstance: VideoModel = res.locals.video
const videoFieldsSave = videoInstance.toJSON()
+ const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
const videoInfoToUpdate: VideoUpdate = req.body
const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE
await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
- })
- logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
+ auditLogger.update(
+ res.locals.oauth.token.User.Account.Actor.getIdentifier(),
+ new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
+ oldVideoAuditView
+ )
+ logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
+ })
} catch (err) {
// Force fields we want to update
// If the transaction is retried, sequelize will think the object has not changed
await videoInstance.destroy({ transaction: t })
})
+ auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoAuditView(videoInstance.toFormattedDetailsJSON()))
logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
return res.type('json').status(204).end()
import * as winston from 'winston'
import { CONFIG } from '../initializers'
import { jsonLoggerFormat, labelFormatter } from './logger'
-import { VideoDetails } from '../../shared'
+import { VideoDetails, User, VideoChannel, VideoAbuse } from '../../shared'
+import { VideoComment } from '../../shared/models/videos/video-comment.model'
+import { CustomConfig } from '../../shared/models/server/custom-config.model'
enum AUDIT_TYPE {
CREATE = 'create',
'support',
'commentsEnabled'
]
-class VideoAuditView extends AuditEntity {
+class VideoAuditView extends EntityAuditView {
constructor (private video: VideoDetails) {
super(videoKeysToKeep, 'video', video)
}
}
+const commentKeysToKeep = [
+ 'id',
+ 'text',
+ 'threadId',
+ 'inReplyToCommentId',
+ 'videoId',
+ 'createdAt',
+ 'updatedAt',
+ 'totalReplies',
+ 'account-id',
+ 'account-uuid',
+ 'account-name'
+]
+class CommentAuditView extends EntityAuditView {
+ constructor (private comment: VideoComment) {
+ super(commentKeysToKeep, 'comment', comment)
+ }
+}
+
+const userKeysToKeep = [
+ 'id',
+ 'username',
+ 'email',
+ 'nsfwPolicy',
+ 'autoPlayVideo',
+ 'role',
+ 'videoQuota',
+ 'createdAt',
+ 'account-id',
+ 'account-uuid',
+ 'account-name',
+ 'account-followingCount',
+ 'account-followersCount',
+ 'account-createdAt',
+ 'account-updatedAt',
+ 'account-avatar-path',
+ 'account-avatar-createdAt',
+ 'account-avatar-updatedAt',
+ 'account-displayName',
+ 'account-description',
+ 'videoChannels'
+]
+class UserAuditView extends EntityAuditView {
+ constructor (private user: User) {
+ super(userKeysToKeep, 'user', user)
+ }
+}
+
+const channelKeysToKeep = [
+ 'id',
+ 'uuid',
+ 'name',
+ 'followingCount',
+ 'followersCount',
+ 'createdAt',
+ 'updatedAt',
+ 'avatar-path',
+ 'avatar-createdAt',
+ 'avatar-updatedAt',
+ 'displayName',
+ 'description',
+ 'support',
+ 'isLocal',
+ 'ownerAccount-id',
+ 'ownerAccount-uuid',
+ 'ownerAccount-name',
+ 'ownerAccount-displayedName'
+]
+class VideoChannelAuditView extends EntityAuditView {
+ constructor (private channel: VideoChannel) {
+ super(channelKeysToKeep, 'channel', channel)
+ }
+}
+
+const videoAbuseKeysToKeep = [
+ 'id',
+ 'reason',
+ 'reporterAccount',
+ 'video-id',
+ 'video-name',
+ 'video-uuid',
+ 'createdAt'
+]
+class VideoAbuseAuditView extends EntityAuditView {
+ constructor (private videoAbuse: VideoAbuse) {
+ super(videoAbuseKeysToKeep, 'abuse', videoAbuse)
+ }
+}
+
+const customConfigKeysToKeep = [
+ 'instance-name',
+ 'instance-shortDescription',
+ 'instance-description',
+ 'instance-terms',
+ 'instance-defaultClientRoute',
+ 'instance-defaultNSFWPolicy',
+ 'instance-customizations-javascript',
+ 'instance-customizations-css',
+ 'services-twitter-username',
+ 'services-twitter-whitelisted',
+ 'cache-previews-size',
+ 'cache-captions-size',
+ 'signup-enabled',
+ 'signup-limit',
+ 'admin-email',
+ 'user-videoQuota',
+ 'transcoding-enabled',
+ 'transcoding-threads',
+ 'transcoding-resolutions'
+]
+class CustomConfigAuditView extends EntityAuditView {
+ constructor (customConfig: CustomConfig) {
+ const infos: any = customConfig
+ const resolutionsDict = infos.transcoding.resolutions
+ const resolutionsArray = []
+ Object.entries(resolutionsDict).forEach(([resolution, isEnabled]) => {
+ if (isEnabled) {
+ resolutionsArray.push(resolution)
+ }
+ })
+ infos.transcoding.resolutions = resolutionsArray
+ super(customConfigKeysToKeep, 'config', infos)
+ }
+}
+
export {
auditLoggerFactory,
- VideoAuditView
+ VideoChannelAuditView,
+ CommentAuditView,
+ UserAuditView,
+ VideoAuditView,
+ VideoAbuseAuditView,
+ CustomConfigAuditView
}
const userCreated = await userToCreate.save(userOptions)
const accountCreated = await createLocalAccountWithoutKeys(userToCreate.username, userToCreate.id, null, t)
+ userCreated.Account = accountCreated
const videoChannelDisplayName = `Default ${userCreated.username} channel`
const videoChannelInfo = {
return 'acct:' + this.preferredUsername + '@' + this.getHost()
}
+ getIdentifier () {
+ return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername
+ }
+
getHost () {
return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST
}