1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-04 10:19:35 +02:00

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
This commit is contained in:
Wicklow 2023-06-29 07:48:55 +00:00 committed by GitHub
parent ae22c59f14
commit 40346ead2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
122 changed files with 2631 additions and 251 deletions

View file

@ -29,6 +29,7 @@ function makeRawRequest (options: {
range?: string
query?: { [ id: string ]: string }
method?: 'GET' | 'POST'
headers?: { [ name: string ]: string }
}) {
const { host, protocol, pathname } = new URL(options.url)
@ -37,7 +38,7 @@ function makeRawRequest (options: {
path: pathname,
contentType: undefined,
...pick(options, [ 'expectedStatus', 'range', 'token', 'query' ])
...pick(options, [ 'expectedStatus', 'range', 'token', 'query', 'headers' ])
}
if (options.method === 'POST') {
@ -132,6 +133,7 @@ function makePutBodyRequest (options: {
token?: string
fields: { [ fieldName: string ]: any }
expectedStatus?: HttpStatusCode
headers?: { [name: string]: string }
}) {
const req = request(options.url).put(options.path)
.send(options.fields)

View file

@ -32,6 +32,7 @@ import {
HistoryCommand,
ImportsCommand,
LiveCommand,
VideoPasswordsCommand,
PlaylistsCommand,
ServicesCommand,
StreamingPlaylistsCommand,
@ -146,6 +147,7 @@ export class PeerTubeServer {
twoFactor?: TwoFactorCommand
videoToken?: VideoTokenCommand
registrations?: RegistrationsCommand
videoPasswords?: VideoPasswordsCommand
runners?: RunnersCommand
runnerRegistrationTokens?: RunnerRegistrationTokensCommand
@ -437,5 +439,6 @@ export class PeerTubeServer {
this.runners = new RunnersCommand(this)
this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this)
this.runnerJobs = new RunnerJobsCommand(this)
this.videoPasswords = new VideoPasswordsCommand(this)
}
}

View file

@ -101,25 +101,29 @@ abstract class AbstractCommand {
protected putBodyRequest (options: InternalCommonCommandOptions & {
fields?: { [ fieldName: string ]: any }
headers?: { [name: string]: string }
}) {
const { fields } = options
const { fields, headers } = options
return makePutBodyRequest({
...this.buildCommonRequestOptions(options),
fields
fields,
headers
})
}
protected postBodyRequest (options: InternalCommonCommandOptions & {
fields?: { [ fieldName: string ]: any }
headers?: { [name: string]: string }
}) {
const { fields } = options
const { fields, headers } = options
return makePostBodyRequest({
...this.buildCommonRequestOptions(options),
fields
fields,
headers
})
}
@ -206,6 +210,12 @@ abstract class AbstractCommand {
return expectedStatus !== undefined ? expectedStatus : defaultExpectedStatus
}
protected buildVideoPasswordHeader (videoPassword: string) {
return videoPassword !== undefined && videoPassword !== null
? { 'x-peertube-video-password': videoPassword }
: undefined
}
}
export {

View file

@ -34,14 +34,16 @@ export class CaptionsCommand extends AbstractCommand {
list (options: OverrideCommandOptions & {
videoId: string | number
videoPassword?: string
}) {
const { videoId } = options
const { videoId, videoPassword } = options
const path = '/api/v1/videos/' + videoId + '/captions'
return this.getRequestBody<ResultList<VideoCaption>>({
...options,
path,
headers: this.buildVideoPasswordHeader(videoPassword),
implicitToken: false,
defaultExpectedStatus: HttpStatusCode.OK_200
})

View file

@ -36,11 +36,12 @@ export class CommentsCommand extends AbstractCommand {
listThreads (options: OverrideCommandOptions & {
videoId: number | string
videoPassword?: string
start?: number
count?: number
sort?: string
}) {
const { start, count, sort, videoId } = options
const { start, count, sort, videoId, videoPassword } = options
const path = '/api/v1/videos/' + videoId + '/comment-threads'
return this.getRequestBody<VideoCommentThreads>({
@ -48,6 +49,7 @@ export class CommentsCommand extends AbstractCommand {
path,
query: { start, count, sort },
headers: this.buildVideoPasswordHeader(videoPassword),
implicitToken: false,
defaultExpectedStatus: HttpStatusCode.OK_200
})
@ -72,8 +74,9 @@ export class CommentsCommand extends AbstractCommand {
async createThread (options: OverrideCommandOptions & {
videoId: number | string
text: string
videoPassword?: string
}) {
const { videoId, text } = options
const { videoId, text, videoPassword } = options
const path = '/api/v1/videos/' + videoId + '/comment-threads'
const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({
@ -81,6 +84,7 @@ export class CommentsCommand extends AbstractCommand {
path,
fields: { text },
headers: this.buildVideoPasswordHeader(videoPassword),
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.OK_200
}))
@ -95,8 +99,9 @@ export class CommentsCommand extends AbstractCommand {
videoId: number | string
toCommentId: number
text: string
videoPassword?: string
}) {
const { videoId, toCommentId, text } = options
const { videoId, toCommentId, text, videoPassword } = options
const path = '/api/v1/videos/' + videoId + '/comments/' + toCommentId
const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({
@ -104,6 +109,7 @@ export class CommentsCommand extends AbstractCommand {
path,
fields: { text },
headers: this.buildVideoPasswordHeader(videoPassword),
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.OK_200
}))

View file

@ -17,3 +17,4 @@ export * from './video-studio-command'
export * from './video-token-command'
export * from './views-command'
export * from './videos-command'
export * from './video-passwords-command'

View file

@ -120,8 +120,13 @@ export class LiveCommand extends AbstractCommand {
saveReplay: boolean
permanentLive: boolean
privacy?: VideoPrivacy
videoPasswords?: string[]
}) {
const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC } = options
const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC, videoPasswords } = options
const replaySettings = privacy === VideoPrivacy.PASSWORD_PROTECTED
? { privacy: VideoPrivacy.PRIVATE }
: { privacy }
const { uuid } = await this.create({
...options,
@ -130,9 +135,10 @@ export class LiveCommand extends AbstractCommand {
name: 'live',
permanentLive,
saveReplay,
replaySettings: { privacy },
replaySettings,
channelId: this.server.store.channel.id,
privacy
privacy,
videoPasswords
}
})

View file

@ -0,0 +1,55 @@
import { HttpStatusCode, ResultList, VideoPassword } from '@shared/models'
import { AbstractCommand, OverrideCommandOptions } from '../shared'
export class VideoPasswordsCommand extends AbstractCommand {
list (options: OverrideCommandOptions & {
videoId: number | string
start?: number
count?: number
sort?: string
}) {
const { start, count, sort, videoId } = options
const path = '/api/v1/videos/' + videoId + '/passwords'
return this.getRequestBody<ResultList<VideoPassword>>({
...options,
path,
query: { start, count, sort },
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.OK_200
})
}
updateAll (options: OverrideCommandOptions & {
videoId: number | string
passwords: string[]
}) {
const { videoId, passwords } = options
const path = `/api/v1/videos/${videoId}/passwords`
return this.putBodyRequest({
...options,
path,
fields: { passwords },
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
})
}
remove (options: OverrideCommandOptions & {
id: number
videoId: number | string
}) {
const { id, videoId } = options
const path = `/api/v1/videos/${videoId}/passwords/${id}`
return this.deleteRequest({
...options,
path,
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
})
}
}

View file

@ -8,12 +8,14 @@ export class VideoTokenCommand extends AbstractCommand {
create (options: OverrideCommandOptions & {
videoId: number | string
videoPassword?: string
}) {
const { videoId } = options
const { videoId, videoPassword } = options
const path = '/api/v1/videos/' + videoId + '/token'
return unwrapBody<VideoToken>(this.postBodyRequest({
...options,
headers: this.buildVideoPasswordHeader(videoPassword),
path,
implicitToken: true,
@ -23,6 +25,7 @@ export class VideoTokenCommand extends AbstractCommand {
async getVideoFileToken (options: OverrideCommandOptions & {
videoId: number | string
videoPassword?: string
}) {
const { files } = await this.create(options)

View file

@ -111,8 +111,9 @@ export class VideosCommand extends AbstractCommand {
rate (options: OverrideCommandOptions & {
id: number | string
rating: UserVideoRateType
videoPassword?: string
}) {
const { id, rating } = options
const { id, rating, videoPassword } = options
const path = '/api/v1/videos/' + id + '/rate'
return this.putBodyRequest({
@ -120,6 +121,7 @@ export class VideosCommand extends AbstractCommand {
path,
fields: { rating },
headers: this.buildVideoPasswordHeader(videoPassword),
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
})
@ -151,6 +153,23 @@ export class VideosCommand extends AbstractCommand {
})
}
getWithPassword (options: OverrideCommandOptions & {
id: number | string
password?: string
}) {
const path = '/api/v1/videos/' + options.id
return this.getRequestBody<VideoDetails>({
...options,
headers:{
'x-peertube-video-password': options.password
},
path,
implicitToken: false,
defaultExpectedStatus: HttpStatusCode.OK_200
})
}
getSource (options: OverrideCommandOptions & {
id: number | string
}) {
@ -608,11 +627,13 @@ export class VideosCommand extends AbstractCommand {
nsfw?: boolean
privacy?: VideoPrivacy
fixture?: string
videoPasswords?: string[]
}) {
const attributes: VideoEdit = { name: options.name }
if (options.nsfw) attributes.nsfw = options.nsfw
if (options.privacy) attributes.privacy = options.privacy
if (options.fixture) attributes.fixture = options.fixture
if (options.videoPasswords) attributes.videoPasswords = options.videoPasswords
return this.upload({ ...options, attributes })
}