]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/user/user.ts
Merge branch 'release/5.0.0' into develop
[github/Chocobozzz/PeerTube.git] / server / models / user / user.ts
index bcf56dfa12d10dc09f6fa8ac89588d3c7cedda65..bfc9b30495a55791034fce1768802307949f02fc 100644 (file)
@@ -1,4 +1,3 @@
-import { values } from 'lodash'
 import { col, FindOptions, fn, literal, Op, QueryTypes, where, WhereOptions } from 'sequelize'
 import {
   AfterDestroy,
@@ -22,18 +21,19 @@ import {
   UpdatedAt
 } from 'sequelize-typescript'
 import { TokensCache } from '@server/lib/auth/tokens-cache'
+import { LiveQuotaStore } from '@server/lib/live'
 import {
   MMyUserFormattable,
   MUser,
   MUserDefault,
   MUserFormattable,
   MUserNotifSettingChannelDefault,
-  MUserWithNotificationSetting,
-  MVideoWithRights
+  MUserWithNotificationSetting
 } from '@server/types/models'
+import { forceNumber } from '@shared/core-utils'
 import { AttributesOnly } from '@shared/typescript-utils'
 import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
-import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models'
+import { AbuseState, MyUser, UserRight, VideoPlaylistType } from '../../../shared/models'
 import { User, UserRole } from '../../../shared/models/users'
 import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
 import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
@@ -51,7 +51,6 @@ import {
   isUserP2PEnabledValid,
   isUserPasswordValid,
   isUserRoleValid,
-  isUserUsernameValid,
   isUserVideoLanguages,
   isUserVideoQuotaDailyValid,
   isUserVideoQuotaValid,
@@ -65,7 +64,7 @@ import { ActorModel } from '../actor/actor'
 import { ActorFollowModel } from '../actor/actor-follow'
 import { ActorImageModel } from '../actor/actor-image'
 import { OAuthTokenModel } from '../oauth/oauth-token'
-import { getSort, throwIfNotValid } from '../utils'
+import { getAdminUsersSort, throwIfNotValid } from '../shared'
 import { VideoModel } from '../video/video'
 import { VideoChannelModel } from '../video/video-channel'
 import { VideoImportModel } from '../video/video-import'
@@ -76,6 +75,7 @@ import { UserNotificationSettingModel } from './user-notification-setting'
 enum ScopeNames {
   FOR_ME_API = 'FOR_ME_API',
   WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS',
+  WITH_QUOTA = 'WITH_QUOTA',
   WITH_STATS = 'WITH_STATS'
 }
 
@@ -153,7 +153,7 @@ enum ScopeNames {
       }
     ]
   },
-  [ScopeNames.WITH_STATS]: {
+  [ScopeNames.WITH_QUOTA]: {
     attributes: {
       include: [
         [
@@ -161,12 +161,31 @@ enum ScopeNames {
             '(' +
               UserModel.generateUserQuotaBaseSQL({
                 withSelect: false,
-                whereUserId: '"UserModel"."id"'
+                whereUserId: '"UserModel"."id"',
+                daily: false
               }) +
             ')'
           ),
           'videoQuotaUsed'
         ],
+        [
+          literal(
+            '(' +
+              UserModel.generateUserQuotaBaseSQL({
+                withSelect: false,
+                whereUserId: '"UserModel"."id"',
+                daily: true
+              }) +
+            ')'
+          ),
+          'videoQuotaUsedDaily'
+        ]
+      ]
+    }
+  },
+  [ScopeNames.WITH_STATS]: {
+    attributes: {
+      include: [
         [
           literal(
             '(' +
@@ -241,7 +260,6 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
   password: string
 
   @AllowNull(false)
-  @Is('UserUsername', value => throwIfNotValid(value, isUserUsernameValid, 'user name'))
   @Column
   username: string
 
@@ -263,7 +281,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
 
   @AllowNull(false)
   @Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy'))
-  @Column(DataType.ENUM(...values(NSFW_POLICY_TYPES)))
+  @Column(DataType.ENUM(...Object.values(NSFW_POLICY_TYPES)))
   nsfwPolicy: NSFWPolicyType
 
   @AllowNull(false)
@@ -386,6 +404,11 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
   @Column
   lastLoginDate: Date
 
+  @AllowNull(true)
+  @Default(null)
+  @Column
+  otpSecret: string
+
   @CreatedAt
   createdAt: Date
 
@@ -418,16 +441,17 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
   })
   OAuthTokens: OAuthTokenModel[]
 
+  // Used if we already set an encrypted password in user model
+  skipPasswordEncryption = false
+
   @BeforeCreate
   @BeforeUpdate
-  static cryptPasswordIfNeeded (instance: UserModel) {
-    if (instance.changed('password') && instance.password) {
-      return cryptPassword(instance.password)
-        .then(hash => {
-          instance.password = hash
-          return undefined
-        })
-    }
+  static async cryptPasswordIfNeeded (instance: UserModel) {
+    if (instance.skipPasswordEncryption) return
+    if (!instance.changed('password')) return
+    if (!instance.password) return
+
+    instance.password = await cryptPassword(instance.password)
   }
 
   @AfterUpdate
@@ -437,10 +461,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
   }
 
   static countTotal () {
-    return this.count()
+    return UserModel.unscoped().count()
   }
 
-  static listForApi (parameters: {
+  static listForAdminApi (parameters: {
     start: number
     count: number
     sort: string
@@ -468,36 +492,19 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
     }
 
     if (blocked !== undefined) {
-      Object.assign(where, {
-        blocked: blocked
-      })
+      Object.assign(where, { blocked })
     }
 
     const query: FindOptions = {
-      attributes: {
-        include: [
-          [
-            literal(
-              '(' +
-                UserModel.generateUserQuotaBaseSQL({
-                  withSelect: false,
-                  whereUserId: '"UserModel"."id"'
-                }) +
-              ')'
-            ),
-            'videoQuotaUsed'
-          ]
-        ]
-      },
       offset: start,
       limit: count,
-      order: getSort(sort),
+      order: getAdminUsersSort(sort),
       where
     }
 
     return Promise.all([
       UserModel.unscoped().count(query),
-      UserModel.findAll(query)
+      UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA ]).findAll(query)
     ]).then(([ total, data ]) => ({ total, data }))
   }
 
@@ -579,7 +586,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
       ScopeNames.WITH_VIDEOCHANNELS
     ]
 
-    if (withStats) scopes.push(ScopeNames.WITH_STATS)
+    if (withStats) {
+      scopes.push(ScopeNames.WITH_QUOTA)
+      scopes.push(ScopeNames.WITH_STATS)
+    }
 
     return UserModel.scope(scopes).findByPk(id)
   }
@@ -760,10 +770,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
   static generateUserQuotaBaseSQL (options: {
     whereUserId: '$userId' | '"UserModel"."id"'
     withSelect: boolean
-    where?: string
+    daily: boolean
   }) {
-    const andWhere = options.where
-      ? 'AND ' + options.where
+    const andWhere = options.daily === true
+      ? 'AND "video"."createdAt" > now() - interval \'24 hours\''
       : ''
 
     const videoChannelJoin = 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
@@ -810,10 +820,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
         }
       }
 
-      return UserModel.count(query)
+      return UserModel.unscoped().count(query)
     }
 
