diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 7efc3a137..8a06bfe93 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -3,8 +3,9 @@ import RateLimit from 'express-rate-limit' import { tokensRouter } from '@server/controllers/api/users/token' import { Hooks } from '@server/lib/plugins/hooks' import { OAuthTokenModel } from '@server/models/oauth/oauth-token' -import { MUser, MUserAccountDefault } from '@server/types/models' -import { HttpStatusCode, UserAdminFlag, UserCreate, UserCreateResult, UserRegister, UserRight, UserRole, UserUpdate } from '@shared/models' +import { MUserAccountDefault } from '@server/types/models' +import { pick } from '@shared/core-utils' +import { HttpStatusCode, UserCreate, UserCreateResult, UserRegister, UserRight, UserUpdate } from '@shared/models' import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' import { logger } from '../../../helpers/logger' import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' @@ -14,7 +15,7 @@ import { sequelizeTypescript } from '../../../initializers/database' import { Emailer } from '../../../lib/emailer' import { Notifier } from '../../../lib/notifier' import { Redis } from '../../../lib/redis' -import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' +import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' import { asyncMiddleware, asyncRetryTransactionMiddleware, @@ -175,18 +176,11 @@ export { async function createUser (req: express.Request, res: express.Response) { const body: UserCreate = req.body - const userToCreate = new UserModel({ - username: body.username, - password: body.password, - email: body.email, - nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, - p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED, - autoPlayVideo: true, - role: body.role, - videoQuota: body.videoQuota, - videoQuotaDaily: body.videoQuotaDaily, - adminFlags: body.adminFlags || UserAdminFlag.NONE - }) as MUser + const userToCreate = buildUser({ + ...pick(body, [ 'username', 'password', 'email', 'role', 'videoQuota', 'videoQuotaDaily', 'adminFlags' ]), + + emailVerified: null + }) // NB: due to the validator usersAddValidator, password==='' can only be true if we can send the mail. const createPassword = userToCreate.password === '' @@ -225,16 +219,9 @@ async function createUser (req: express.Request, res: express.Response) { async function registerUser (req: express.Request, res: express.Response) { const body: UserRegister = req.body - const userToCreate = new UserModel({ - username: body.username, - password: body.password, - email: body.email, - nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, - p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED, - autoPlayVideo: true, - role: UserRole.USER, - videoQuota: CONFIG.USER.VIDEO_QUOTA, - videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, + const userToCreate = buildUser({ + ...pick(body, [ 'username', 'password', 'email' ]), + emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null }) diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index a1d621152..595abcf95 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts @@ -35,7 +35,7 @@ import { VideoImportModel } from '../../../models/video/video-import' const auditLogger = auditLoggerFactory('users') -const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) +const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) const meRouter = express.Router() diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 2f869d9b3..2454b1ec9 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts @@ -12,7 +12,6 @@ import { resetSequelizeInstance } from '../../helpers/database-utils' import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' import { logger } from '../../helpers/logger' import { getFormattedObjects } from '../../helpers/utils' -import { CONFIG } from '../../initializers/config' import { MIMETYPES } from '../../initializers/constants' import { sequelizeTypescript } from '../../initializers/database' import { sendUpdateActor } from '../../lib/activitypub/send' @@ -51,8 +50,8 @@ import { VideoChannelModel } from '../../models/video/video-channel' import { VideoPlaylistModel } from '../../models/video/video-playlist' const auditLogger = auditLoggerFactory('channels') -const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) -const reqBannerFile = createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { bannerfile: CONFIG.STORAGE.TMP_DIR }) +const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) +const reqBannerFile = createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) const videoChannelRouter = express.Router() diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index ee6c73855..1255d14c6 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts @@ -47,7 +47,7 @@ import { AccountModel } from '../../models/account/account' import { VideoPlaylistModel } from '../../models/video/video-playlist' import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' -const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) +const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) const videoPlaylistRouter = express.Router() diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 2a9a9d233..2b511a398 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts @@ -1,26 +1,19 @@ import express from 'express' +import { Hooks } from '@server/lib/plugins/hooks' import { MVideoCaption } from '@server/types/models' import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' import { createReqFiles } from '../../../helpers/express-utils' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' -import { CONFIG } from '../../../initializers/config' import { MIMETYPES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' import { VideoCaptionModel } from '../../../models/video/video-caption' -import { Hooks } from '@server/lib/plugins/hooks' -const reqVideoCaptionAdd = createReqFiles( - [ 'captionfile' ], - MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT, - { - captionfile: CONFIG.STORAGE.CAPTIONS_DIR - } -) +const reqVideoCaptionAdd = createReqFiles([ 'captionfile' ], MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) const videoCaptionsRouter = express.Router() diff --git a/server/controllers/api/videos/editor.ts b/server/controllers/api/videos/editor.ts index 61e2eb5da..588cc1a8c 100644 --- a/server/controllers/api/videos/editor.ts +++ b/server/controllers/api/videos/editor.ts @@ -1,6 +1,5 @@ import express from 'express' import { createAnyReqFiles } from '@server/helpers/express-utils' -import { CONFIG } from '@server/initializers/config' import { MIMETYPES } from '@server/initializers/constants' import { JobQueue } from '@server/lib/job-queue' import { buildTaskFileFieldname, getTaskFile } from '@server/lib/video-editor' @@ -21,7 +20,6 @@ const editorRouter = express.Router() const tasksFiles = createAnyReqFiles( MIMETYPES.VIDEO.MIMETYPE_EXT, - CONFIG.STORAGE.TMP_DIR, (req: express.Request, file: Express.Multer.File, cb: (err: Error, result?: boolean) => void) => { const body = req.body as VideoEditorCreateEdition diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index b54fa822c..44283e266 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts @@ -61,12 +61,7 @@ const videoImportsRouter = express.Router() const reqVideoFileImport = createReqFiles( [ 'thumbnailfile', 'previewfile', 'torrentfile' ], - Object.assign({}, MIMETYPES.TORRENT.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), - { - thumbnailfile: CONFIG.STORAGE.TMP_DIR, - previewfile: CONFIG.STORAGE.TMP_DIR, - torrentfile: CONFIG.STORAGE.TMP_DIR - } + { ...MIMETYPES.TORRENT.MIMETYPE_EXT, ...MIMETYPES.IMAGE.MIMETYPE_EXT } ) videoImportsRouter.post('/imports', diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts index 8b8cacff9..49cabb6f3 100644 --- a/server/controllers/api/videos/live.ts +++ b/server/controllers/api/videos/live.ts @@ -1,6 +1,5 @@ import express from 'express' import { createReqFiles } from '@server/helpers/express-utils' -import { CONFIG } from '@server/initializers/config' import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' @@ -19,14 +18,7 @@ import { VideoModel } from '../../../models/video/video' const liveRouter = express.Router() -const reqVideoFileLive = createReqFiles( - [ 'thumbnailfile', 'previewfile' ], - MIMETYPES.IMAGE.MIMETYPE_EXT, - { - thumbnailfile: CONFIG.STORAGE.TMP_DIR, - previewfile: CONFIG.STORAGE.TMP_DIR - } -) +const reqVideoFileLive = createReqFiles([ 'thumbnailfile', 'previewfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) liveRouter.post('/live', authenticate, diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts index f600847d4..8906003fc 100644 --- a/server/controllers/api/videos/update.ts +++ b/server/controllers/api/videos/update.ts @@ -11,7 +11,6 @@ import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../ import { resetSequelizeInstance } from '../../../helpers/database-utils' import { createReqFiles } from '../../../helpers/express-utils' import { logger, loggerTagsFactory } from '../../../helpers/logger' -import { CONFIG } from '../../../initializers/config' import { MIMETYPES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' @@ -26,14 +25,7 @@ const lTags = loggerTagsFactory('api', 'video') const auditLogger = auditLoggerFactory('videos') const updateRouter = express.Router() -const reqVideoFileUpdate = createReqFiles( - [ 'thumbnailfile', 'previewfile' ], - MIMETYPES.IMAGE.MIMETYPE_EXT, - { - thumbnailfile: CONFIG.STORAGE.TMP_DIR, - previewfile: CONFIG.STORAGE.TMP_DIR - } -) +const reqVideoFileUpdate = createReqFiles([ 'thumbnailfile', 'previewfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) updateRouter.put('/:id', openapiOperationDoc({ operationId: 'putVideo' }), diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts index 3c026ad1f..dd69cf238 100644 --- a/server/controllers/api/videos/upload.ts +++ b/server/controllers/api/videos/upload.ts @@ -24,9 +24,8 @@ import { HttpStatusCode, VideoCreate, VideoResolution, VideoState } from '@share import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { createReqFiles } from '../../../helpers/express-utils' -import { ffprobePromise, buildFileMetadata, getVideoStreamFPS, getVideoStreamDimensionsInfo } from '../../../helpers/ffmpeg' +import { buildFileMetadata, ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '../../../helpers/ffmpeg' import { logger, loggerTagsFactory } from '../../../helpers/logger' -import { CONFIG } from '../../../initializers/config' import { MIMETYPES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' @@ -52,21 +51,13 @@ const uploadRouter = express.Router() const reqVideoFileAdd = createReqFiles( [ 'videofile', 'thumbnailfile', 'previewfile' ], - Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), - { - videofile: CONFIG.STORAGE.TMP_DIR, - thumbnailfile: CONFIG.STORAGE.TMP_DIR, - previewfile: CONFIG.STORAGE.TMP_DIR - } + { ...MIMETYPES.VIDEO.MIMETYPE_EXT, ...MIMETYPES.IMAGE.MIMETYPE_EXT } ) const reqVideoFileAddResumable = createReqFiles( [ 'thumbnailfile', 'previewfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, - { - thumbnailfile: getResumableUploadPath(), - previewfile: getResumableUploadPath() - } + getResumableUploadPath() ) uploadRouter.post('/upload', diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 08f77966f..82dd4c178 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts @@ -68,11 +68,11 @@ function badRequest (_req: express.Request, res: express.Response) { function createReqFiles ( fieldNames: string[], mimeTypes: { [id: string]: string | string[] }, - destinations: { [fieldName: string]: string } + destination = CONFIG.STORAGE.TMP_DIR ): RequestHandler { const storage = diskStorage({ destination: (req, file, cb) => { - cb(null, destinations[file.fieldname]) + cb(null, destination) }, filename: (req, file, cb) => { @@ -93,12 +93,11 @@ function createReqFiles ( function createAnyReqFiles ( mimeTypes: { [id: string]: string | string[] }, - destinationDirectory: string, fileFilter: (req: express.Request, file: Express.Multer.File, cb: (err: Error, result: boolean) => void) => void ): RequestHandler { const storage = diskStorage({ destination: (req, file, cb) => { - cb(null, destinationDirectory) + cb(null, CONFIG.STORAGE.TMP_DIR) }, filename: (req, file, cb) => { diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index 7e321fb76..0517e0084 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts @@ -2,10 +2,9 @@ import { ensureDir, remove } from 'fs-extra' import passwordGenerator from 'password-generator' import { UserRole } from '@shared/models' import { logger } from '../helpers/logger' -import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' +import { buildUser, createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' import { ApplicationModel } from '../models/application/application' import { OAuthClientModel } from '../models/oauth/oauth-client' -import { UserModel } from '../models/user/user' import { applicationExist, clientsExist, usersExist } from './checker-after-init' import { CONFIG } from './config' import { FILES_CACHE, HLS_STREAMING_PLAYLIST_DIRECTORY, LAST_MIGRATION_VERSION, RESUMABLE_UPLOAD_DIRECTORY } from './constants' @@ -137,18 +136,15 @@ async function createOAuthAdminIfNotExist () { password = passwordGenerator(16, true) } - const userData = { + const user = buildUser({ username, email, password, role, - verified: true, - nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, - p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED, + emailVerified: true, videoQuota: -1, videoQuotaDaily: -1 - } - const user = new UserModel(userData) + }) await createUserAccountAndChannelAndPlaylist({ userToCreate: user, channelNames: undefined, validateUser: validatePassword }) logger.info('Username: ' + username) diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts index 5d68f44e9..910fdeec1 100644 --- a/server/lib/auth/oauth-model.ts +++ b/server/lib/auth/oauth-model.ts @@ -5,14 +5,14 @@ import { ActorModel } from '@server/models/actor/actor' import { MOAuthClient } from '@server/types/models' import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' import { MUser } from '@server/types/models/user/user' -import { UserAdminFlag } from '@shared/models/users/user-flag.model' +import { pick } from '@shared/core-utils' import { UserRole } from '@shared/models/users/user-role' import { logger } from '../../helpers/logger' import { CONFIG } from '../../initializers/config' import { OAuthClientModel } from '../../models/oauth/oauth-client' import { OAuthTokenModel } from '../../models/oauth/oauth-token' import { UserModel } from '../../models/user/user' -import { createUserAccountAndChannelAndPlaylist } from '../user' +import { buildUser, createUserAccountAndChannelAndPlaylist } from '../user' import { TokensCache } from './tokens-cache' type TokenInfo = { @@ -229,19 +229,13 @@ async function createUserFromExternal (pluginAuth: string, options: { const actor = await ActorModel.loadLocalByName(options.username) if (actor) return null - const userToCreate = new UserModel({ - username: options.username, + const userToCreate = buildUser({ + ...pick(options, [ 'username', 'email', 'role' ]), + + emailVerified: null, password: null, - email: options.email, - nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, - p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED, - autoPlayVideo: true, - role: options.role, - videoQuota: CONFIG.USER.VIDEO_QUOTA, - videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, - adminFlags: UserAdminFlag.NONE, pluginAuth - }) as MUser + }) const { user } = await createUserAccountAndChannelAndPlaylist({ userToCreate, diff --git a/server/lib/user.ts b/server/lib/user.ts index 3f7499296..ea755f4be 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts @@ -1,9 +1,11 @@ import { Transaction } from 'sequelize/types' +import { logger } from '@server/helpers/logger' +import { CONFIG } from '@server/initializers/config' import { UserModel } from '@server/models/user/user' import { MActorDefault } from '@server/types/models/actor' import { buildUUID } from '@shared/extra-utils' import { ActivityPubActorType } from '../../shared/models/activitypub' -import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' +import { UserAdminFlag, UserNotificationSetting, UserNotificationSettingValue, UserRole } from '../../shared/models/users' import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' import { sequelizeTypescript } from '../initializers/database' import { AccountModel } from '../models/account/account' @@ -19,10 +21,56 @@ import { buildActorInstance } from './local-actor' import { Redis } from './redis' import { createLocalVideoChannel } from './video-channel' import { createWatchLaterPlaylist } from './video-playlist' -import { logger } from '@server/helpers/logger' type ChannelNames = { name: string, displayName: string } +function buildUser (options: { + username: string + password: string + email: string + + role?: UserRole // Default to UserRole.User + adminFlags?: UserAdminFlag // Default to UserAdminFlag.NONE + + emailVerified: boolean | null + + videoQuota?: number // Default to CONFIG.USER.VIDEO_QUOTA + videoQuotaDaily?: number // Default to CONFIG.USER.VIDEO_QUOTA_DAILY + + pluginAuth?: string +}): MUser { + const { + username, + password, + email, + role = UserRole.USER, + emailVerified, + videoQuota = CONFIG.USER.VIDEO_QUOTA, + videoQuotaDaily = CONFIG.USER.VIDEO_QUOTA_DAILY, + adminFlags = UserAdminFlag.NONE, + pluginAuth + } = options + + return new UserModel({ + username, + password, + email, + + nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, + p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED, + autoPlayVideo: true, + + role, + emailVerified, + adminFlags, + + videoQuota: videoQuota, + videoQuotaDaily: videoQuotaDaily, + + pluginAuth + }) +} + async function createUserAccountAndChannelAndPlaylist (parameters: { userToCreate: MUser userDisplayName?: string @@ -118,7 +166,7 @@ async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) { const email = isPendingEmail ? user.pendingEmail : user.email const username = user.username - await Emailer.Instance.addVerifyEmailJob(username, email, url) + Emailer.Instance.addVerifyEmailJob(username, email, url) } async function getOriginalVideoFileTotalFromUser (user: MUserId) { @@ -180,7 +228,8 @@ export { createUserAccountAndChannelAndPlaylist, createLocalAccountWithoutKeys, sendVerifyUserEmail, - isAbleToUploadVideo + isAbleToUploadVideo, + buildUser } // ---------------------------------------------------------------------------