Stricter import types
Avoid forgetting to sanitize a field by specifying the sanitized object type
This commit is contained in:
parent
02596be702
commit
009d7b39ac
14 changed files with 147 additions and 107 deletions
|
@ -17,48 +17,52 @@ import { exists, isArray, isDateValid, isFileValid } from './misc.js'
|
|||
|
||||
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
|
||||
|
||||
function isVideoIncludeValid (include: VideoIncludeType) {
|
||||
export function isVideoIncludeValid (include: VideoIncludeType) {
|
||||
return exists(include) && validator.default.isInt('' + include)
|
||||
}
|
||||
|
||||
function isVideoCategoryValid (value: any) {
|
||||
export function isVideoCategoryValid (value: any) {
|
||||
return value === null || VIDEO_CATEGORIES[value] !== undefined
|
||||
}
|
||||
|
||||
function isVideoStateValid (value: any) {
|
||||
export function isVideoStateValid (value: any) {
|
||||
return exists(value) && VIDEO_STATES[value] !== undefined
|
||||
}
|
||||
|
||||
function isVideoLicenceValid (value: any) {
|
||||
export function isVideoLicenceValid (value: any) {
|
||||
return value === null || VIDEO_LICENCES[value] !== undefined
|
||||
}
|
||||
|
||||
function isVideoLanguageValid (value: any) {
|
||||
export function isVideoLanguageValid (value: any) {
|
||||
return value === null ||
|
||||
(typeof value === 'string' && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE))
|
||||
}
|
||||
|
||||
function isVideoDurationValid (value: string) {
|
||||
export function isVideoDurationValid (value: string) {
|
||||
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
|
||||
}
|
||||
|
||||
function isVideoDescriptionValid (value: string) {
|
||||
export function isVideoDescriptionValid (value: string) {
|
||||
return value === null || (exists(value) && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
|
||||
}
|
||||
|
||||
function isVideoSupportValid (value: string) {
|
||||
export function isVideoSupportValid (value: string) {
|
||||
return value === null || (exists(value) && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
|
||||
}
|
||||
|
||||
function isVideoNameValid (value: string) {
|
||||
export function isVideoNameValid (value: string) {
|
||||
return exists(value) && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
|
||||
}
|
||||
|
||||
function isVideoTagValid (tag: string) {
|
||||
export function isVideoSourceFilenameValid (value: string) {
|
||||
return exists(value) && validator.default.isLength(value, CONSTRAINTS_FIELDS.VIDEO_SOURCE.FILENAME)
|
||||
}
|
||||
|
||||
export function isVideoTagValid (tag: string) {
|
||||
return exists(tag) && validator.default.isLength(tag, VIDEOS_CONSTRAINTS_FIELDS.TAG)
|
||||
}
|
||||
|
||||
function areVideoTagsValid (tags: string[]) {
|
||||
export function areVideoTagsValid (tags: string[]) {
|
||||
return tags === null || (
|
||||
isArray(tags) &&
|
||||
validator.default.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
|
||||
|
@ -66,20 +70,20 @@ function areVideoTagsValid (tags: string[]) {
|
|||
)
|
||||
}
|
||||
|
||||
function isVideoViewsValid (value: string) {
|
||||
export function isVideoViewsValid (value: string) {
|
||||
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
|
||||
}
|
||||
|
||||
const ratingTypes = new Set(Object.values(VIDEO_RATE_TYPES))
|
||||
function isVideoRatingTypeValid (value: string) {
|
||||
export function isVideoRatingTypeValid (value: string) {
|
||||
return value === 'none' || ratingTypes.has(value as VideoRateType)
|
||||
}
|
||||
|
||||
function isVideoFileExtnameValid (value: string) {
|
||||
export function isVideoFileExtnameValid (value: string) {
|
||||
return exists(value) && (value === VIDEO_LIVE.EXTENSION || MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined)
|
||||
}
|
||||
|
||||
function isVideoFileMimeTypeValid (files: UploadFilesForCheck, field = 'videofile') {
|
||||
export function isVideoFileMimeTypeValid (files: UploadFilesForCheck, field = 'videofile') {
|
||||
return isFileValid({
|
||||
files,
|
||||
mimeTypeRegex: MIMETYPES.VIDEO.MIMETYPES_REGEX,
|
||||
|
@ -93,7 +97,7 @@ const videoImageTypes = CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME
|
|||
.join('|')
|
||||
const videoImageTypesRegex = `image/(${videoImageTypes})`
|
||||
|
||||
function isVideoImageValid (files: UploadFilesForCheck, field: string, optional = true) {
|
||||
export function isVideoImageValid (files: UploadFilesForCheck, field: string, optional = true) {
|
||||
return isFileValid({
|
||||
files,
|
||||
mimeTypeRegex: videoImageTypesRegex,
|
||||
|
@ -103,51 +107,51 @@ function isVideoImageValid (files: UploadFilesForCheck, field: string, optional
|
|||
})
|
||||
}
|
||||
|
||||
function isVideoPrivacyValid (value: number) {
|
||||
export function isVideoPrivacyValid (value: number) {
|
||||
return VIDEO_PRIVACIES[value] !== undefined
|
||||
}
|
||||
|
||||
function isVideoReplayPrivacyValid (value: number) {
|
||||
export function isVideoReplayPrivacyValid (value: number) {
|
||||
return VIDEO_PRIVACIES[value] !== undefined && value !== VideoPrivacy.PASSWORD_PROTECTED
|
||||
}
|
||||
|
||||
function isScheduleVideoUpdatePrivacyValid (value: number) {
|
||||
export function isScheduleVideoUpdatePrivacyValid (value: number) {
|
||||
return value === VideoPrivacy.UNLISTED || value === VideoPrivacy.PUBLIC || value === VideoPrivacy.INTERNAL
|
||||
}
|
||||
|
||||
function isVideoOriginallyPublishedAtValid (value: string | null) {
|
||||
export function isVideoOriginallyPublishedAtValid (value: string | null) {
|
||||
return value === null || isDateValid(value)
|
||||
}
|
||||
|
||||
function isVideoFileInfoHashValid (value: string | null | undefined) {
|
||||
export function isVideoFileInfoHashValid (value: string | null | undefined) {
|
||||
return exists(value) && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
|
||||
}
|
||||
|
||||
function isVideoFileResolutionValid (value: string) {
|
||||
export function isVideoFileResolutionValid (value: string) {
|
||||
return exists(value) && validator.default.isInt(value + '')
|
||||
}
|
||||
|
||||
function isVideoFPSResolutionValid (value: string) {
|
||||
export function isVideoFPSResolutionValid (value: string) {
|
||||
return value === null || validator.default.isInt(value + '')
|
||||
}
|
||||
|
||||
function isVideoFileSizeValid (value: string) {
|
||||
export function isVideoFileSizeValid (value: string) {
|
||||
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.FILE_SIZE)
|
||||
}
|
||||
|
||||
function isVideoMagnetUriValid (value: string) {
|
||||
export function isVideoMagnetUriValid (value: string) {
|
||||
if (!exists(value)) return false
|
||||
|
||||
const parsed = magnetUriDecode(value)
|
||||
return parsed && isVideoFileInfoHashValid(parsed.infoHash)
|
||||
}
|
||||
|
||||
function isPasswordValid (password: string) {
|
||||
export function isPasswordValid (password: string) {
|
||||
return password.length >= CONSTRAINTS_FIELDS.VIDEO_PASSWORD.LENGTH.min &&
|
||||
password.length < CONSTRAINTS_FIELDS.VIDEO_PASSWORD.LENGTH.max
|
||||
}
|
||||
|
||||
function isValidPasswordProtectedPrivacy (req: Request, res: Response) {
|
||||
export function isValidPasswordProtectedPrivacy (req: Request, res: Response) {
|
||||
const fail = (message: string) => {
|
||||
res.fail({
|
||||
status: HttpStatusCode.BAD_REQUEST_400,
|
||||
|
@ -184,35 +188,3 @@ function isValidPasswordProtectedPrivacy (req: Request, res: Response) {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isVideoCategoryValid,
|
||||
isVideoLicenceValid,
|
||||
isVideoLanguageValid,
|
||||
isVideoDescriptionValid,
|
||||
isVideoFileInfoHashValid,
|
||||
isVideoNameValid,
|
||||
areVideoTagsValid,
|
||||
isVideoFPSResolutionValid,
|
||||
isScheduleVideoUpdatePrivacyValid,
|
||||
isVideoOriginallyPublishedAtValid,
|
||||
isVideoMagnetUriValid,
|
||||
isVideoStateValid,
|
||||
isVideoIncludeValid,
|
||||
isVideoViewsValid,
|
||||
isVideoRatingTypeValid,
|
||||
isVideoFileExtnameValid,
|
||||
isVideoFileMimeTypeValid,
|
||||
isVideoDurationValid,
|
||||
isVideoTagValid,
|
||||
isVideoPrivacyValid,
|
||||
isVideoReplayPrivacyValid,
|
||||
isVideoFileResolutionValid,
|
||||
isVideoFileSizeValid,
|
||||
isVideoImageValid,
|
||||
isVideoSupportValid,
|
||||
isPasswordValid,
|
||||
isValidPasswordProtectedPrivacy
|
||||
}
|
||||
|
|
|
@ -414,6 +414,9 @@ const CONSTRAINTS_FIELDS = {
|
|||
PARTIAL_UPLOAD_SIZE: { max: 50 * 1024 * 1024 * 1024 }, // 50GB
|
||||
URL: { min: 3, max: 2000 } // Length
|
||||
},
|
||||
VIDEO_SOURCE: {
|
||||
FILENAME: { min: 1, max: 1000 } // Length
|
||||
},
|
||||
VIDEO_PLAYLISTS: {
|
||||
NAME: { min: 1, max: 120 }, // Length
|
||||
DESCRIPTION: { min: 3, max: 1000 }, // Length
|
||||
|
|
|
@ -4,16 +4,19 @@ import { loadOrCreateVideoIfAllowedForUser } from '@server/lib/model-loaders/vid
|
|||
import { userRateVideo } from '@server/lib/rate.js'
|
||||
import { VideoModel } from '@server/models/video/video.js'
|
||||
import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
|
||||
export abstract class AbstractRatesImporter <E, O> extends AbstractUserImporter <E, O> {
|
||||
export type SanitizedRateObject = { videoUrl: string }
|
||||
|
||||
export abstract class AbstractRatesImporter <ROOT_OBJECT, OBJECT> extends AbstractUserImporter <ROOT_OBJECT, OBJECT, SanitizedRateObject> {
|
||||
|
||||
protected sanitizeRate <O extends { videoUrl: string }> (data: O) {
|
||||
if (!isUrlValid(data.videoUrl)) return undefined
|
||||
|
||||
return data
|
||||
return pick(data, [ 'videoUrl' ])
|
||||
}
|
||||
|
||||
protected async importRate (data: { videoUrl: string }, rateType: VideoRateType) {
|
||||
protected async importRate (data: SanitizedRateObject, rateType: VideoRateType) {
|
||||
const videoUrl = data.videoUrl
|
||||
const videoImmutable = await loadOrCreateVideoIfAllowedForUser(videoUrl)
|
||||
|
||||
|
|
|
@ -7,7 +7,11 @@ import { dirname, resolve } from 'path'
|
|||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export abstract class AbstractUserImporter <E, O extends { archiveFiles?: Record<string, string | Record<string, string>> }> {
|
||||
export abstract class AbstractUserImporter <
|
||||
ROOT_OBJECT,
|
||||
OBJECT extends { archiveFiles?: Record<string, string | Record<string, string>> },
|
||||
SANITIZED_OBJECT
|
||||
> {
|
||||
protected user: MUserDefault
|
||||
protected extractedDirectory: string
|
||||
protected jsonFilePath: string
|
||||
|
@ -78,7 +82,7 @@ export abstract class AbstractUserImporter <E, O extends { archiveFiles?: Record
|
|||
}
|
||||
|
||||
async import () {
|
||||
const importData: E = await readJSON(this.jsonFilePath)
|
||||
const importData: ROOT_OBJECT = await readJSON(this.jsonFilePath)
|
||||
const summary = {
|
||||
duplicates: 0,
|
||||
success: 0,
|
||||
|
@ -111,9 +115,9 @@ export abstract class AbstractUserImporter <E, O extends { archiveFiles?: Record
|
|||
return summary
|
||||
}
|
||||
|
||||
protected abstract getImportObjects (object: E): O[]
|
||||
protected abstract getImportObjects (object: ROOT_OBJECT): OBJECT[]
|
||||
|
||||
protected abstract sanitize (object: O): O | undefined
|
||||
protected abstract sanitize (object: OBJECT): SANITIZED_OBJECT | undefined
|
||||
|
||||
protected abstract importObject (object: O): Awaitable<{ duplicate: boolean }>
|
||||
protected abstract importObject (object: SANITIZED_OBJECT): Awaitable<{ duplicate: boolean }>
|
||||
}
|
||||
|
|
|
@ -6,12 +6,13 @@ import { ServerModel } from '@server/models/server/server.js'
|
|||
import { AccountModel } from '@server/models/account/account.js'
|
||||
import { isValidActorHandle } from '@server/helpers/custom-validators/activitypub/actor.js'
|
||||
import { isHostValid } from '@server/helpers/custom-validators/servers.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
type ImportObject = { handle: string | null, host: string | null, archiveFiles?: never }
|
||||
|
||||
export class BlocklistImporter extends AbstractUserImporter <BlocklistExportJSON, ImportObject> {
|
||||
export class BlocklistImporter extends AbstractUserImporter <BlocklistExportJSON, ImportObject, ImportObject> {
|
||||
|
||||
protected getImportObjects (json: BlocklistExportJSON) {
|
||||
return [
|
||||
|
@ -23,7 +24,7 @@ export class BlocklistImporter extends AbstractUserImporter <BlocklistExportJSON
|
|||
protected sanitize (blocklistImportData: ImportObject) {
|
||||
if (!isValidActorHandle(blocklistImportData.handle) && !isHostValid(blocklistImportData.host)) return undefined
|
||||
|
||||
return blocklistImportData
|
||||
return pick(blocklistImportData, [ 'handle', 'host' ])
|
||||
}
|
||||
|
||||
protected async importObject (blocklistImportData: ImportObject) {
|
||||
|
|
|
@ -6,24 +6,27 @@ import { saveInTransactionWithRetries } from '@server/helpers/database-utils.js'
|
|||
import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
|
||||
import { MAccountDefault } from '@server/types/models/index.js'
|
||||
import { isUserDescriptionValid, isUserDisplayNameValid } from '@server/helpers/custom-validators/users.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export class AccountImporter extends AbstractUserImporter <AccountExportJSON, AccountExportJSON> {
|
||||
type SanitizedObject = Pick<AccountExportJSON, 'description' | 'displayName' | 'archiveFiles'>
|
||||
|
||||
export class AccountImporter extends AbstractUserImporter <AccountExportJSON, AccountExportJSON, SanitizedObject> {
|
||||
|
||||
protected getImportObjects (json: AccountExportJSON) {
|
||||
return [ json ]
|
||||
}
|
||||
|
||||
protected sanitize (blocklistImportData: AccountExportJSON) {
|
||||
if (!isUserDisplayNameValid(blocklistImportData.name)) return undefined
|
||||
if (!isUserDisplayNameValid(blocklistImportData.displayName)) return undefined
|
||||
|
||||
if (!isUserDescriptionValid(blocklistImportData.description)) blocklistImportData.description = null
|
||||
|
||||
return blocklistImportData
|
||||
return pick(blocklistImportData, [ 'displayName', 'description', 'archiveFiles' ])
|
||||
}
|
||||
|
||||
protected async importObject (accountImportData: AccountExportJSON) {
|
||||
protected async importObject (accountImportData: SanitizedObject) {
|
||||
const account = this.user.Account
|
||||
|
||||
account.name = accountImportData.displayName
|
||||
|
@ -38,7 +41,7 @@ export class AccountImporter extends AbstractUserImporter <AccountExportJSON, Ac
|
|||
return { duplicate: false }
|
||||
}
|
||||
|
||||
private async importAvatar (account: MAccountDefault, accountImportData: AccountExportJSON) {
|
||||
private async importAvatar (account: MAccountDefault, accountImportData: SanitizedObject) {
|
||||
const avatarPath = this.getSafeArchivePathOrThrow(accountImportData.archiveFiles.avatar)
|
||||
if (!avatarPath) return undefined
|
||||
|
||||
|
|
|
@ -17,23 +17,25 @@ import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
|
|||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export class ChannelsImporter extends AbstractUserImporter <ChannelExportJSON, ChannelExportJSON['channels'][0]> {
|
||||
type SanitizedObject = Pick<ChannelExportJSON['channels'][0], 'name' | 'displayName' | 'description' | 'support' | 'archiveFiles'>
|
||||
|
||||
export class ChannelsImporter extends AbstractUserImporter <ChannelExportJSON, ChannelExportJSON['channels'][0], SanitizedObject> {
|
||||
|
||||
protected getImportObjects (json: ChannelExportJSON) {
|
||||
return json.channels
|
||||
}
|
||||
|
||||
protected sanitize (blocklistImportData: ChannelExportJSON['channels'][0]) {
|
||||
if (!isVideoChannelUsernameValid(blocklistImportData.name)) return undefined
|
||||
if (!isVideoChannelDisplayNameValid(blocklistImportData.name)) return undefined
|
||||
protected sanitize (channelImportData: ChannelExportJSON['channels'][0]) {
|
||||
if (!isVideoChannelUsernameValid(channelImportData.name)) return undefined
|
||||
if (!isVideoChannelDisplayNameValid(channelImportData.displayName)) return undefined
|
||||
|
||||
if (!isVideoChannelDescriptionValid(blocklistImportData.description)) blocklistImportData.description = null
|
||||
if (!isVideoChannelSupportValid(blocklistImportData.support)) blocklistImportData.description = null
|
||||
if (!isVideoChannelDescriptionValid(channelImportData.description)) channelImportData.description = null
|
||||
if (!isVideoChannelSupportValid(channelImportData.support)) channelImportData.support = null
|
||||
|
||||
return blocklistImportData
|
||||
return pick(channelImportData, [ 'name', 'displayName', 'description', 'support', 'archiveFiles' ])
|
||||
}
|
||||
|
||||
protected async importObject (channelImportData: ChannelExportJSON['channels'][0]) {
|
||||
protected async importObject (channelImportData: SanitizedObject) {
|
||||
const account = this.user.Account
|
||||
const existingChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(channelImportData.name)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DislikesExportJSON } from '@peertube/peertube-models'
|
||||
import { AbstractRatesImporter } from './abstract-rates-importer.js'
|
||||
import { AbstractRatesImporter, SanitizedRateObject } from './abstract-rates-importer.js'
|
||||
|
||||
export class DislikesImporter extends AbstractRatesImporter <DislikesExportJSON, DislikesExportJSON['dislikes'][0]> {
|
||||
|
||||
|
@ -11,7 +11,7 @@ export class DislikesImporter extends AbstractRatesImporter <DislikesExportJSON,
|
|||
return this.sanitizeRate(o)
|
||||
}
|
||||
|
||||
protected async importObject (dislikesImportData: DislikesExportJSON['dislikes'][0]) {
|
||||
protected async importObject (dislikesImportData: SanitizedRateObject) {
|
||||
return this.importRate(dislikesImportData, 'dislike')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@ import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
|||
import { AbstractUserImporter } from './abstract-user-importer.js'
|
||||
import { JobQueue } from '@server/lib/job-queue/job-queue.js'
|
||||
import { isValidActorHandle } from '@server/helpers/custom-validators/activitypub/actor.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export class FollowingImporter extends AbstractUserImporter <FollowingExportJSON, FollowingExportJSON['following'][0]> {
|
||||
type SanitizedObject = Pick<FollowingExportJSON['following'][0], 'targetHandle'>
|
||||
|
||||
export class FollowingImporter extends AbstractUserImporter <FollowingExportJSON, FollowingExportJSON['following'][0], SanitizedObject> {
|
||||
|
||||
protected getImportObjects (json: FollowingExportJSON) {
|
||||
return json.following
|
||||
|
@ -15,10 +18,10 @@ export class FollowingImporter extends AbstractUserImporter <FollowingExportJSON
|
|||
protected sanitize (followingImportData: FollowingExportJSON['following'][0]) {
|
||||
if (!isValidActorHandle(followingImportData.targetHandle)) return undefined
|
||||
|
||||
return followingImportData
|
||||
return pick(followingImportData, [ 'targetHandle' ])
|
||||
}
|
||||
|
||||
protected async importObject (followingImportData: FollowingExportJSON['following'][0]) {
|
||||
protected async importObject (followingImportData: SanitizedObject) {
|
||||
const [ name, host ] = followingImportData.targetHandle.split('@')
|
||||
|
||||
const payload = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { LikesExportJSON } from '@peertube/peertube-models'
|
||||
import { AbstractRatesImporter } from './abstract-rates-importer.js'
|
||||
import { AbstractRatesImporter, SanitizedRateObject } from './abstract-rates-importer.js'
|
||||
|
||||
export class LikesImporter extends AbstractRatesImporter <LikesExportJSON, LikesExportJSON['likes'][0]> {
|
||||
|
||||
|
@ -11,7 +11,7 @@ export class LikesImporter extends AbstractRatesImporter <LikesExportJSON, Likes
|
|||
return this.sanitizeRate(o)
|
||||
}
|
||||
|
||||
protected async importObject (likesImportData: LikesExportJSON['likes'][0]) {
|
||||
protected async importObject (likesImportData: SanitizedRateObject) {
|
||||
return this.importRate(likesImportData, 'like')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,14 @@ import {
|
|||
import { isThemeNameValid } from '@server/helpers/custom-validators/plugins.js'
|
||||
import { isThemeRegistered } from '@server/lib/plugins/theme-utils.js'
|
||||
import { isUserNotificationSettingValid } from '@server/helpers/custom-validators/user-notifications.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export class UserSettingsImporter extends AbstractUserImporter <UserSettingsExportJSON, UserSettingsExportJSON> {
|
||||
type SanitizedObject = Pick<UserSettingsExportJSON, 'nsfwPolicy' | 'autoPlayVideo' | 'autoPlayNextVideo' | 'autoPlayNextVideo' |
|
||||
'autoPlayNextVideoPlaylist' | 'p2pEnabled' | 'videosHistoryEnabled' | 'videoLanguages' | 'theme' | 'notificationSettings'>
|
||||
|
||||
export class UserSettingsImporter extends AbstractUserImporter <UserSettingsExportJSON, UserSettingsExportJSON, SanitizedObject> {
|
||||
|
||||
protected getImportObjects (json: UserSettingsExportJSON) {
|
||||
return [ json ]
|
||||
|
@ -27,7 +31,6 @@ export class UserSettingsImporter extends AbstractUserImporter <UserSettingsExpo
|
|||
|
||||
protected sanitize (o: UserSettingsExportJSON) {
|
||||
if (!isUserNSFWPolicyValid(o.nsfwPolicy)) o.nsfwPolicy = undefined
|
||||
|
||||
if (!isUserAutoPlayVideoValid(o.autoPlayVideo)) o.autoPlayVideo = undefined
|
||||
if (!isUserAutoPlayNextVideoValid(o.autoPlayNextVideo)) o.autoPlayNextVideo = undefined
|
||||
if (!isUserAutoPlayNextVideoPlaylistValid(o.autoPlayNextVideoPlaylist)) o.autoPlayNextVideoPlaylist = undefined
|
||||
|
@ -40,10 +43,20 @@ export class UserSettingsImporter extends AbstractUserImporter <UserSettingsExpo
|
|||
if (!isUserNotificationSettingValid(o.notificationSettings[key])) (o.notificationSettings[key] as any) = undefined
|
||||
}
|
||||
|
||||
return o
|
||||
return pick(o, [
|
||||
'nsfwPolicy',
|
||||
'autoPlayVideo',
|
||||
'autoPlayNextVideo',
|
||||
'autoPlayNextVideoPlaylist',
|
||||
'p2pEnabled',
|
||||
'videosHistoryEnabled',
|
||||
'videoLanguages',
|
||||
'theme',
|
||||
'notificationSettings'
|
||||
])
|
||||
}
|
||||
|
||||
protected async importObject (userImportData: UserSettingsExportJSON) {
|
||||
protected async importObject (userImportData: SanitizedObject) {
|
||||
if (exists(userImportData.nsfwPolicy)) this.user.nsfwPolicy = userImportData.nsfwPolicy
|
||||
if (exists(userImportData.autoPlayVideo)) this.user.autoPlayVideo = userImportData.autoPlayVideo
|
||||
if (exists(userImportData.autoPlayNextVideo)) this.user.autoPlayNextVideo = userImportData.autoPlayNextVideo
|
||||
|
|
|
@ -27,16 +27,21 @@ import { isActorPreferredUsernameValid } from '@server/helpers/custom-validators
|
|||
import { saveInTransactionWithRetries } from '@server/helpers/database-utils.js'
|
||||
import { isArray } from '@server/helpers/custom-validators/misc.js'
|
||||
import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylistsExportJSON, VideoPlaylistsExportJSON['videoPlaylists'][0]> {
|
||||
type ImportObject = VideoPlaylistsExportJSON['videoPlaylists'][0]
|
||||
type SanitizedObject = Pick<ImportObject, 'type' | 'displayName' | 'privacy' | 'elements' | 'description' | 'elements' | 'channel' |
|
||||
'archiveFiles'>
|
||||
|
||||
export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylistsExportJSON, ImportObject, SanitizedObject> {
|
||||
|
||||
protected getImportObjects (json: VideoPlaylistsExportJSON) {
|
||||
return json.videoPlaylists
|
||||
}
|
||||
|
||||
protected sanitize (o: VideoPlaylistsExportJSON['videoPlaylists'][0]) {
|
||||
protected sanitize (o: ImportObject) {
|
||||
if (!isVideoPlaylistTypeValid(o.type)) return undefined
|
||||
if (!isVideoPlaylistNameValid(o.displayName)) return undefined
|
||||
if (!isVideoPlaylistPrivacyValid(o.privacy)) return undefined
|
||||
|
@ -53,10 +58,10 @@ export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylists
|
|||
return true
|
||||
})
|
||||
|
||||
return o
|
||||
return pick(o, [ 'type', 'displayName', 'privacy', 'elements', 'channel', 'description', 'archiveFiles' ])
|
||||
}
|
||||
|
||||
protected async importObject (playlistImportData: VideoPlaylistsExportJSON['videoPlaylists'][0]) {
|
||||
protected async importObject (playlistImportData: SanitizedObject) {
|
||||
const existingPlaylist = await VideoPlaylistModel.loadRegularByAccountAndName(this.user.Account, playlistImportData.displayName)
|
||||
|
||||
if (existingPlaylist) {
|
||||
|
@ -77,7 +82,7 @@ export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylists
|
|||
return { duplicate: false }
|
||||
}
|
||||
|
||||
private async createPlaylist (playlistImportData: VideoPlaylistsExportJSON['videoPlaylists'][0]) {
|
||||
private async createPlaylist (playlistImportData: SanitizedObject) {
|
||||
let videoChannel: MChannelBannerAccountDefault
|
||||
|
||||
if (playlistImportData.channel.name) {
|
||||
|
@ -115,7 +120,7 @@ export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylists
|
|||
return VideoPlaylistModel.loadWatchLaterOf(this.user.Account)
|
||||
}
|
||||
|
||||
private async createThumbnail (playlist: MVideoPlaylistThumbnail, playlistImportData: VideoPlaylistsExportJSON['videoPlaylists'][0]) {
|
||||
private async createThumbnail (playlist: MVideoPlaylistThumbnail, playlistImportData: SanitizedObject) {
|
||||
const thumbnailPath = this.getSafeArchivePathOrThrow(playlistImportData.archiveFiles.thumbnail)
|
||||
if (!thumbnailPath) return undefined
|
||||
|
||||
|
@ -130,7 +135,7 @@ export class VideoPlaylistsImporter extends AbstractUserImporter <VideoPlaylists
|
|||
await playlist.setAndSaveThumbnail(thumbnail, undefined)
|
||||
}
|
||||
|
||||
private async createElements (playlist: MVideoPlaylist, playlistImportData: VideoPlaylistsExportJSON['videoPlaylists'][0]) {
|
||||
private async createElements (playlist: MVideoPlaylist, playlistImportData: SanitizedObject) {
|
||||
const elementsToCreate: { videoId: number, startTimestamp: number, stopTimestamp: number }[] = []
|
||||
|
||||
for (const element of playlistImportData.elements.slice(0, USER_IMPORT.MAX_PLAYLIST_ELEMENTS)) {
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
isVideoOriginallyPublishedAtValid,
|
||||
isVideoPrivacyValid,
|
||||
isVideoReplayPrivacyValid,
|
||||
isVideoSourceFilenameValid,
|
||||
isVideoSupportValid,
|
||||
isVideoTagValid
|
||||
} from '@server/helpers/custom-validators/videos.js'
|
||||
|
@ -50,13 +51,18 @@ import { isLocalVideoFileAccepted } from '@server/lib/moderation.js'
|
|||
|
||||
const lTags = loggerTagsFactory('user-import')
|
||||
|
||||
export class VideosImporter extends AbstractUserImporter <VideoExportJSON, VideoExportJSON['videos'][0]> {
|
||||
type ImportObject = VideoExportJSON['videos'][0]
|
||||
type SanitizedObject = Pick<ImportObject, 'name' | 'duration' | 'channel' | 'privacy' | 'archiveFiles' | 'captions' | 'category' |
|
||||
'licence' | 'language' | 'description' | 'support' | 'nsfw' | 'isLive' | 'commentsEnabled' | 'downloadEnabled' | 'waitTranscoding' |
|
||||
'originallyPublishedAt' | 'tags' | 'live' | 'passwords' | 'source'>
|
||||
|
||||
export class VideosImporter extends AbstractUserImporter <VideoExportJSON, ImportObject, SanitizedObject> {
|
||||
|
||||
protected getImportObjects (json: VideoExportJSON) {
|
||||
return json.videos
|
||||
}
|
||||
|
||||
protected sanitize (o: VideoExportJSON['videos'][0]) {
|
||||
protected sanitize (o: ImportObject) {
|
||||
if (!isVideoNameValid(o.name)) return undefined
|
||||
if (!isVideoDurationValid(o.duration + '')) return undefined
|
||||
if (!isVideoChannelUsernameValid(o.channel?.name)) return undefined
|
||||
|
@ -75,6 +81,8 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Video
|
|||
if (!isBooleanValid(o.downloadEnabled)) o.downloadEnabled = CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED
|
||||
if (!isBooleanValid(o.waitTranscoding)) o.waitTranscoding = true
|
||||
|
||||
if (!isVideoSourceFilenameValid(o.source?.filename)) o.source = undefined
|
||||
|
||||
if (!isVideoOriginallyPublishedAtValid(o.originallyPublishedAt)) o.originallyPublishedAt = null
|
||||
|
||||
if (!isArray(o.tags)) o.tags = []
|
||||
|
@ -102,10 +110,32 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Video
|
|||
if (o.passwords.some(p => !isPasswordValid(p))) return undefined
|
||||
}
|
||||
|
||||
return o
|
||||
return pick(o, [
|
||||
'name',
|
||||
'duration',
|
||||
'channel',
|
||||
'privacy',
|
||||
'archiveFiles',
|
||||
'category',
|
||||
'licence',
|
||||
'language',
|
||||
'description',
|
||||
'support',
|
||||
'nsfw',
|
||||
'isLive',
|
||||
'commentsEnabled',
|
||||
'downloadEnabled',
|
||||
'waitTranscoding',
|
||||
'originallyPublishedAt',
|
||||
'tags',
|
||||
'captions',
|
||||
'live',
|
||||
'passwords',
|
||||
'source'
|
||||
])
|
||||
}
|
||||
|
||||
protected async importObject (videoImportData: VideoExportJSON['videos'][0]) {
|
||||
protected async importObject (videoImportData: SanitizedObject) {
|
||||
const videoFilePath = this.getSafeArchivePathOrThrow(videoImportData.archiveFiles.videoFile)
|
||||
const videoSize = await getFileSize(videoFilePath)
|
||||
|
||||
|
@ -247,7 +277,7 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Video
|
|||
return { duplicate: false }
|
||||
}
|
||||
|
||||
private async importCaptions (video: MVideoFullLight, videoImportData: VideoExportJSON['videos'][0]) {
|
||||
private async importCaptions (video: MVideoFullLight, videoImportData: SanitizedObject) {
|
||||
const captionPaths: string[] = []
|
||||
|
||||
for (const captionImport of videoImportData.captions) {
|
||||
|
@ -284,7 +314,7 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Video
|
|||
videoFilePath: string
|
||||
size: number
|
||||
channel: MChannelId
|
||||
videoImportData: VideoExportJSON['videos'][0]
|
||||
videoImportData: SanitizedObject
|
||||
}) {
|
||||
const { videoFilePath, size, videoImportData, channel } = options
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
isVideoNameValid,
|
||||
isVideoOriginallyPublishedAtValid,
|
||||
isVideoPrivacyValid,
|
||||
isVideoSourceFilenameValid,
|
||||
isVideoSupportValid
|
||||
} from '../../../helpers/custom-validators/videos.js'
|
||||
import { cleanUpReqFiles } from '../../../helpers/express-utils.js'
|
||||
|
@ -133,7 +134,7 @@ const videosAddResumableValidator = [
|
|||
*/
|
||||
const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
|
||||
body('filename')
|
||||
.exists(),
|
||||
.custom(isVideoSourceFilenameValid),
|
||||
body('name')
|
||||
.trim()
|
||||
.custom(isVideoNameValid).withMessage(
|
||||
|
|
Loading…
Reference in a new issue