-    const totalUsers = await UserModel.count()
+    const totalUsers = await UserModel.unscoped().count()
     const totalDailyActiveUsers = await getActiveUsers(1)
     const totalWeeklyActiveUsers = await getActiveUsers(7)
     const totalMonthlyActiveUsers = await getActiveUsers(30)
@@ -842,22 +852,6 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
                     .then(u => u.map(u => u.username))
   }
 
-  canGetVideo (video: MVideoWithRights) {
-    const videoUserId = video.VideoChannel.Account.userId
-
-    if (video.isBlacklisted()) {
-      return videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
-    }
-
-    if (video.privacy === VideoPrivacy.PRIVATE) {
-      return video.VideoChannel && videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
-    }
-
-    if (video.privacy === VideoPrivacy.INTERNAL) return true
-
-    return false
-  }
-
   hasRight (right: UserRight) {
     return hasUserRight(this.role, right)
   }
@@ -899,31 +893,36 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
       autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist,
       videoLanguages: this.videoLanguages,
 
-      role: this.role,
-      roleLabel: USER_ROLE_LABELS[this.role],
+      role: {
+        id: this.role,
+        label: USER_ROLE_LABELS[this.role]
+      },
 
       videoQuota: this.videoQuota,
       videoQuotaDaily: this.videoQuotaDaily,
+
       videoQuotaUsed: videoQuotaUsed !== undefined
-        ? parseInt(videoQuotaUsed + '', 10)
+        ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
         : undefined,
+
       videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
-        ? parseInt(videoQuotaUsedDaily + '', 10)
+        ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
         : undefined,
+
       videosCount: videosCount !== undefined
-        ? parseInt(videosCount + '', 10)
+        ? forceNumber(videosCount)
         : undefined,
       abusesCount: abusesCount
-        ? parseInt(abusesCount, 10)
+        ? forceNumber(abusesCount)
         : undefined,
       abusesAcceptedCount: abusesAcceptedCount
-        ? parseInt(abusesAcceptedCount, 10)
+        ? forceNumber(abusesAcceptedCount)
         : undefined,
       abusesCreatedCount: abusesCreatedCount !== undefined
-        ? parseInt(abusesCreatedCount + '', 10)
+        ? forceNumber(abusesCreatedCount)
         : undefined,
       videoCommentsCount: videoCommentsCount !== undefined
-        ? parseInt(videoCommentsCount + '', 10)
+        ? forceNumber(videoCommentsCount)
         : undefined,
 
       noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,
@@ -945,7 +944,9 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
 
       pluginAuth: this.pluginAuth,
 
-      lastLoginDate: this.lastLoginDate
+      lastLoginDate: this.lastLoginDate,
+
+      twoFactorEnabled: !!this.otpSecret
     }
 
     if (parameters.withAdminFlags) {