aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
authorWicklow <123956049+wickloww@users.noreply.github.com>2023-06-29 07:48:55 +0000
committerGitHub <noreply@github.com>2023-06-29 09:48:55 +0200
commit40346ead2b0b7afa475aef057d3673b6c7574b7a (patch)
tree24ffdc23c3a9d987334842e0d400b5bd44500cf7 /server/models
parentae22c59f14d0d553f60b281948b6c232c2aca178 (diff)
downloadPeerTube-40346ead2b0b7afa475aef057d3673b6c7574b7a.tar.gz
PeerTube-40346ead2b0b7afa475aef057d3673b6c7574b7a.tar.zst
PeerTube-40346ead2b0b7afa475aef057d3673b6c7574b7a.zip
Feature/password protected videos (#5836)
* Add server endpoints * Refactoring test suites * Update server and add openapi documentation * fix compliation and tests * upload/import password protected video on client * add server error code * Add video password to update resolver * add custom message when sharing pw protected video * improve confirm component * Add new alert in component * Add ability to watch protected video on client * Cannot have password protected replay privacy * Add migration * Add tests * update after review * Update check params tests * Add live videos test * Add more filter test * Update static file privacy test * Update object storage tests * Add test on feeds * Add missing word * Fix tests * Fix tests on live videos * add embed support on password protected videos * fix style * Correcting data leaks * Unable to add password protected privacy on replay * Updated code based on review comments * fix validator and command * Updated code based on review comments
Diffstat (limited to 'server/models')
-rw-r--r--server/models/video/video-password.ts137
-rw-r--r--server/models/video/video-playlist-element.ts5
-rw-r--r--server/models/video/video.ts18
3 files changed, 155 insertions, 5 deletions
diff --git a/server/models/video/video-password.ts b/server/models/video/video-password.ts
new file mode 100644
index 000000000..648366c3b
--- /dev/null
+++ b/server/models/video/video-password.ts
@@ -0,0 +1,137 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { VideoModel } from './video'
3import { AttributesOnly } from '@shared/typescript-utils'
4import { ResultList, VideoPassword } from '@shared/models'
5import { getSort, throwIfNotValid } from '../shared'
6import { FindOptions, Transaction } from 'sequelize'
7import { MVideoPassword } from '@server/types/models'
8import { isPasswordValid } from '@server/helpers/custom-validators/videos'
9import { pick } from '@shared/core-utils'
10
11@DefaultScope(() => ({
12 include: [
13 {
14 model: VideoModel.unscoped(),
15 required: true
16 }
17 ]
18}))
19@Table({
20 tableName: 'videoPassword',
21 indexes: [
22 {
23 fields: [ 'videoId', 'password' ],
24 unique: true
25 }
26 ]
27})
28export class VideoPasswordModel extends Model<Partial<AttributesOnly<VideoPasswordModel>>> {
29
30 @AllowNull(false)
31 @Is('VideoPassword', value => throwIfNotValid(value, isPasswordValid, 'videoPassword'))
32 @Column
33 password: string
34
35 @CreatedAt
36 createdAt: Date
37
38 @UpdatedAt
39 updatedAt: Date
40
41 @ForeignKey(() => VideoModel)
42 @Column
43 videoId: number
44
45 @BelongsTo(() => VideoModel, {
46 foreignKey: {
47 allowNull: false
48 },
49 onDelete: 'cascade'
50 })
51 Video: VideoModel
52
53 static async countByVideoId (videoId: number, t?: Transaction) {
54 const query: FindOptions = {
55 where: {
56 videoId
57 },
58 transaction: t
59 }
60
61 return VideoPasswordModel.count(query)
62 }
63
64 static async loadByIdAndVideo (options: { id: number, videoId: number, t?: Transaction }): Promise<MVideoPassword> {
65 const { id, videoId, t } = options
66 const query: FindOptions = {
67 where: {
68 id,
69 videoId
70 },
71 transaction: t
72 }
73
74 return VideoPasswordModel.findOne(query)
75 }
76
77 static async listPasswords (options: {
78 start: number
79 count: number
80 sort: string
81 videoId: number
82 }): Promise<ResultList<MVideoPassword>> {
83 const { start, count, sort, videoId } = options
84
85 const { count: total, rows: data } = await VideoPasswordModel.findAndCountAll({
86 where: { videoId },
87 order: getSort(sort),
88 offset: start,
89 limit: count
90 })
91
92 return { total, data }
93 }
94
95 static async addPasswords (passwords: string[], videoId: number, transaction?: Transaction): Promise<void> {
96 for (const password of passwords) {
97 await VideoPasswordModel.create({
98 password,
99 videoId
100 }, { transaction })
101 }
102 }
103
104 static async deleteAllPasswords (videoId: number, transaction?: Transaction) {
105 await VideoPasswordModel.destroy({
106 where: { videoId },
107 transaction
108 })
109 }
110
111 static async deletePassword (passwordId: number, transaction?: Transaction) {
112 await VideoPasswordModel.destroy({
113 where: { id: passwordId },
114 transaction
115 })
116 }
117
118 static async isACorrectPassword (options: {
119 videoId: number
120 password: string
121 }) {
122 const query = {
123 where: pick(options, [ 'videoId', 'password' ])
124 }
125 return VideoPasswordModel.findOne(query)
126 }
127
128 toFormattedJSON (): VideoPassword {
129 return {
130 id: this.id,
131 password: this.password,
132 videoId: this.videoId,
133 createdAt: this.createdAt,
134 updatedAt: this.updatedAt
135 }
136 }
137}
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index b832f9768..61ae6b9fe 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -336,7 +336,10 @@ export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<Vide
336 // Internal video? 336 // Internal video?
337 if (video.privacy === VideoPrivacy.INTERNAL && accountId) return VideoPlaylistElementType.REGULAR 337 if (video.privacy === VideoPrivacy.INTERNAL && accountId) return VideoPlaylistElementType.REGULAR
338 338
339 if (video.privacy === VideoPrivacy.PRIVATE || video.privacy === VideoPrivacy.INTERNAL) return VideoPlaylistElementType.PRIVATE 339 // Private, internal and password protected videos cannot be read without appropriate access (ownership, internal)
340 if (new Set([ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL, VideoPrivacy.PASSWORD_PROTECTED ]).has(video.privacy)) {
341 return VideoPlaylistElementType.PRIVATE
342 }
340 343
341 if (video.isBlacklisted() || video.isBlocked()) return VideoPlaylistElementType.UNAVAILABLE 344 if (video.isBlacklisted() || video.isBlocked()) return VideoPlaylistElementType.UNAVAILABLE
342 345
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 8e3af62a4..f90f2b7f6 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -136,6 +136,7 @@ import { VideoFileModel } from './video-file'
136import { VideoImportModel } from './video-import' 136import { VideoImportModel } from './video-import'
137import { VideoJobInfoModel } from './video-job-info' 137import { VideoJobInfoModel } from './video-job-info'
138import { VideoLiveModel } from './video-live' 138import { VideoLiveModel } from './video-live'
139import { VideoPasswordModel } from './video-password'
139import { VideoPlaylistElementModel } from './video-playlist-element' 140import { VideoPlaylistElementModel } from './video-playlist-element'
140import { VideoShareModel } from './video-share' 141import { VideoShareModel } from './video-share'
141import { VideoSourceModel } from './video-source' 142import { VideoSourceModel } from './video-source'
@@ -734,6 +735,15 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
734 }) 735 })
735 VideoCaptions: VideoCaptionModel[] 736 VideoCaptions: VideoCaptionModel[]
736 737
738 @HasMany(() => VideoPasswordModel, {
739 foreignKey: {
740 name: 'videoId',
741 allowNull: false
742 },
743 onDelete: 'cascade'
744 })
745 VideoPasswords: VideoPasswordModel[]
746
737 @HasOne(() => VideoJobInfoModel, { 747 @HasOne(() => VideoJobInfoModel, {
738 foreignKey: { 748 foreignKey: {
739 name: 'videoId', 749 name: 'videoId',
@@ -1918,7 +1928,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1918 1928
1919 // --------------------------------------------------------------------------- 1929 // ---------------------------------------------------------------------------
1920 1930
1921 requiresAuth (options: { 1931 requiresUserAuth (options: {
1922 urlParamId: string 1932 urlParamId: string
1923 checkBlacklist: boolean 1933 checkBlacklist: boolean
1924 }) { 1934 }) {
@@ -1936,11 +1946,11 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1936 1946
1937 if (checkBlacklist && this.VideoBlacklist) return true 1947 if (checkBlacklist && this.VideoBlacklist) return true
1938 1948
1939 if (this.privacy !== VideoPrivacy.PUBLIC) { 1949 if (this.privacy === VideoPrivacy.PUBLIC || this.privacy === VideoPrivacy.PASSWORD_PROTECTED) {
1940 throw new Error(`Unknown video privacy ${this.privacy} to know if the video requires auth`) 1950 return false
1941 } 1951 }
1942 1952
1943 return false 1953 throw new Error(`Unknown video privacy ${this.privacy} to know if the video requires auth`)
1944 } 1954 }
1945 1955
1946 hasPrivateStaticPath () { 1956 hasPrivateStaticPath () {