aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts18
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/initializers/migrations/0130-video-channel-actor.ts10
-rw-r--r--server/lib/activitypub/actor.ts19
-rw-r--r--server/lib/activitypub/send/misc.ts9
-rw-r--r--server/lib/activitypub/send/send-accept.ts15
-rw-r--r--server/lib/activitypub/send/send-create.ts13
-rw-r--r--server/lib/activitypub/send/send-like.ts7
-rw-r--r--server/lib/activitypub/send/send-undo.ts7
-rw-r--r--server/lib/activitypub/send/send-update.ts10
-rw-r--r--server/lib/activitypub/share.ts3
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts6
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts26
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts6
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts9
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts5
-rw-r--r--server/middlewares/activitypub.ts7
-rw-r--r--server/models/activitypub/actor.ts67
18 files changed, 130 insertions, 109 deletions
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts
index 5930bd5da..ec8da3350 100644
--- a/server/helpers/custom-validators/activitypub/actor.ts
+++ b/server/helpers/custom-validators/activitypub/actor.ts
@@ -1,8 +1,8 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '../../../initializers' 2import { CONSTRAINTS_FIELDS } from '../../../initializers'
3import { isAccountNameValid } from '../accounts' 3import { isAccountNameValid } from '../accounts'
4import { exists, isUUIDValid } from '../misc' 4import { exists } from '../misc'
5import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' 5import { isVideoChannelNameValid } from '../video-channels'
6import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' 6import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
7 7
8function isActorEndpointsObjectValid (endpointObject: any) { 8function isActorEndpointsObjectValid (endpointObject: any) {
@@ -23,41 +23,39 @@ function isActorPublicKeyValid (publicKey: string) {
23 return exists(publicKey) && 23 return exists(publicKey) &&
24 typeof publicKey === 'string' && 24 typeof publicKey === 'string' &&
25 publicKey.startsWith('-----BEGIN PUBLIC KEY-----') && 25 publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
26 publicKey.endsWith('-----END PUBLIC KEY-----') && 26 publicKey.indexOf('-----END PUBLIC KEY-----') !== -1 &&
27 validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY) 27 validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY)
28} 28}
29 29
30const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
30function isActorPreferredUsernameValid (preferredUsername: string) { 31function isActorPreferredUsernameValid (preferredUsername: string) {
31 return isAccountNameValid(preferredUsername) || isVideoChannelNameValid(preferredUsername) 32 return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
32} 33}
33 34
34const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
35function isActorNameValid (name: string) { 35function isActorNameValid (name: string) {
36 return exists(name) && validator.matches(name, actorNameRegExp) 36 return isAccountNameValid(name) || isVideoChannelNameValid(name)
37} 37}
38 38
39function isActorPrivateKeyValid (privateKey: string) { 39function isActorPrivateKeyValid (privateKey: string) {
40 return exists(privateKey) && 40 return exists(privateKey) &&
41 typeof privateKey === 'string' && 41 typeof privateKey === 'string' &&
42 privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') && 42 privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
43 privateKey.endsWith('-----END RSA PRIVATE KEY-----') && 43 // Sometimes there is a \n at the end, so just assert the string contains the end mark
44 privateKey.indexOf('-----END RSA PRIVATE KEY-----') !== -1 &&
44 validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY) 45 validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY)
45} 46}
46 47
47function isRemoteActorValid (remoteActor: any) { 48function isRemoteActorValid (remoteActor: any) {
48 return isActivityPubUrlValid(remoteActor.id) && 49 return isActivityPubUrlValid(remoteActor.id) &&
49 isUUIDValid(remoteActor.uuid) &&
50 isActorTypeValid(remoteActor.type) && 50 isActorTypeValid(remoteActor.type) &&
51 isActivityPubUrlValid(remoteActor.following) && 51 isActivityPubUrlValid(remoteActor.following) &&
52 isActivityPubUrlValid(remoteActor.followers) && 52 isActivityPubUrlValid(remoteActor.followers) &&
53 isActivityPubUrlValid(remoteActor.inbox) && 53 isActivityPubUrlValid(remoteActor.inbox) &&
54 isActivityPubUrlValid(remoteActor.outbox) && 54 isActivityPubUrlValid(remoteActor.outbox) &&
55 isActorNameValid(remoteActor.name) &&
56 isActorPreferredUsernameValid(remoteActor.preferredUsername) && 55 isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
57 isActivityPubUrlValid(remoteActor.url) && 56 isActivityPubUrlValid(remoteActor.url) &&
58 isActorPublicKeyObjectValid(remoteActor.publicKey) && 57 isActorPublicKeyObjectValid(remoteActor.publicKey) &&
59 isActorEndpointsObjectValid(remoteActor.endpoints) && 58 isActorEndpointsObjectValid(remoteActor.endpoints) &&
60 (!remoteActor.summary || isVideoChannelDescriptionValid(remoteActor.summary)) &&
61 setValidAttributedTo(remoteActor) && 59 setValidAttributedTo(remoteActor) &&
62 // If this is not an account, it should be attributed to an account 60 // If this is not an account, it should be attributed to an account
63 // 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
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 04b610b7a..4cb54dc93 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -316,7 +316,7 @@ const CACHE = {
316 } 316 }
317} 317}
318 318
319const ACCEPT_HEADERS = ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.concat('html', 'application/json') 319const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
320 320
321// --------------------------------------------------------------------------- 321// ---------------------------------------------------------------------------
322 322
diff --git a/server/initializers/migrations/0130-video-channel-actor.ts b/server/initializers/migrations/0130-video-channel-actor.ts
index 15b67be81..2e4694a75 100644
--- a/server/initializers/migrations/0130-video-channel-actor.ts
+++ b/server/initializers/migrations/0130-video-channel-actor.ts
@@ -22,7 +22,7 @@ async function up (utils: {
22 id integer NOT NULL, 22 id integer NOT NULL,
23 type enum_actor_type NOT NULL, 23 type enum_actor_type NOT NULL,
24 uuid uuid NOT NULL, 24 uuid uuid NOT NULL,
25 name character varying(255) NOT NULL, 25 "preferredUsername" character varying(255) NOT NULL,
26 url character varying(2000) NOT NULL, 26 url character varying(2000) NOT NULL,
27 "publicKey" character varying(5000), 27 "publicKey" character varying(5000),
28 "privateKey" character varying(5000), 28 "privateKey" character varying(5000),
@@ -50,7 +50,7 @@ async function up (utils: {
50 `ALTER SEQUENCE actor_id_seq OWNED BY actor.id`, 50 `ALTER SEQUENCE actor_id_seq OWNED BY actor.id`,
51 `ALTER TABLE ONLY actor ALTER COLUMN id SET DEFAULT nextval('actor_id_seq'::regclass)`, 51 `ALTER TABLE ONLY actor ALTER COLUMN id SET DEFAULT nextval('actor_id_seq'::regclass)`,
52 `ALTER TABLE ONLY actor ADD CONSTRAINT actor_pkey PRIMARY KEY (id);`, 52 `ALTER TABLE ONLY actor ADD CONSTRAINT actor_pkey PRIMARY KEY (id);`,
53 `CREATE UNIQUE INDEX actor_name_server_id ON actor USING btree (name, "serverId")`, 53 `CREATE UNIQUE INDEX actor_preferred_username_server_id ON actor USING btree ("preferredUsername", "serverId")`,
54 `ALTER TABLE ONLY actor 54 `ALTER TABLE ONLY actor
55 ADD CONSTRAINT "actor_avatarId_fkey" FOREIGN KEY ("avatarId") REFERENCES avatar(id) ON UPDATE CASCADE ON DELETE CASCADE`, 55 ADD CONSTRAINT "actor_avatarId_fkey" FOREIGN KEY ("avatarId") REFERENCES avatar(id) ON UPDATE CASCADE ON DELETE CASCADE`,
56 `ALTER TABLE ONLY actor 56 `ALTER TABLE ONLY actor
@@ -68,7 +68,7 @@ async function up (utils: {
68 ` 68 `
69 INSERT INTO "actor" 69 INSERT INTO "actor"
70 ( 70 (
71 type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 71 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
72 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 72 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
73 ) 73 )
74 SELECT 74 SELECT
@@ -83,7 +83,7 @@ async function up (utils: {
83 ` 83 `
84 INSERT INTO "actor" 84 INSERT INTO "actor"
85 ( 85 (
86 type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 86 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
87 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 87 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
88 ) 88 )
89 SELECT 89 SELECT
@@ -119,7 +119,7 @@ async function up (utils: {
119 const query = ` 119 const query = `
120 INSERT INTO actor 120 INSERT INTO actor
121 ( 121 (
122 type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 122 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
123 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 123 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
124 ) 124 )
125 SELECT 125 SELECT
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index c3de4bdce..ff0a291e8 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -11,7 +11,7 @@ import { ActorModel } from '../../models/activitypub/actor'
11import { ServerModel } from '../../models/server/server' 11import { ServerModel } from '../../models/server/server'
12import { VideoChannelModel } from '../../models/video/video-channel' 12import { VideoChannelModel } from '../../models/video/video-channel'
13 13
14 // Set account keys, this could be long so process after the account creation and do not block the client 14// Set account keys, this could be long so process after the account creation and do not block the client
15function setAsyncActorKeys (actor: ActorModel) { 15function setAsyncActorKeys (actor: ActorModel) {
16 return createPrivateAndPublicKeys() 16 return createPrivateAndPublicKeys()
17 .then(({ publicKey, privateKey }) => { 17 .then(({ publicKey, privateKey }) => {
@@ -107,7 +107,7 @@ function saveActorAndServerAndModelIfNotExist (
107 107
108type FetchRemoteActorResult = { 108type FetchRemoteActorResult = {
109 actor: ActorModel 109 actor: ActorModel
110 preferredUsername: string 110 name: string
111 summary: string 111 summary: string
112 attributedTo: ActivityPubAttributedTo[] 112 attributedTo: ActivityPubAttributedTo[]
113} 113}
@@ -142,8 +142,8 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
142 const actor = new ActorModel({ 142 const actor = new ActorModel({
143 type: actorJSON.type, 143 type: actorJSON.type,
144 uuid: actorJSON.uuid, 144 uuid: actorJSON.uuid,
145 name: actorJSON.name, 145 preferredUsername: actorJSON.preferredUsername,
146 url: actorJSON.url, 146 url: actorJSON.id,
147 publicKey: actorJSON.publicKey.publicKeyPem, 147 publicKey: actorJSON.publicKey.publicKeyPem,
148 privateKey: null, 148 privateKey: null,
149 followersCount: followersCount, 149 followersCount: followersCount,
@@ -155,19 +155,20 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
155 followingUrl: actorJSON.following 155 followingUrl: actorJSON.following
156 }) 156 })
157 157
158 const name = actorJSON.name || actorJSON.preferredUsername
158 return { 159 return {
159 actor, 160 actor,
160 preferredUsername: actorJSON.preferredUsername, 161 name,
161 summary: actorJSON.summary, 162 summary: actorJSON.summary,
162 attributedTo: actorJSON.attributedTo 163 attributedTo: actorJSON.attributedTo
163 } 164 }
164} 165}
165 166
166function buildActorInstance (type: ActivityPubActorType, url: string, name: string, uuid?: string) { 167function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) {
167 return new ActorModel({ 168 return new ActorModel({
168 type, 169 type,
169 url, 170 url,
170 name, 171 preferredUsername,
171 uuid, 172 uuid,
172 publicKey: null, 173 publicKey: null,
173 privateKey: null, 174 privateKey: null,
@@ -210,7 +211,7 @@ async function fetchActorTotalItems (url: string) {
210 211
211function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) { 212function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
212 const account = new AccountModel({ 213 const account = new AccountModel({
213 name: result.preferredUsername, 214 name: result.name,
214 actorId: actor.id 215 actorId: actor.id
215 }) 216 })
216 217
@@ -219,7 +220,7 @@ function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Tran
219 220
220async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) { 221async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
221 const videoChannel = new VideoChannelModel({ 222 const videoChannel = new VideoChannelModel({
222 name: result.preferredUsername, 223 name: result.name,
223 description: result.summary, 224 description: result.summary,
224 actorId: actor.id, 225 actorId: actor.id,
225 accountId: ownerActor.Account.id 226 accountId: ownerActor.Account.id
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index 14101e630..2dc8d3d59 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -1,5 +1,5 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { Activity } from '../../../../shared/models/activitypub' 2import { Activity, ActivityAudience } from '../../../../shared/models/activitypub'
3import { logger } from '../../../helpers' 3import { logger } from '../../../helpers'
4import { ACTIVITY_PUB } from '../../../initializers' 4import { ACTIVITY_PUB } from '../../../initializers'
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/activitypub/actor'
@@ -116,6 +116,10 @@ async function getAudience (actorSender: ActorModel, t: Transaction, isPublic =
116 return { to, cc } 116 return { to, cc }
117} 117}
118 118
119function audiencify (object: any, audience: ActivityAudience) {
120 return Object.assign(object, audience)
121}
122
119async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) { 123async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) {
120 const toActorFollowerIds = toActorFollower.map(a => a.id) 124 const toActorFollowerIds = toActorFollower.map(a => a.id)
121 125
@@ -133,5 +137,6 @@ export {
133 getOriginVideoAudience, 137 getOriginVideoAudience,
134 getActorsInvolvedInVideo, 138 getActorsInvolvedInVideo,
135 getObjectFollowersAudience, 139 getObjectFollowersAudience,
136 forwardActivity 140 forwardActivity,
141 audiencify
137} 142}
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 7579884a7..4eaa329d9 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -1,16 +1,20 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAccept } from '../../../../shared/models/activitypub' 2import { ActivityAccept, ActivityFollow } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 4import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
5import { getActorFollowAcceptActivityPubUrl } from '../url' 5import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url'
6import { unicastTo } from './misc' 6import { unicastTo } from './misc'
7import { followActivityData } from './send-follow'
7 8
8async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) { 9async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) {
9 const follower = actorFollow.ActorFollower 10 const follower = actorFollow.ActorFollower
10 const me = actorFollow.ActorFollowing 11 const me = actorFollow.ActorFollowing
11 12
13 const followUrl = getActorFollowActivityPubUrl(actorFollow)
14 const followData = followActivityData(followUrl, follower, me)
15
12 const url = getActorFollowAcceptActivityPubUrl(actorFollow) 16 const url = getActorFollowAcceptActivityPubUrl(actorFollow)
13 const data = acceptActivityData(url, me) 17 const data = acceptActivityData(url, me, followData)
14 18
15 return unicastTo(data, me, follower.inboxUrl, t) 19 return unicastTo(data, me, follower.inboxUrl, t)
16} 20}
@@ -23,10 +27,11 @@ export {
23 27
24// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
25 29
26function acceptActivityData (url: string, byActor: ActorModel): ActivityAccept { 30function acceptActivityData (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityAccept {
27 return { 31 return {
28 type: 'Accept', 32 type: 'Accept',
29 id: url, 33 id: url,
30 actor: byActor.url 34 actor: byActor.url,
35 object: followActivityData
31 } 36 }
32} 37}
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index d26c24838..249dd91dc 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -7,6 +7,7 @@ import { VideoModel } from '../../../models/video/video'
7import { VideoAbuseModel } from '../../../models/video/video-abuse' 7import { VideoAbuseModel } from '../../../models/video/video-abuse'
8import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' 8import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
9import { 9import {
10 audiencify,
10 broadcastToFollowers, 11 broadcastToFollowers,
11 getActorsInvolvedInVideo, 12 getActorsInvolvedInVideo,
12 getAudience, 13 getAudience,
@@ -16,9 +17,11 @@ import {
16} from './misc' 17} from './misc'
17 18
18async function sendCreateVideo (video: VideoModel, t: Transaction) { 19async function sendCreateVideo (video: VideoModel, t: Transaction) {
19 const byActor = video.VideoChannel.Account.Actor 20 if (video.privacy === VideoPrivacy.PRIVATE) return
20 21
22 const byActor = video.VideoChannel.Account.Actor
21 const videoObject = video.toActivityPubObject() 23 const videoObject = video.toActivityPubObject()
24
22 const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC) 25 const audience = await getAudience(byActor, t, video.privacy === VideoPrivacy.PUBLIC)
23 const data = await createActivityData(video.url, byActor, videoObject, t, audience) 26 const data = await createActivityData(video.url, byActor, videoObject, t, audience)
24 27
@@ -93,14 +96,12 @@ async function createActivityData (
93 audience = await getAudience(byActor, t) 96 audience = await getAudience(byActor, t)
94 } 97 }
95 98
96 return { 99 return audiencify({
97 type: 'Create', 100 type: 'Create',
98 id: url, 101 id: url,
99 actor: byActor.url, 102 actor: byActor.url,
100 to: audience.to, 103 object: audiencify(object, audience)
101 cc: audience.cc, 104 }, audience)
102 object
103 }
104} 105}
105 106
106function createDislikeActivityData (byActor: ActorModel, video: VideoModel) { 107function createDislikeActivityData (byActor: ActorModel, video: VideoModel) {
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 7e0c73796..743646455 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -4,6 +4,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video' 4import { VideoModel } from '../../../models/video/video'
5import { getVideoLikeActivityPubUrl } from '../url' 5import { getVideoLikeActivityPubUrl } from '../url'
6import { 6import {
7 audiencify,
7 broadcastToFollowers, 8 broadcastToFollowers,
8 getActorsInvolvedInVideo, 9 getActorsInvolvedInVideo,
9 getAudience, 10 getAudience,
@@ -44,14 +45,12 @@ async function likeActivityData (
44 audience = await getAudience(byActor, t) 45 audience = await getAudience(byActor, t)
45 } 46 }
46 47
47 return { 48 return audiencify({
48 type: 'Like', 49 type: 'Like',
49 id: url, 50 id: url,
50 actor: byActor.url, 51 actor: byActor.url,
51 to: audience.to,
52 cc: audience.cc,
53 object: video.url 52 object: video.url
54 } 53 }, audience)
55} 54}
56 55
57// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 92271b700..3a0597fba 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -11,6 +11,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
11import { VideoModel } from '../../../models/video/video' 11import { VideoModel } from '../../../models/video/video'
12import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
13import { 13import {
14 audiencify,
14 broadcastToFollowers, 15 broadcastToFollowers,
15 getActorsInvolvedInVideo, 16 getActorsInvolvedInVideo,
16 getAudience, 17 getAudience,
@@ -112,12 +113,10 @@ async function undoActivityData (
112 audience = await getAudience(byActor, t) 113 audience = await getAudience(byActor, t)
113 } 114 }
114 115
115 return { 116 return audiencify({
116 type: 'Undo', 117 type: 'Undo',
117 id: url, 118 id: url,
118 actor: byActor.url, 119 actor: byActor.url,
119 to: audience.to,
120 cc: audience.cc,
121 object 120 object
122 } 121 }, audience)
123} 122}
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 48bbbcac1..b623fec6c 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -5,7 +5,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
6import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
7import { getUpdateActivityPubUrl } from '../url' 7import { getUpdateActivityPubUrl } from '../url'
8import { broadcastToFollowers, getAudience } from './misc' 8import { audiencify, broadcastToFollowers, getAudience } from './misc'
9 9
10async function sendUpdateVideo (video: VideoModel, t: Transaction) { 10async function sendUpdateVideo (video: VideoModel, t: Transaction) {
11 const byActor = video.VideoChannel.Account.Actor 11 const byActor = video.VideoChannel.Account.Actor
@@ -41,12 +41,10 @@ async function updateActivityData (
41 audience = await getAudience(byActor, t) 41 audience = await getAudience(byActor, t)
42 } 42 }
43 43
44 return { 44 return audiencify({
45 type: 'Update', 45 type: 'Update',
46 id: url, 46 id: url,
47 actor: byActor.url, 47 actor: byActor.url,
48 to: audience.to, 48 object: audiencify(object, audience)
49 cc: audience.cc, 49 }, audience)
50 object
51 }
52} 50}
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index fb01368ec..386ae362a 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -1,10 +1,13 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { VideoPrivacy } from '../../../shared/models/videos'
2import { getServerActor } from '../../helpers' 3import { getServerActor } from '../../helpers'
3import { VideoModel } from '../../models/video/video' 4import { VideoModel } from '../../models/video/video'
4import { VideoShareModel } from '../../models/video/video-share' 5import { VideoShareModel } from '../../models/video/video-share'
5import { sendVideoAnnounceToFollowers } from './send' 6import { sendVideoAnnounceToFollowers } from './send'
6 7
7async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { 8async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
9 if (video.privacy === VideoPrivacy.PRIVATE) return
10
8 const serverActor = await getServerActor() 11 const serverActor = await getServerActor()
9 12
10 const serverShare = VideoShareModel.create({ 13 const serverShare = VideoShareModel.create({
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
index 8040dde2a..3c4d5556f 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
@@ -1,15 +1,17 @@
1import { doRequest, logger } from '../../../helpers' 1import { doRequest, logger } from '../../../helpers'
2import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 2import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
3 3
4async function process (payload: ActivityPubHttpPayload, jobId: number) { 4async function process (payload: ActivityPubHttpPayload, jobId: number) {
5 logger.info('Processing ActivityPub broadcast in job %d.', jobId) 5 logger.info('Processing ActivityPub broadcast in job %d.', jobId)
6 6
7 const body = await computeBody(payload) 7 const body = await computeBody(payload)
8 const httpSignatureOptions = await buildSignedRequestOptions(payload)
8 9
9 const options = { 10 const options = {
10 method: 'POST', 11 method: 'POST',
11 uri: '', 12 uri: '',
12 json: body 13 json: body,
14 httpSignature: httpSignatureOptions
13 } 15 }
14 16
15 for (const uri of payload.uris) { 17 for (const uri of payload.uris) {
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
index 95a5d3ff2..88885cf97 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
@@ -1,5 +1,5 @@
1import { JobCategory } from '../../../../shared' 1import { JobCategory } from '../../../../shared'
2import { buildSignedActivity, logger } from '../../../helpers' 2import { buildSignedActivity, getServerActor, logger } from '../../../helpers'
3import { ACTIVITY_PUB } from '../../../initializers' 3import { ACTIVITY_PUB } from '../../../initializers'
4import { ActorModel } from '../../../models/activitypub/actor' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { JobHandler, JobScheduler } from '../job-scheduler' 5import { JobHandler, JobScheduler } from '../job-scheduler'
@@ -46,16 +46,36 @@ async function computeBody (payload: ActivityPubHttpPayload) {
46 46
47 if (payload.signatureActorId) { 47 if (payload.signatureActorId) {
48 const actorSignature = await ActorModel.load(payload.signatureActorId) 48 const actorSignature = await ActorModel.load(payload.signatureActorId)
49 if (!actorSignature) throw new Error('Unknown signature account id.') 49 if (!actorSignature) throw new Error('Unknown signature actor id.')
50 body = await buildSignedActivity(actorSignature, payload.body) 50 body = await buildSignedActivity(actorSignature, payload.body)
51 } 51 }
52 52
53 return body 53 return body
54} 54}
55 55
56async function buildSignedRequestOptions (payload: ActivityPubHttpPayload) {
57 let actor: ActorModel
58 if (payload.signatureActorId) {
59 actor = await ActorModel.load(payload.signatureActorId)
60 if (!actor) throw new Error('Unknown signature actor id.')
61 } else {
62 // We need to sign the request, so use the server
63 actor = await getServerActor()
64 }
65
66 const keyId = actor.getWebfingerUrl()
67 return {
68 algorithm: 'rsa-sha256',
69 authorizationHeaderName: 'Signature',
70 keyId,
71 key: actor.privateKey
72 }
73}
74
56export { 75export {
57 ActivityPubHttpPayload, 76 ActivityPubHttpPayload,
58 activitypubHttpJobScheduler, 77 activitypubHttpJobScheduler,
59 maybeRetryRequestLater, 78 maybeRetryRequestLater,
60 computeBody 79 computeBody,
80 buildSignedRequestOptions
61} 81}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
index f16cfcec3..7a5caa679 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
@@ -1,16 +1,18 @@
1import { doRequest, logger } from '../../../helpers' 1import { doRequest, logger } from '../../../helpers'
2import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 2import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
3 3
4async function process (payload: ActivityPubHttpPayload, jobId: number) { 4async function process (payload: ActivityPubHttpPayload, jobId: number) {
5 logger.info('Processing ActivityPub unicast in job %d.', jobId) 5 logger.info('Processing ActivityPub unicast in job %d.', jobId)
6 6
7 const body = await computeBody(payload) 7 const body = await computeBody(payload)
8 const httpSignatureOptions = await buildSignedRequestOptions(payload)
8 9
9 const uri = payload.uris[0] 10 const uri = payload.uris[0]
10 const options = { 11 const options = {
11 method: 'POST', 12 method: 'POST',
12 uri, 13 uri,
13 json: body 14 json: body,
15 httpSignature: httpSignatureOptions
14 } 16 }
15 17
16 try { 18 try {
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
index 47b12e66f..d905882be 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
@@ -1,4 +1,5 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { VideoPrivacy } from '../../../../shared/models/videos'
2import { computeResolutionsToTranscode, logger } from '../../../helpers' 3import { computeResolutionsToTranscode, logger } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
4import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
@@ -35,9 +36,11 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
35 // Video does not exist anymore 36 // Video does not exist anymore
36 if (!videoDatabase) return undefined 37 if (!videoDatabase) return undefined
37 38
38 // Now we'll add the video's meta data to our followers 39 if (video.privacy !== VideoPrivacy.PRIVATE) {
39 await sendCreateVideo(video, undefined) 40 // Now we'll add the video's meta data to our followers
40 await shareVideoByServerAndChannel(video, undefined) 41 await sendCreateVideo(video, undefined)
42 await shareVideoByServerAndChannel(video, undefined)
43 }
41 44
42 const originalFileHeight = await videoDatabase.getOriginalFileHeight() 45 const originalFileHeight = await videoDatabase.getOriginalFileHeight()
43 46
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
index 8957b4565..409123bfe 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
@@ -1,4 +1,5 @@
1import { VideoResolution } from '../../../../shared' 1import { VideoResolution } from '../../../../shared'
2import { VideoPrivacy } from '../../../../shared/models/videos'
2import { logger } from '../../../helpers' 3import { logger } from '../../../helpers'
3import { VideoModel } from '../../../models/video/video' 4import { VideoModel } from '../../../models/video/video'
4import { sendUpdateVideo } from '../../activitypub/send' 5import { sendUpdateVideo } from '../../activitypub/send'
@@ -31,7 +32,9 @@ async function onSuccess (jobId: number, video: VideoModel) {
31 // Video does not exist anymore 32 // Video does not exist anymore
32 if (!videoDatabase) return undefined 33 if (!videoDatabase) return undefined
33 34
34 await sendUpdateVideo(video, undefined) 35 if (video.privacy !== VideoPrivacy.PRIVATE) {
36 await sendUpdateVideo(video, undefined)
37 }
35 38
36 return undefined 39 return undefined
37} 40}
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index 37b7c42ec..9113e02a7 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -9,11 +9,13 @@ import { ActorModel } from '../models/activitypub/actor'
9async function checkSignature (req: Request, res: Response, next: NextFunction) { 9async function checkSignature (req: Request, res: Response, next: NextFunction) {
10 const signatureObject: ActivityPubSignature = req.body.signature 10 const signatureObject: ActivityPubSignature = req.body.signature
11 11
12 logger.debug('Checking signature of actor %s...', signatureObject.creator) 12 const [ creator ] = signatureObject.creator.split('#')
13
14 logger.debug('Checking signature of actor %s...', creator)
13 15
14 let actor: ActorModel 16 let actor: ActorModel
15 try { 17 try {
16 actor = await getOrCreateActorAndServerAndModel(signatureObject.creator) 18 actor = await getOrCreateActorAndServerAndModel(creator)
17 } catch (err) { 19 } catch (err) {
18 logger.error('Cannot create remote actor and check signature.', err) 20 logger.error('Cannot create remote actor and check signature.', err)
19 return res.sendStatus(403) 21 return res.sendStatus(403)
@@ -32,6 +34,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
32function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) { 34function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) {
33 return (req: Request, res: Response, next: NextFunction) => { 35 return (req: Request, res: Response, next: NextFunction) => {
34 const accepted = req.accepts(ACCEPT_HEADERS) 36 const accepted = req.accepts(ACCEPT_HEADERS)
37 console.log(accepted)
35 if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) { 38 if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) {
36 return next() 39 return next()
37 } 40 }
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 8cedcc2bc..e7eb35e2c 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -2,32 +2,15 @@ import { values } from 'lodash'
2import { join } from 'path' 2import { join } from 'path'
3import * as Sequelize from 'sequelize' 3import * as Sequelize from 'sequelize'
4import { 4import {
5 AllowNull, 5 AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes,
6 BelongsTo, 6 Table, UpdatedAt
7 Column,
8 CreatedAt,
9 DataType,
10 Default, DefaultScope,
11 ForeignKey,
12 HasMany,
13 HasOne,
14 Is,
15 IsUUID,
16 Model,
17 Scopes,
18 Table,
19 UpdatedAt
20} from 'sequelize-typescript' 7} from 'sequelize-typescript'
21import { ActivityPubActorType } from '../../../shared/models/activitypub' 8import { ActivityPubActorType } from '../../../shared/models/activitypub'
22import { Avatar } from '../../../shared/models/avatars/avatar.model' 9import { Avatar } from '../../../shared/models/avatars/avatar.model'
23import { activityPubContextify } from '../../helpers' 10import { activityPubContextify } from '../../helpers'
24import { 11import {
25 isActivityPubUrlValid, 12 isActivityPubUrlValid, isActorFollowersCountValid, isActorFollowingCountValid, isActorPreferredUsernameValid,
26 isActorFollowersCountValid, 13 isActorPrivateKeyValid, isActorPublicKeyValid
27 isActorFollowingCountValid,
28 isActorNameValid,
29 isActorPrivateKeyValid,
30 isActorPublicKeyValid
31} from '../../helpers/custom-validators/activitypub' 14} from '../../helpers/custom-validators/activitypub'
32import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' 15import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
33import { AccountModel } from '../account/account' 16import { AccountModel } from '../account/account'
@@ -71,7 +54,7 @@ enum ScopeNames {
71 tableName: 'actor', 54 tableName: 'actor',
72 indexes: [ 55 indexes: [
73 { 56 {
74 fields: [ 'name', 'serverId' ], 57 fields: [ 'preferredUsername', 'serverId' ],
75 unique: true 58 unique: true
76 } 59 }
77 ] 60 ]
@@ -89,9 +72,9 @@ export class ActorModel extends Model<ActorModel> {
89 uuid: string 72 uuid: string
90 73
91 @AllowNull(false) 74 @AllowNull(false)
92 @Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name')) 75 @Is('ActorPreferredUsername', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor preferred username'))
93 @Column 76 @Column
94 name: string 77 preferredUsername: string
95 78
96 @AllowNull(false) 79 @AllowNull(false)
97 @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) 80 @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
@@ -212,16 +195,6 @@ export class ActorModel extends Model<ActorModel> {
212 return ActorModel.scope(ScopeNames.FULL).findById(id) 195 return ActorModel.scope(ScopeNames.FULL).findById(id)
213 } 196 }
214 197
215 static loadByUUID (uuid: string) {
216 const query = {
217 where: {
218 uuid
219 }
220 }
221
222 return ActorModel.scope(ScopeNames.FULL).findOne(query)
223 }
224
225 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { 198 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
226 const query = { 199 const query = {
227 where: { 200 where: {
@@ -235,10 +208,10 @@ export class ActorModel extends Model<ActorModel> {
235 return ActorModel.scope(ScopeNames.FULL).findAll(query) 208 return ActorModel.scope(ScopeNames.FULL).findAll(query)
236 } 209 }
237 210
238 static loadLocalByName (name: string) { 211 static loadLocalByName (preferredUsername: string) {
239 const query = { 212 const query = {
240 where: { 213 where: {
241 name, 214 preferredUsername,
242 serverId: null 215 serverId: null
243 } 216 }
244 } 217 }
@@ -246,10 +219,10 @@ export class ActorModel extends Model<ActorModel> {
246 return ActorModel.scope(ScopeNames.FULL).findOne(query) 219 return ActorModel.scope(ScopeNames.FULL).findOne(query)
247 } 220 }
248 221
249 static loadByNameAndHost (name: string, host: string) { 222 static loadByNameAndHost (preferredUsername: string, host: string) {
250 const query = { 223 const query = {
251 where: { 224 where: {
252 name 225 preferredUsername
253 }, 226 },
254 include: [ 227 include: [
255 { 228 {
@@ -286,17 +259,15 @@ export class ActorModel extends Model<ActorModel> {
286 } 259 }
287 } 260 }
288 261
289 let host = CONFIG.WEBSERVER.HOST
290 let score: number 262 let score: number
291 if (this.Server) { 263 if (this.Server) {
292 host = this.Server.host
293 score = this.Server.score 264 score = this.Server.score
294 } 265 }
295 266
296 return { 267 return {
297 id: this.id, 268 id: this.id,
298 uuid: this.uuid, 269 uuid: this.uuid,
299 host, 270 host: this.getHost(),
300 score, 271 score,
301 followingCount: this.followingCount, 272 followingCount: this.followingCount,
302 followersCount: this.followersCount, 273 followersCount: this.followersCount,
@@ -304,7 +275,7 @@ export class ActorModel extends Model<ActorModel> {
304 } 275 }
305 } 276 }
306 277
307 toActivityPubObject (preferredUsername: string, type: 'Account' | 'Application' | 'VideoChannel') { 278 toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') {
308 let activityPubType 279 let activityPubType
309 if (type === 'Account') { 280 if (type === 'Account') {
310 activityPubType = 'Person' as 'Person' 281 activityPubType = 'Person' as 'Person'
@@ -321,9 +292,9 @@ export class ActorModel extends Model<ActorModel> {
321 followers: this.getFollowersUrl(), 292 followers: this.getFollowersUrl(),
322 inbox: this.inboxUrl, 293 inbox: this.inboxUrl,
323 outbox: this.outboxUrl, 294 outbox: this.outboxUrl,
324 preferredUsername, 295 preferredUsername: this.preferredUsername,
325 url: this.url, 296 url: this.url,
326 name: this.name, 297 name,
327 endpoints: { 298 endpoints: {
328 sharedInbox: this.sharedInboxUrl 299 sharedInbox: this.sharedInboxUrl
329 }, 300 },
@@ -373,4 +344,12 @@ export class ActorModel extends Model<ActorModel> {
373 isOwned () { 344 isOwned () {
374 return this.serverId === null 345 return this.serverId === null
375 } 346 }
347
348 getWebfingerUrl () {
349 return 'acct:' + this.preferredUsername + '@' + this.getHost()
350 }
351
352 getHost () {
353 return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST
354 }
376} 355}