diff options
Diffstat (limited to 'server/helpers')
-rw-r--r-- | server/helpers/activitypub.ts | 3 | ||||
-rw-r--r-- | server/helpers/actor.ts | 9 | ||||
-rw-r--r-- | server/helpers/captions-utils.ts | 6 | ||||
-rw-r--r-- | server/helpers/custom-jsonld-signature.ts | 5 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/actor.ts | 15 | ||||
-rw-r--r-- | server/helpers/custom-validators/users.ts | 10 | ||||
-rw-r--r-- | server/helpers/custom-validators/video-ownership.ts | 22 | ||||
-rw-r--r-- | server/helpers/middlewares/accounts.ts | 7 | ||||
-rw-r--r-- | server/helpers/middlewares/video-abuses.ts | 44 | ||||
-rw-r--r-- | server/helpers/middlewares/video-captions.ts | 4 | ||||
-rw-r--r-- | server/helpers/middlewares/video-channels.ts | 45 | ||||
-rw-r--r-- | server/helpers/middlewares/video-playlists.ts | 35 | ||||
-rw-r--r-- | server/helpers/middlewares/videos.ts | 26 | ||||
-rw-r--r-- | server/helpers/peertube-crypto.ts | 126 | ||||
-rw-r--r-- | server/helpers/utils.ts | 5 | ||||
-rw-r--r-- | server/helpers/video.ts | 40 | ||||
-rw-r--r-- | server/helpers/webfinger.ts | 3 |
17 files changed, 237 insertions, 168 deletions
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 951a25669..97c809a0c 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor' | |||
7 | import { signJsonLDObject } from './peertube-crypto' | 7 | import { signJsonLDObject } from './peertube-crypto' |
8 | import { pageToStartAndCount } from './core-utils' | 8 | import { pageToStartAndCount } from './core-utils' |
9 | import { parse } from 'url' | 9 | import { parse } from 'url' |
10 | import { MActor } from '../typings/models' | ||
10 | 11 | ||
11 | function activityPubContextify <T> (data: T) { | 12 | function activityPubContextify <T> (data: T) { |
12 | return Object.assign(data, { | 13 | return Object.assign(data, { |
@@ -143,7 +144,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi | |||
143 | 144 | ||
144 | } | 145 | } |
145 | 146 | ||
146 | function buildSignedActivity (byActor: ActorModel, data: Object) { | 147 | function buildSignedActivity (byActor: MActor, data: Object) { |
147 | const activity = activityPubContextify(data) | 148 | const activity = activityPubContextify(data) |
148 | 149 | ||
149 | return signJsonLDObject(byActor, activity) as Promise<Activity> | 150 | return signJsonLDObject(byActor, activity) as Promise<Activity> |
diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts index 12a7ace9f..117548a60 100644 --- a/server/helpers/actor.ts +++ b/server/helpers/actor.ts | |||
@@ -1,10 +1,13 @@ | |||
1 | import { ActorModel } from '../models/activitypub/actor' | 1 | import { ActorModel } from '../models/activitypub/actor' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { MActorFull, MActorAccountChannelId } from '../typings/models' | ||
2 | 4 | ||
3 | type ActorFetchByUrlType = 'all' | 'actor-and-association-ids' | 5 | type ActorFetchByUrlType = 'all' | 'association-ids' |
4 | function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) { | 6 | |
7 | function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird<MActorFull | MActorAccountChannelId> { | ||
5 | if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) | 8 | if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) |
6 | 9 | ||
7 | if (fetchType === 'actor-and-association-ids') return ActorModel.loadByUrl(url) | 10 | if (fetchType === 'association-ids') return ActorModel.loadByUrl(url) |
8 | } | 11 | } |
9 | 12 | ||
10 | export { | 13 | export { |
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 7174d4654..2830ae017 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
4 | import * as srt2vtt from 'srt-to-vtt' | 3 | import * as srt2vtt from 'srt-to-vtt' |
5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' | 4 | import { createReadStream, createWriteStream, move, remove } from 'fs-extra' |
5 | import { MVideoCaptionFormattable } from '@server/typings/models' | ||
6 | 6 | ||
7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { | 7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) { |
8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR | 8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR |
9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) | 9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) |
10 | 10 | ||
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index a3bceb047..cb07fa3b2 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | 1 | import * as AsyncLRU from 'async-lru' |
2 | import * as jsonld from 'jsonld' | 2 | import * as jsonld from 'jsonld' |
3 | import * as jsig from 'jsonld-signatures' | ||
4 | import { logger } from './logger' | 3 | import { logger } from './logger' |
5 | 4 | ||
6 | const CACHE = { | 5 | const CACHE = { |
@@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => { | |||
79 | lru.get(url, cb) | 78 | lru.get(url, cb) |
80 | } | 79 | } |
81 | 80 | ||
82 | jsig.use('jsonld', jsonld) | 81 | export { jsonld } |
83 | |||
84 | export { jsig, jsonld } | ||
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index deb331abb..55bc8cc96 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) { | |||
27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) | 27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) |
28 | } | 28 | } |
29 | 29 | ||
30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' | 30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]' |
31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) | 31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) |
32 | function isActorPreferredUsernameValid (preferredUsername: string) { | 32 | function isActorPreferredUsernameValid (preferredUsername: string) { |
33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) | 33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) |
@@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) { | |||
46 | return exists(actor) && | 46 | return exists(actor) && |
47 | isActivityPubUrlValid(actor.id) && | 47 | isActivityPubUrlValid(actor.id) && |
48 | isActorTypeValid(actor.type) && | 48 | isActorTypeValid(actor.type) && |
49 | isActivityPubUrlValid(actor.following) && | ||
50 | isActivityPubUrlValid(actor.followers) && | ||
51 | isActivityPubUrlValid(actor.inbox) && | 49 | isActivityPubUrlValid(actor.inbox) && |
52 | isActivityPubUrlValid(actor.outbox) && | ||
53 | isActorPreferredUsernameValid(actor.preferredUsername) && | 50 | isActorPreferredUsernameValid(actor.preferredUsername) && |
54 | isActivityPubUrlValid(actor.url) && | 51 | isActivityPubUrlValid(actor.url) && |
55 | isActorPublicKeyObjectValid(actor.publicKey) && | 52 | isActorPublicKeyObjectValid(actor.publicKey) && |
56 | isActorEndpointsObjectValid(actor.endpoints) && | 53 | isActorEndpointsObjectValid(actor.endpoints) && |
57 | setValidAttributedTo(actor) && | ||
58 | 54 | ||
59 | // If this is not an account, it should be attributed to an account | 55 | (!actor.outbox || isActivityPubUrlValid(actor.outbox)) && |
56 | (!actor.following || isActivityPubUrlValid(actor.following)) && | ||
57 | (!actor.followers || isActivityPubUrlValid(actor.followers)) && | ||
58 | |||
59 | setValidAttributedTo(actor) && | ||
60 | // If this is a group (a channel), it should be attributed to an account | ||
60 | // In PeerTube we use this to attach a video channel to a specific account | 61 | // In PeerTube we use this to attach a video channel to a specific account |
61 | (actor.type === 'Person' || actor.attributedTo.length !== 0) | 62 | (actor.type !== 'Group' || actor.attributedTo.length !== 0) |
62 | } | 63 | } |
63 | 64 | ||
64 | function isActorFollowingCountValid (value: string) { | 65 | function isActorFollowingCountValid (value: string) { |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index c56ae14ef..68e84d9eb 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -65,6 +65,14 @@ function isUserBlockedValid (value: any) { | |||
65 | return isBooleanValid(value) | 65 | return isBooleanValid(value) |
66 | } | 66 | } |
67 | 67 | ||
68 | function isNoInstanceConfigWarningModal (value: any) { | ||
69 | return isBooleanValid(value) | ||
70 | } | ||
71 | |||
72 | function isNoWelcomeModal (value: any) { | ||
73 | return isBooleanValid(value) | ||
74 | } | ||
75 | |||
68 | function isUserBlockedReasonValid (value: any) { | 76 | function isUserBlockedReasonValid (value: any) { |
69 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) | 77 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) |
70 | } | 78 | } |
@@ -100,5 +108,7 @@ export { | |||
100 | isUserAutoPlayVideoValid, | 108 | isUserAutoPlayVideoValid, |
101 | isUserDisplayNameValid, | 109 | isUserDisplayNameValid, |
102 | isUserDescriptionValid, | 110 | isUserDescriptionValid, |
111 | isNoInstanceConfigWarningModal, | ||
112 | isNoWelcomeModal, | ||
103 | isAvatarFile | 113 | isAvatarFile |
104 | } | 114 | } |
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts index a7771e07b..9570b2799 100644 --- a/server/helpers/custom-validators/video-ownership.ts +++ b/server/helpers/custom-validators/video-ownership.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | ||
3 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' | 2 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' |
4 | import { UserModel } from '../../models/account/user' | 3 | import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership' |
4 | import { MUserId } from '@server/typings/models' | ||
5 | 5 | ||
6 | export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> { | 6 | export async function doesChangeVideoOwnershipExist (id: number, res: Response) { |
7 | const videoChangeOwnership = await loadVideoChangeOwnership(id) | 7 | const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) |
8 | 8 | ||
9 | if (!videoChangeOwnership) { | 9 | if (!videoChangeOwnership) { |
10 | res.status(404) | 10 | res.status(404) |
@@ -18,19 +18,7 @@ export async function doesChangeVideoOwnershipExist (id: string, res: Response): | |||
18 | return true | 18 | return true |
19 | } | 19 | } |
20 | 20 | ||
21 | async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> { | 21 | export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) { |
22 | if (validator.isInt(id)) { | ||
23 | return VideoChangeOwnershipModel.load(parseInt(id, 10)) | ||
24 | } | ||
25 | |||
26 | return undefined | ||
27 | } | ||
28 | |||
29 | export function checkUserCanTerminateOwnershipChange ( | ||
30 | user: UserModel, | ||
31 | videoChangeOwnership: VideoChangeOwnershipModel, | ||
32 | res: Response | ||
33 | ): boolean { | ||
34 | if (videoChangeOwnership.NextOwner.userId === user.id) { | 22 | if (videoChangeOwnership.NextOwner.userId === user.id) { |
35 | return true | 23 | return true |
36 | } | 24 | } |
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 791022b97..f5aa0bada 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { AccountModel } from '../../models/account/account' | 2 | import { AccountModel } from '../../models/account/account' |
3 | import * as Bluebird from 'bluebird' | 3 | import * as Bluebird from 'bluebird' |
4 | import { MAccountDefault } from '../../typings/models' | ||
4 | 5 | ||
5 | function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { | 6 | function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { |
6 | const promise = AccountModel.load(id) | 7 | const promise = AccountModel.load(id) |
@@ -15,10 +16,12 @@ function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = | |||
15 | } | 16 | } |
16 | 17 | ||
17 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | 18 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { |
18 | return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) | 19 | const promise = AccountModel.loadByNameWithHost(nameWithDomain) |
20 | |||
21 | return doesAccountExist(promise, res, sendNotFound) | ||
19 | } | 22 | } |
20 | 23 | ||
21 | async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { | 24 | async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, sendNotFound: boolean) { |
22 | const account = await p | 25 | const account = await p |
23 | 26 | ||
24 | if (!account) { | 27 | if (!account) { |
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts index b23f1f021..1b573ca37 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/video-abuses.ts | |||
@@ -1,41 +1,23 @@ | |||
1 | import * as express from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoChannelModel } from '../../models/video/video-channel' | 2 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
3 | 3 | ||
4 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { | 4 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { |
5 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | 5 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) |
6 | 6 | ||
7 | return processVideoChannelExist(videoChannel, res) | 7 | if (videoAbuse === null) { |
8 | } | ||
9 | |||
10 | async function doesVideoChannelIdExist (id: number, res: express.Response) { | ||
11 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | ||
12 | |||
13 | return processVideoChannelExist(videoChannel, res) | ||
14 | } | ||
15 | |||
16 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
17 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
18 | |||
19 | return processVideoChannelExist(videoChannel, res) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | doesLocalVideoChannelNameExist, | ||
26 | doesVideoChannelIdExist, | ||
27 | doesVideoChannelNameWithHostExist | ||
28 | } | ||
29 | |||
30 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | ||
31 | if (!videoChannel) { | ||
32 | res.status(404) | 8 | res.status(404) |
33 | .json({ error: 'Video channel not found' }) | 9 | .json({ error: 'Video abuse not found' }) |
34 | .end() | 10 | .end() |
35 | 11 | ||
36 | return false | 12 | return false |
37 | } | 13 | } |
38 | 14 | ||
39 | res.locals.videoChannel = videoChannel | 15 | res.locals.videoAbuse = videoAbuse |
40 | return true | 16 | return true |
41 | } | 17 | } |
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | doesVideoAbuseExist | ||
23 | } | ||
diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts index dc3d0144b..1b2513b60 100644 --- a/server/helpers/middlewares/video-captions.ts +++ b/server/helpers/middlewares/video-captions.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { VideoModel } from '../../models/video/video' | ||
2 | import { Response } from 'express' | 1 | import { Response } from 'express' |
3 | import { VideoCaptionModel } from '../../models/video/video-caption' | 2 | import { VideoCaptionModel } from '../../models/video/video-caption' |
3 | import { MVideoId } from '@server/typings/models' | ||
4 | 4 | ||
5 | async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { | 5 | async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) { |
6 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) | 6 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) |
7 | 7 | ||
8 | if (!videoCaption) { | 8 | if (!videoCaption) { |
diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts index 1b573ca37..1595ecd94 100644 --- a/server/helpers/middlewares/video-channels.ts +++ b/server/helpers/middlewares/video-channels.ts | |||
@@ -1,23 +1,42 @@ | |||
1 | import { Response } from 'express' | 1 | import * as express from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { VideoChannelModel } from '../../models/video/video-channel' |
3 | import { MChannelAccountDefault } from '@server/typings/models' | ||
3 | 4 | ||
4 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { | 5 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { |
5 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 6 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) |
6 | 7 | ||
7 | if (videoAbuse === null) { | 8 | return processVideoChannelExist(videoChannel, res) |
8 | res.status(404) | 9 | } |
9 | .json({ error: 'Video abuse not found' }) | ||
10 | .end() | ||
11 | 10 | ||
12 | return false | 11 | async function doesVideoChannelIdExist (id: number, res: express.Response) { |
13 | } | 12 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
14 | 13 | ||
15 | res.locals.videoAbuse = videoAbuse | 14 | return processVideoChannelExist(videoChannel, res) |
16 | return true | 15 | } |
16 | |||
17 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
18 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
19 | |||
20 | return processVideoChannelExist(videoChannel, res) | ||
17 | } | 21 | } |
18 | 22 | ||
19 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
20 | 24 | ||
21 | export { | 25 | export { |
22 | doesVideoAbuseExist | 26 | doesLocalVideoChannelNameExist, |
27 | doesVideoChannelIdExist, | ||
28 | doesVideoChannelNameWithHostExist | ||
29 | } | ||
30 | |||
31 | function processVideoChannelExist (videoChannel: MChannelAccountDefault, res: express.Response) { | ||
32 | if (!videoChannel) { | ||
33 | res.status(404) | ||
34 | .json({ error: 'Video channel not found' }) | ||
35 | .end() | ||
36 | |||
37 | return false | ||
38 | } | ||
39 | |||
40 | res.locals.videoChannel = videoChannel | ||
41 | return true | ||
23 | } | 42 | } |
diff --git a/server/helpers/middlewares/video-playlists.ts b/server/helpers/middlewares/video-playlists.ts index 735bf362f..8e7484483 100644 --- a/server/helpers/middlewares/video-playlists.ts +++ b/server/helpers/middlewares/video-playlists.ts | |||
@@ -1,11 +1,31 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 2 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
3 | import { MVideoPlaylist } from '../../typings/models/video/video-playlist' | ||
3 | 4 | ||
4 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { | 5 | export type VideoPlaylistFetchType = 'summary' | 'all' |
5 | const videoPlaylist = fetchType === 'summary' | 6 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') { |
6 | ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) | 7 | if (fetchType === 'summary') { |
7 | : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | 8 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) |
9 | res.locals.videoPlaylistSummary = videoPlaylist | ||
8 | 10 | ||
11 | return handleVideoPlaylist(videoPlaylist, res) | ||
12 | } | ||
13 | |||
14 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | ||
15 | res.locals.videoPlaylistFull = videoPlaylist | ||
16 | |||
17 | return handleVideoPlaylist(videoPlaylist, res) | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | doesVideoPlaylistExist | ||
24 | } | ||
25 | |||
26 | // --------------------------------------------------------------------------- | ||
27 | |||
28 | function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) { | ||
9 | if (!videoPlaylist) { | 29 | if (!videoPlaylist) { |
10 | res.status(404) | 30 | res.status(404) |
11 | .json({ error: 'Video playlist not found' }) | 31 | .json({ error: 'Video playlist not found' }) |
@@ -14,12 +34,5 @@ async function doesVideoPlaylistExist (id: number | string, res: express.Respons | |||
14 | return false | 34 | return false |
15 | } | 35 | } |
16 | 36 | ||
17 | res.locals.videoPlaylist = videoPlaylist | ||
18 | return true | 37 | return true |
19 | } | 38 | } |
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | doesVideoPlaylistExist | ||
25 | } | ||
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts index ceb1058ec..74f529804 100644 --- a/server/helpers/middlewares/videos.ts +++ b/server/helpers/middlewares/videos.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { fetchVideo, VideoFetchType } from '../video' | 2 | import { fetchVideo, VideoFetchType } from '../video' |
3 | import { UserModel } from '../../models/account/user' | ||
4 | import { UserRight } from '../../../shared/models/users' | 3 | import { UserRight } from '../../../shared/models/users' |
5 | import { VideoChannelModel } from '../../models/video/video-channel' | 4 | import { VideoChannelModel } from '../../models/video/video-channel' |
6 | import { VideoModel } from '../../models/video/video' | 5 | import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models' |
7 | 6 | ||
8 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { | 7 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
9 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 8 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
@@ -18,11 +17,28 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi | |||
18 | return false | 17 | return false |
19 | } | 18 | } |
20 | 19 | ||
21 | if (fetchType !== 'none') res.locals.video = video | 20 | switch (fetchType) { |
21 | case 'all': | ||
22 | res.locals.videoAll = video as MVideoFullLight | ||
23 | break | ||
24 | |||
25 | case 'id': | ||
26 | res.locals.videoId = video | ||
27 | break | ||
28 | |||
29 | case 'only-video': | ||
30 | res.locals.onlyVideo = video as MVideoThumbnail | ||
31 | break | ||
32 | |||
33 | case 'only-video-with-rights': | ||
34 | res.locals.onlyVideoWithRights = video as MVideoWithRights | ||
35 | break | ||
36 | } | ||
37 | |||
22 | return true | 38 | return true |
23 | } | 39 | } |
24 | 40 | ||
25 | async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { | 41 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { |
26 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | 42 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { |
27 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | 43 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) |
28 | if (videoChannel === null) { | 44 | if (videoChannel === null) { |
@@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode | |||
50 | return true | 66 | return true |
51 | } | 67 | } |
52 | 68 | ||
53 | function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) { | 69 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) { |
54 | // Retrieve the user who did the request | 70 | // Retrieve the user who did the request |
55 | if (video.isOwned() === false) { | 71 | if (video.isOwned() === false) { |
56 | res.status(403) | 72 | res.status(403) |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 1424949d0..9eb782302 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | ||
4 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' | 3 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 4 | import { jsonld } from './custom-jsonld-signature' |
6 | import { logger } from './logger' | 5 | import { logger } from './logger' |
7 | import { cloneDeep } from 'lodash' | 6 | import { cloneDeep } from 'lodash' |
8 | import { createVerify } from 'crypto' | 7 | import { createSign, createVerify } from 'crypto' |
9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' | 8 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' |
10 | import * as bcrypt from 'bcrypt' | 9 | import * as bcrypt from 'bcrypt' |
10 | import { MActor } from '../typings/models' | ||
11 | 11 | ||
12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | 12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) |
13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | 13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) |
@@ -46,7 +46,7 @@ function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { | |||
46 | return true | 46 | return true |
47 | } | 47 | } |
48 | 48 | ||
49 | function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { | 49 | function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean { |
50 | return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true | 50 | return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true |
51 | } | 51 | } |
52 | 52 | ||
@@ -56,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) { | |||
56 | 56 | ||
57 | // JSONLD | 57 | // JSONLD |
58 | 58 | ||
59 | async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> { | 59 | function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> { |
60 | if (signedDocument.signature.type === 'RsaSignature2017') { | 60 | if (signedDocument.signature.type === 'RsaSignature2017') { |
61 | // Mastodon algorithm | 61 | return isJsonLDRSA2017Verified(fromActor, signedDocument) |
62 | const res = await isJsonLDRSA2017Verified(fromActor, signedDocument) | ||
63 | // Success? If no, try with our library | ||
64 | if (res === true) return true | ||
65 | } | 62 | } |
66 | 63 | ||
67 | const publicKeyObject = { | 64 | logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument) |
68 | '@context': jsig.SECURITY_CONTEXT_URL, | ||
69 | id: fromActor.url, | ||
70 | type: 'CryptographicKey', | ||
71 | owner: fromActor.url, | ||
72 | publicKeyPem: fromActor.publicKey | ||
73 | } | ||
74 | |||
75 | const publicKeyOwnerObject = { | ||
76 | '@context': jsig.SECURITY_CONTEXT_URL, | ||
77 | id: fromActor.url, | ||
78 | publicKey: [ publicKeyObject ] | ||
79 | } | ||
80 | 65 | ||
81 | const options = { | 66 | return Promise.resolve(false) |
82 | publicKey: publicKeyObject, | ||
83 | publicKeyOwner: publicKeyOwnerObject | ||
84 | } | ||
85 | |||
86 | return jsig.promises | ||
87 | .verify(signedDocument, options) | ||
88 | .then((result: { verified: boolean }) => result.verified) | ||
89 | .catch(err => { | ||
90 | logger.error('Cannot check signature.', { err }) | ||
91 | return false | ||
92 | }) | ||
93 | } | 67 | } |
94 | 68 | ||
95 | // Backward compatibility with "other" implementations | 69 | // Backward compatibility with "other" implementations |
96 | async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { | 70 | async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) { |
97 | function hash (obj: any): Promise<any> { | ||
98 | return jsonld.promises | ||
99 | .normalize(obj, { | ||
100 | algorithm: 'URDNA2015', | ||
101 | format: 'application/n-quads' | ||
102 | }) | ||
103 | .then(res => sha256(res)) | ||
104 | } | ||
105 | |||
106 | const signatureCopy = cloneDeep(signedDocument.signature) | ||
107 | Object.assign(signatureCopy, { | ||
108 | '@context': [ | ||
109 | 'https://w3id.org/security/v1', | ||
110 | { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } | ||
111 | ] | ||
112 | }) | ||
113 | delete signatureCopy.type | ||
114 | delete signatureCopy.id | ||
115 | delete signatureCopy.signatureValue | ||
116 | |||
117 | const docWithoutSignature = cloneDeep(signedDocument) | ||
118 | delete docWithoutSignature.signature | ||
119 | |||
120 | const [ documentHash, optionsHash ] = await Promise.all([ | 71 | const [ documentHash, optionsHash ] = await Promise.all([ |
121 | hash(docWithoutSignature), | 72 | createDocWithoutSignatureHash(signedDocument), |
122 | hash(signatureCopy) | 73 | createSignatureHash(signedDocument.signature) |
123 | ]) | 74 | ]) |
124 | 75 | ||
125 | const toVerify = optionsHash + documentHash | 76 | const toVerify = optionsHash + documentHash |
@@ -130,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: a | |||
130 | return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') | 81 | return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') |
131 | } | 82 | } |
132 | 83 | ||
133 | function signJsonLDObject (byActor: ActorModel, data: any) { | 84 | async function signJsonLDObject (byActor: MActor, data: any) { |
134 | const options = { | 85 | const signature = { |
135 | privateKeyPem: byActor.privateKey, | 86 | type: 'RsaSignature2017', |
136 | creator: byActor.url, | 87 | creator: byActor.url, |
137 | algorithm: 'RsaSignature2017' | 88 | created: new Date().toISOString() |
138 | } | 89 | } |
139 | 90 | ||
140 | return jsig.promises.sign(data, options) | 91 | const [ documentHash, optionsHash ] = await Promise.all([ |
92 | createDocWithoutSignatureHash(data), | ||
93 | createSignatureHash(signature) | ||
94 | ]) | ||
95 | |||
96 | const toSign = optionsHash + documentHash | ||
97 | |||
98 | const sign = createSign('RSA-SHA256') | ||
99 | sign.update(toSign, 'utf8') | ||
100 | |||
101 | const signatureValue = sign.sign(byActor.privateKey, 'base64') | ||
102 | Object.assign(signature, { signatureValue }) | ||
103 | |||
104 | return Object.assign(data, { signature }) | ||
141 | } | 105 | } |
142 | 106 | ||
143 | // --------------------------------------------------------------------------- | 107 | // --------------------------------------------------------------------------- |
@@ -154,3 +118,35 @@ export { | |||
154 | } | 118 | } |
155 | 119 | ||
156 | // --------------------------------------------------------------------------- | 120 | // --------------------------------------------------------------------------- |
121 | |||
122 | function hash (obj: any): Promise<any> { | ||
123 | return jsonld.promises | ||
124 | .normalize(obj, { | ||
125 | algorithm: 'URDNA2015', | ||
126 | format: 'application/n-quads' | ||
127 | }) | ||
128 | .then(res => sha256(res)) | ||
129 | } | ||
130 | |||
131 | function createSignatureHash (signature: any) { | ||
132 | const signatureCopy = cloneDeep(signature) | ||
133 | Object.assign(signatureCopy, { | ||
134 | '@context': [ | ||
135 | 'https://w3id.org/security/v1', | ||
136 | { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } | ||
137 | ] | ||
138 | }) | ||
139 | |||
140 | delete signatureCopy.type | ||
141 | delete signatureCopy.id | ||
142 | delete signatureCopy.signatureValue | ||
143 | |||
144 | return hash(signatureCopy) | ||
145 | } | ||
146 | |||
147 | function createDocWithoutSignatureHash (doc: any) { | ||
148 | const docWithoutSignature = cloneDeep(doc) | ||
149 | delete docWithoutSignature.signature | ||
150 | |||
151 | return hash(docWithoutSignature) | ||
152 | } | ||
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 1464b1477..ba07eaaf3 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -19,7 +19,10 @@ async function generateRandomString (size: number) { | |||
19 | return raw.toString('hex') | 19 | return raw.toString('hex') |
20 | } | 20 | } |
21 | 21 | ||
22 | interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V } | 22 | interface FormattableToJSON<U, V> { |
23 | toFormattedJSON (args?: U): V | ||
24 | } | ||
25 | |||
23 | function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { | 26 | function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { |
24 | const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) | 27 | const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) |
25 | 28 | ||
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index c90fe06c7..d066e2b1f 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -1,8 +1,30 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { VideoModel } from '../models/video/video' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { | ||
4 | MVideoAccountLightBlacklistAllFiles, | ||
5 | MVideoFullLight, | ||
6 | MVideoIdThumbnail, | ||
7 | MVideoThumbnail, | ||
8 | MVideoWithRights | ||
9 | } from '@server/typings/models' | ||
10 | import { Response } from 'express' | ||
2 | 11 | ||
3 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 12 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' |
4 | 13 | ||
5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { | 14 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> |
15 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> | ||
16 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> | ||
17 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> | ||
18 | function fetchVideo ( | ||
19 | id: number | string, | ||
20 | fetchType: VideoFetchType, | ||
21 | userId?: number | ||
22 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> | ||
23 | function fetchVideo ( | ||
24 | id: number | string, | ||
25 | fetchType: VideoFetchType, | ||
26 | userId?: number | ||
27 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> { | ||
6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 28 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
7 | 29 | ||
8 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | 30 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) |
@@ -13,15 +35,29 @@ function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: nu | |||
13 | } | 35 | } |
14 | 36 | ||
15 | type VideoFetchByUrlType = 'all' | 'only-video' | 37 | type VideoFetchByUrlType = 'all' | 'only-video' |
16 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) { | 38 | |
39 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> | ||
40 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> | ||
41 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> | ||
42 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> { | ||
17 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) | 43 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) |
18 | 44 | ||
19 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) | 45 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) |
20 | } | 46 | } |
21 | 47 | ||
48 | function getVideo (res: Response) { | ||
49 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId | ||
50 | } | ||
51 | |||
52 | function getVideoWithAttributes (res: Response) { | ||
53 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights | ||
54 | } | ||
55 | |||
22 | export { | 56 | export { |
23 | VideoFetchType, | 57 | VideoFetchType, |
24 | VideoFetchByUrlType, | 58 | VideoFetchByUrlType, |
25 | fetchVideo, | 59 | fetchVideo, |
60 | getVideo, | ||
61 | getVideoWithAttributes, | ||
26 | fetchVideoByUrl | 62 | fetchVideoByUrl |
27 | } | 63 | } |
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index d1229e28f..5443a266b 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -4,6 +4,7 @@ import { ActorModel } from '../models/activitypub/actor' | |||
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' |
6 | import { WEBSERVER } from '../initializers/constants' | 6 | import { WEBSERVER } from '../initializers/constants' |
7 | import { MActorFull } from '../typings/models' | ||
7 | 8 | ||
8 | const webfinger = new WebFinger({ | 9 | const webfinger = new WebFinger({ |
9 | webfist_fallback: false, | 10 | webfist_fallback: false, |
@@ -17,7 +18,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) { | |||
17 | const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg | 18 | const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg |
18 | 19 | ||
19 | const [ name, host ] = uri.split('@') | 20 | const [ name, host ] = uri.split('@') |
20 | let actor: ActorModel | 21 | let actor: MActorFull |
21 | 22 | ||
22 | if (!host || host === WEBSERVER.HOST) { | 23 | if (!host || host === WEBSERVER.HOST) { |
23 | actor = await ActorModel.loadLocalByName(name) | 24 | actor = await ActorModel.loadLocalByName(name) |