From 40346ead2b0b7afa475aef057d3673b6c7574b7a Mon Sep 17 00:00:00 2001 From: Wicklow <123956049+wickloww@users.noreply.github.com> Date: Thu, 29 Jun 2023 07:48:55 +0000 Subject: 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 --- shared/server-commands/requests/requests.ts | 4 +- shared/server-commands/server/server.ts | 3 ++ shared/server-commands/shared/abstract-command.ts | 18 +++++-- shared/server-commands/videos/captions-command.ts | 4 +- shared/server-commands/videos/comments-command.ts | 12 +++-- shared/server-commands/videos/index.ts | 1 + shared/server-commands/videos/live-command.ts | 12 +++-- .../videos/video-passwords-command.ts | 55 ++++++++++++++++++++++ .../server-commands/videos/video-token-command.ts | 5 +- shared/server-commands/videos/videos-command.ts | 23 ++++++++- 10 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 shared/server-commands/videos/video-passwords-command.ts (limited to 'shared/server-commands') diff --git a/shared/server-commands/requests/requests.ts b/shared/server-commands/requests/requests.ts index e3f1817f1..8227017eb 100644 --- a/shared/server-commands/requests/requests.ts +++ b/shared/server-commands/requests/requests.ts @@ -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) diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts index 70f7a3ee2..0911e22b0 100644 --- a/shared/server-commands/server/server.ts +++ b/shared/server-commands/server/server.ts @@ -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) } } diff --git a/shared/server-commands/shared/abstract-command.ts b/shared/server-commands/shared/abstract-command.ts index ca4ffada9..463acc26b 100644 --- a/shared/server-commands/shared/abstract-command.ts +++ b/shared/server-commands/shared/abstract-command.ts @@ -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 { diff --git a/shared/server-commands/videos/captions-command.ts b/shared/server-commands/videos/captions-command.ts index 62bf9c5e6..a26fcb57d 100644 --- a/shared/server-commands/videos/captions-command.ts +++ b/shared/server-commands/videos/captions-command.ts @@ -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>({ ...options, path, + headers: this.buildVideoPasswordHeader(videoPassword), implicitToken: false, defaultExpectedStatus: HttpStatusCode.OK_200 }) diff --git a/shared/server-commands/videos/comments-command.ts b/shared/server-commands/videos/comments-command.ts index 154ec0c24..0dab1b66a 100644 --- a/shared/server-commands/videos/comments-command.ts +++ b/shared/server-commands/videos/comments-command.ts @@ -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({ @@ -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 })) diff --git a/shared/server-commands/videos/index.ts b/shared/server-commands/videos/index.ts index c17f6ef20..da36b5b6b 100644 --- a/shared/server-commands/videos/index.ts +++ b/shared/server-commands/videos/index.ts @@ -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' diff --git a/shared/server-commands/videos/live-command.ts b/shared/server-commands/videos/live-command.ts index 44d625970..6006d9fe9 100644 --- a/shared/server-commands/videos/live-command.ts +++ b/shared/server-commands/videos/live-command.ts @@ -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 } }) diff --git a/shared/server-commands/videos/video-passwords-command.ts b/shared/server-commands/videos/video-passwords-command.ts new file mode 100644 index 000000000..bf10335b4 --- /dev/null +++ b/shared/server-commands/videos/video-passwords-command.ts @@ -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>({ + ...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 + }) + } +} diff --git a/shared/server-commands/videos/video-token-command.ts b/shared/server-commands/videos/video-token-command.ts index 0531bee65..c4ed29a8c 100644 --- a/shared/server-commands/videos/video-token-command.ts +++ b/shared/server-commands/videos/video-token-command.ts @@ -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(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) diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts index b5df9c325..93ca623e1 100644 --- a/shared/server-commands/videos/videos-command.ts +++ b/shared/server-commands/videos/videos-command.ts @@ -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({ + ...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 }) } -- cgit v1.2.3