aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers')
-rw-r--r--server/helpers/activitypub.ts3
-rw-r--r--server/helpers/actor.ts9
-rw-r--r--server/helpers/captions-utils.ts6
-rw-r--r--server/helpers/custom-jsonld-signature.ts5
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts15
-rw-r--r--server/helpers/custom-validators/users.ts10
-rw-r--r--server/helpers/custom-validators/video-ownership.ts22
-rw-r--r--server/helpers/middlewares/accounts.ts7
-rw-r--r--server/helpers/middlewares/video-abuses.ts44
-rw-r--r--server/helpers/middlewares/video-captions.ts4
-rw-r--r--server/helpers/middlewares/video-channels.ts45
-rw-r--r--server/helpers/middlewares/video-playlists.ts35
-rw-r--r--server/helpers/middlewares/videos.ts26
-rw-r--r--server/helpers/peertube-crypto.ts126
-rw-r--r--server/helpers/utils.ts5
-rw-r--r--server/helpers/video.ts40
-rw-r--r--server/helpers/webfinger.ts3
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'
7import { signJsonLDObject } from './peertube-crypto' 7import { signJsonLDObject } from './peertube-crypto'
8import { pageToStartAndCount } from './core-utils' 8import { pageToStartAndCount } from './core-utils'
9import { parse } from 'url' 9import { parse } from 'url'
10import { MActor } from '../typings/models'
10 11
11function activityPubContextify <T> (data: T) { 12function 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
146function buildSignedActivity (byActor: ActorModel, data: Object) { 147function 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 @@
1import { ActorModel } from '../models/activitypub/actor' 1import { ActorModel } from '../models/activitypub/actor'
2import * as Bluebird from 'bluebird'
3import { MActorFull, MActorAccountChannelId } from '../typings/models'
2 4
3type ActorFetchByUrlType = 'all' | 'actor-and-association-ids' 5type ActorFetchByUrlType = 'all' | 'association-ids'
4function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) { 6
7function 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
10export { 13export {
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 @@
1import { join } from 'path' 1import { join } from 'path'
2import { CONFIG } from '../initializers/config' 2import { CONFIG } from '../initializers/config'
3import { VideoCaptionModel } from '../models/video/video-caption'
4import * as srt2vtt from 'srt-to-vtt' 3import * as srt2vtt from 'srt-to-vtt'
5import { createReadStream, createWriteStream, remove, move } from 'fs-extra' 4import { createReadStream, createWriteStream, move, remove } from 'fs-extra'
5import { MVideoCaptionFormattable } from '@server/typings/models'
6 6
7async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { 7async 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 @@
1import * as AsyncLRU from 'async-lru' 1import * as AsyncLRU from 'async-lru'
2import * as jsonld from 'jsonld' 2import * as jsonld from 'jsonld'
3import * as jsig from 'jsonld-signatures'
4import { logger } from './logger' 3import { logger } from './logger'
5 4
6const CACHE = { 5const CACHE = {
@@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => {
79 lru.get(url, cb) 78 lru.get(url, cb)
80} 79}
81 80
82jsig.use('jsonld', jsonld) 81export { jsonld }
83
84export { 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
30const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' 30const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]'
31const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) 31const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
32function isActorPreferredUsernameValid (preferredUsername: string) { 32function 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
64function isActorFollowingCountValid (value: string) { 65function 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
68function isNoInstanceConfigWarningModal (value: any) {
69 return isBooleanValid(value)
70}
71
72function isNoWelcomeModal (value: any) {
73 return isBooleanValid(value)
74}
75
68function isUserBlockedReasonValid (value: any) { 76function 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 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import * as validator from 'validator'
3import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' 2import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership'
4import { UserModel } from '../../models/account/user' 3import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
4import { MUserId } from '@server/typings/models'
5 5
6export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> { 6export 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
21async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> { 21export 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
29export 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 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import { AccountModel } from '../../models/account/account' 2import { AccountModel } from '../../models/account/account'
3import * as Bluebird from 'bluebird' 3import * as Bluebird from 'bluebird'
4import { MAccountDefault } from '../../typings/models'
4 5
5function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { 6function 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
17function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { 18function 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
21async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { 24async 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 @@
1import * as express from 'express' 1import { Response } from 'express'
2import { VideoChannelModel } from '../../models/video/video-channel' 2import { VideoAbuseModel } from '../../models/video/video-abuse'
3 3
4async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { 4async 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
10async function doesVideoChannelIdExist (id: number, res: express.Response) {
11 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
12
13 return processVideoChannelExist(videoChannel, res)
14}
15
16async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
17 const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
18
19 return processVideoChannelExist(videoChannel, res)
20}
21
22// ---------------------------------------------------------------------------
23
24export {
25 doesLocalVideoChannelNameExist,
26 doesVideoChannelIdExist,
27 doesVideoChannelNameWithHostExist
28}
29
30function 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
21export {
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 @@
1import { VideoModel } from '../../models/video/video'
2import { Response } from 'express' 1import { Response } from 'express'
3import { VideoCaptionModel } from '../../models/video/video-caption' 2import { VideoCaptionModel } from '../../models/video/video-caption'
3import { MVideoId } from '@server/typings/models'
4 4
5async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { 5async 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 @@
1import { Response } from 'express' 1import * as express from 'express'
2import { VideoAbuseModel } from '../../models/video/video-abuse' 2import { VideoChannelModel } from '../../models/video/video-channel'
3import { MChannelAccountDefault } from '@server/typings/models'
3 4
4async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { 5async 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 11async 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
17async 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
21export { 25export {
22 doesVideoAbuseExist 26 doesLocalVideoChannelNameExist,
27 doesVideoChannelIdExist,
28 doesVideoChannelNameWithHostExist
29}
30
31function 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { VideoPlaylistModel } from '../../models/video/video-playlist' 2import { VideoPlaylistModel } from '../../models/video/video-playlist'
3import { MVideoPlaylist } from '../../typings/models/video/video-playlist'
3 4
4async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { 5export type VideoPlaylistFetchType = 'summary' | 'all'
5 const videoPlaylist = fetchType === 'summary' 6async 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
22export {
23 doesVideoPlaylistExist
24}
25
26// ---------------------------------------------------------------------------
27
28function 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
23export {
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 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import { fetchVideo, VideoFetchType } from '../video' 2import { fetchVideo, VideoFetchType } from '../video'
3import { UserModel } from '../../models/account/user'
4import { UserRight } from '../../../shared/models/users' 3import { UserRight } from '../../../shared/models/users'
5import { VideoChannelModel } from '../../models/video/video-channel' 4import { VideoChannelModel } from '../../models/video/video-channel'
6import { VideoModel } from '../../models/video/video' 5import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models'
7 6
8async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { 7async 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
25async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { 41async 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
53function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) { 69function 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 @@
1import { Request } from 'express' 1import { Request } from 'express'
2import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' 2import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
3import { ActorModel } from '../models/activitypub/actor'
4import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' 3import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
5import { jsig, jsonld } from './custom-jsonld-signature' 4import { jsonld } from './custom-jsonld-signature'
6import { logger } from './logger' 5import { logger } from './logger'
7import { cloneDeep } from 'lodash' 6import { cloneDeep } from 'lodash'
8import { createVerify } from 'crypto' 7import { createSign, createVerify } from 'crypto'
9import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' 8import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
10import * as bcrypt from 'bcrypt' 9import * as bcrypt from 'bcrypt'
10import { MActor } from '../typings/models'
11 11
12const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) 12const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
13const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) 13const 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
49function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { 49function 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
59async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> { 59function 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
96async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { 70async 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
133function signJsonLDObject (byActor: ActorModel, data: any) { 84async 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
122function 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
131function 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
147function 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
22interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V } 22interface FormattableToJSON<U, V> {
23 toFormattedJSON (args?: U): V
24}
25
23function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { 26function 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 @@
1import { VideoModel } from '../models/video/video' 1import { VideoModel } from '../models/video/video'
2import * as Bluebird from 'bluebird'
3import {
4 MVideoAccountLightBlacklistAllFiles,
5 MVideoFullLight,
6 MVideoIdThumbnail,
7 MVideoThumbnail,
8 MVideoWithRights
9} from '@server/typings/models'
10import { Response } from 'express'
2 11
3type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' 12type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
4 13
5function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { 14function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight>
15function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail>
16function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights>
17function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail>
18function fetchVideo (
19 id: number | string,
20 fetchType: VideoFetchType,
21 userId?: number
22): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail>
23function 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
15type VideoFetchByUrlType = 'all' | 'only-video' 37type VideoFetchByUrlType = 'all' | 'only-video'
16function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) { 38
39function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles>
40function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail>
41function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
42function 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
48function getVideo (res: Response) {
49 return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId
50}
51
52function getVideoWithAttributes (res: Response) {
53 return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights
54}
55
22export { 56export {
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'
4import { isTestInstance } from './core-utils' 4import { isTestInstance } from './core-utils'
5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' 5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
6import { WEBSERVER } from '../initializers/constants' 6import { WEBSERVER } from '../initializers/constants'
7import { MActorFull } from '../typings/models'
7 8
8const webfinger = new WebFinger({ 9const 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)