Fix player play exception on chromium
[oweals/peertube.git] / server / lib / activitypub / actor.ts
1 import * as Bluebird from 'bluebird'
2 import { Transaction } from 'sequelize'
3 import * as url from 'url'
4 import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
5 import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
6 import { createPrivateAndPublicKeys, doRequest, logger, retryTransactionWrapper } from '../../helpers'
7 import { isRemoteActorValid } from '../../helpers/custom-validators/activitypub'
8 import { ACTIVITY_PUB, CONFIG, sequelizeTypescript } from '../../initializers'
9 import { AccountModel } from '../../models/account/account'
10 import { ActorModel } from '../../models/activitypub/actor'
11 import { ServerModel } from '../../models/server/server'
12 import { VideoChannelModel } from '../../models/video/video-channel'
13
14 // Set account keys, this could be long so process after the account creation and do not block the client
15 function setAsyncActorKeys (actor: ActorModel) {
16   return createPrivateAndPublicKeys()
17     .then(({ publicKey, privateKey }) => {
18       actor.set('publicKey', publicKey)
19       actor.set('privateKey', privateKey)
20       return actor.save()
21     })
22     .catch(err => {
23       logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err)
24       return actor
25     })
26 }
27
28 async function getOrCreateActorAndServerAndModel (actorUrl: string, recurseIfNeeded = true) {
29   let actor = await ActorModel.loadByUrl(actorUrl)
30
31   // We don't have this actor in our database, fetch it on remote
32   if (!actor) {
33     const result = await fetchRemoteActor(actorUrl)
34     if (result === undefined) throw new Error('Cannot fetch remote actor.')
35
36     // Create the attributed to actor
37     // In PeerTube a video channel is owned by an account
38     let ownerActor: ActorModel = undefined
39     if (recurseIfNeeded === true && result.actor.type === 'Group') {
40       const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
41       if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
42
43       try {
44         // Assert we don't recurse another time
45         ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, false)
46       } catch (err) {
47         logger.error('Cannot get or create account attributed to video channel ' + actor.url)
48         throw new Error(err)
49       }
50     }
51
52     const options = {
53       arguments: [ result, ownerActor ],
54       errorMessage: 'Cannot save actor and server with many retries.'
55     }
56     actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options)
57   }
58
59   return actor
60 }
61
62 function saveActorAndServerAndModelIfNotExist (
63   result: FetchRemoteActorResult,
64   ownerActor?: ActorModel,
65   t?: Transaction
66 ): Bluebird<ActorModel> | Promise<ActorModel> {
67   let actor = result.actor
68
69   if (t !== undefined) return save(t)
70
71   return sequelizeTypescript.transaction(t => save(t))
72
73   async function save (t: Transaction) {
74     const actorHost = url.parse(actor.url).host
75
76     const serverOptions = {
77       where: {
78         host: actorHost
79       },
80       defaults: {
81         host: actorHost
82       },
83       transaction: t
84     }
85     const [ server ] = await ServerModel.findOrCreate(serverOptions)
86
87     // Save our new account in database
88     actor.set('serverId', server.id)
89
90     // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
91     // (which could be false in a retried query)
92     const actorCreated = await ActorModel.create(actor.toJSON(), { transaction: t })
93
94     if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
95       const account = await saveAccount(actorCreated, result, t)
96       actorCreated.Account = account
97       actorCreated.Account.Actor = actorCreated
98     } else if (actorCreated.type === 'Group') { // Video channel
99       const videoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
100       actorCreated.VideoChannel = videoChannel
101       actorCreated.VideoChannel.Actor = actorCreated
102     }
103
104     return actorCreated
105   }
106 }
107
108 type FetchRemoteActorResult = {
109   actor: ActorModel
110   name: string
111   summary: string
112   attributedTo: ActivityPubAttributedTo[]
113 }
114 async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResult> {
115   const options = {
116     uri: actorUrl,
117     method: 'GET',
118     headers: {
119       'Accept': ACTIVITY_PUB.ACCEPT_HEADER
120     }
121   }
122
123   logger.info('Fetching remote actor %s.', actorUrl)
124
125   let requestResult
126   try {
127     requestResult = await doRequest(options)
128   } catch (err) {
129     logger.warn('Cannot fetch remote actor %s.', actorUrl, err)
130     return undefined
131   }
132
133   const actorJSON: ActivityPubActor = JSON.parse(requestResult.body)
134   if (isRemoteActorValid(actorJSON) === false) {
135     logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
136     return undefined
137   }
138
139   const followersCount = await fetchActorTotalItems(actorJSON.followers)
140   const followingCount = await fetchActorTotalItems(actorJSON.following)
141
142   const actor = new ActorModel({
143     type: actorJSON.type,
144     uuid: actorJSON.uuid,
145     preferredUsername: actorJSON.preferredUsername,
146     url: actorJSON.id,
147     publicKey: actorJSON.publicKey.publicKeyPem,
148     privateKey: null,
149     followersCount: followersCount,
150     followingCount: followingCount,
151     inboxUrl: actorJSON.inbox,
152     outboxUrl: actorJSON.outbox,
153     sharedInboxUrl: actorJSON.endpoints.sharedInbox,
154     followersUrl: actorJSON.followers,
155     followingUrl: actorJSON.following
156   })
157
158   const name = actorJSON.name || actorJSON.preferredUsername
159   return {
160     actor,
161     name,
162     summary: actorJSON.summary,
163     attributedTo: actorJSON.attributedTo
164   }
165 }
166
167 function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) {
168   return new ActorModel({
169     type,
170     url,
171     preferredUsername,
172     uuid,
173     publicKey: null,
174     privateKey: null,
175     followersCount: 0,
176     followingCount: 0,
177     inboxUrl: url + '/inbox',
178     outboxUrl: url + '/outbox',
179     sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox',
180     followersUrl: url + '/followers',
181     followingUrl: url + '/following'
182   })
183 }
184
185 export {
186   getOrCreateActorAndServerAndModel,
187   saveActorAndServerAndModelIfNotExist,
188   fetchRemoteActor,
189   buildActorInstance,
190   setAsyncActorKeys
191 }
192
193 // ---------------------------------------------------------------------------
194
195 async function fetchActorTotalItems (url: string) {
196   const options = {
197     uri: url,
198     method: 'GET'
199   }
200
201   let requestResult
202   try {
203     requestResult = await doRequest(options)
204   } catch (err) {
205     logger.warn('Cannot fetch remote actor count %s.', url, err)
206     return undefined
207   }
208
209   return requestResult.totalItems ? requestResult.totalItems : 0
210 }
211
212 function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
213   const account = new AccountModel({
214     name: result.name,
215     actorId: actor.id
216   })
217
218   return account.save({ transaction: t })
219 }
220
221 async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
222   const videoChannel = new VideoChannelModel({
223     name: result.name,
224     description: result.summary,
225     actorId: actor.id,
226     accountId: ownerActor.Account.id
227   })
228
229   return videoChannel.save({ transaction: t })
230 }