aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/users.ts24
-rw-r--r--server/helpers/database-utils.ts12
-rw-r--r--server/helpers/webfinger.ts5
-rw-r--r--server/initializers/constants.ts4
-rw-r--r--server/lib/activitypub/actor.ts80
-rw-r--r--server/lib/activitypub/process/process-update.ts45
-rw-r--r--server/models/activitypub/actor.ts13
7 files changed, 122 insertions, 61 deletions
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index ef2b63f51..d8ecbdbe2 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -8,6 +8,7 @@ import { retryTransactionWrapper } from '../../helpers/database-utils'
8import { logger } from '../../helpers/logger' 8import { logger } from '../../helpers/logger'
9import { createReqFiles, getFormattedObjects } from '../../helpers/utils' 9import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
10import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers' 10import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
11import { updateActorAvatarInstance } from '../../lib/activitypub'
11import { sendUpdateUser } from '../../lib/activitypub/send' 12import { sendUpdateUser } from '../../lib/activitypub/send'
12import { createUserAccountAndChannel } from '../../lib/user' 13import { createUserAccountAndChannel } from '../../lib/user'
13import { 14import {
@@ -18,7 +19,6 @@ import {
18import { usersUpdateMyAvatarValidator, videosSortValidator } from '../../middlewares/validators' 19import { usersUpdateMyAvatarValidator, videosSortValidator } from '../../middlewares/validators'
19import { AccountVideoRateModel } from '../../models/account/account-video-rate' 20import { AccountVideoRateModel } from '../../models/account/account-video-rate'
20import { UserModel } from '../../models/account/user' 21import { UserModel } from '../../models/account/user'
21import { AvatarModel } from '../../models/avatar/avatar'
22import { VideoModel } from '../../models/video/video' 22import { VideoModel } from '../../models/video/video'
23 23
24const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT) 24const reqAvatarFile = createReqFiles('avatarfile', CONFIG.STORAGE.AVATARS_DIR, AVATAR_MIMETYPE_EXT)
@@ -248,26 +248,12 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
248 248
249 await unlinkPromise(source) 249 await unlinkPromise(source)
250 250
251 const { avatar } = await sequelizeTypescript.transaction(async t => { 251 const avatar = await sequelizeTypescript.transaction(async t => {
252 const avatar = await AvatarModel.create({ 252 await updateActorAvatarInstance(actor, avatarName, t)
253 filename: avatarName
254 }, { transaction: t })
255 253
256 if (actor.Avatar) { 254 await sendUpdateUser(user, t)
257 try {
258 await actor.Avatar.destroy({ transaction: t })
259 } catch (err) {
260 logger.error('Cannot remove old avatar of user %s.', user.username, err)
261 }
262 }
263 255
264 actor.set('avatarId', avatar.id) 256 return avatar
265 actor.Avatar = avatar
266 await actor.save({ transaction: t })
267
268 await sendUpdateUser(user, undefined)
269
270 return { actor, avatar }
271 }) 257 })
272 258
273 return res 259 return res
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts
index fb8ad22b0..78ca768b9 100644
--- a/server/helpers/database-utils.ts
+++ b/server/helpers/database-utils.ts
@@ -1,5 +1,6 @@
1import * as retry from 'async/retry' 1import * as retry from 'async/retry'
2import * as Bluebird from 'bluebird' 2import * as Bluebird from 'bluebird'
3import { Model } from 'sequelize-typescript'
3import { logger } from './logger' 4import { logger } from './logger'
4 5
5type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] } 6type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] }
@@ -34,9 +35,18 @@ function transactionRetryer <T> (func: (err: any, data: T) => any) {
34 }) 35 })
35} 36}
36 37
38function updateInstanceWithAnother <T> (instanceToUpdate: Model<T>, baseInstance: Model<T>) {
39 const obj = baseInstance.toJSON()
40
41 for (const key of Object.keys(obj)) {
42 instanceToUpdate.set(key, obj[key])
43 }
44}
45
37// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
38 47
39export { 48export {
40 retryTransactionWrapper, 49 retryTransactionWrapper,
41 transactionRetryer 50 transactionRetryer,
51 updateInstanceWithAnother
42} 52}
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts
index de8d52c9b..688bf2bab 100644
--- a/server/helpers/webfinger.ts
+++ b/server/helpers/webfinger.ts
@@ -15,6 +15,10 @@ async function loadActorUrlOrGetFromWebfinger (name: string, host: string) {
15 const actor = await ActorModel.loadByNameAndHost(name, host) 15 const actor = await ActorModel.loadByNameAndHost(name, host)
16 if (actor) return actor.url 16 if (actor) return actor.url
17 17
18 return getUrlFromWebfinger(name, host)
19}
20
21async function getUrlFromWebfinger (name: string, host: string) {
18 const webfingerData: WebFingerData = await webfingerLookup(name + '@' + host) 22 const webfingerData: WebFingerData = await webfingerLookup(name + '@' + host)
19 return getLinkOrThrow(webfingerData) 23 return getLinkOrThrow(webfingerData)
20} 24}
@@ -22,6 +26,7 @@ async function loadActorUrlOrGetFromWebfinger (name: string, host: string) {
22// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
23 27
24export { 28export {
29 getUrlFromWebfinger,
25 loadActorUrlOrGetFromWebfinger 30 loadActorUrlOrGetFromWebfinger
26} 31}
27 32
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index d2bcea443..1f18b4401 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -278,7 +278,8 @@ const ACTIVITY_PUB = {
278 VIDEO: [ 'video/mp4', 'video/webm', 'video/ogg' ], // TODO: Merge with VIDEO_MIMETYPE_EXT 278 VIDEO: [ 'video/mp4', 'video/webm', 'video/ogg' ], // TODO: Merge with VIDEO_MIMETYPE_EXT
279 TORRENT: [ 'application/x-bittorrent' ], 279 TORRENT: [ 'application/x-bittorrent' ],
280 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] 280 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
281 } 281 },
282 ACTOR_REFRESH_INTERVAL: 3600 * 24 // 1 day
282} 283}
283 284
284const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = { 285const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
@@ -350,6 +351,7 @@ if (isTestInstance() === true) {
350 REMOTE_SCHEME.WS = 'ws' 351 REMOTE_SCHEME.WS = 'ws'
351 STATIC_MAX_AGE = '0' 352 STATIC_MAX_AGE = '0'
352 ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2 353 ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
354 ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 60 // 1 minute
353 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB 355 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
354} 356}
355 357
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index b6ba2cc22..0882ab843 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -7,10 +7,11 @@ import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/a
7import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' 7import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
8import { isActorObjectValid } from '../../helpers/custom-validators/activitypub/actor' 8import { isActorObjectValid } from '../../helpers/custom-validators/activitypub/actor'
9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10import { retryTransactionWrapper } from '../../helpers/database-utils' 10import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils'
11import { logger } from '../../helpers/logger' 11import { logger } from '../../helpers/logger'
12import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' 12import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
13import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 13import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
14import { getUrlFromWebfinger } from '../../helpers/webfinger'
14import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers' 15import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
15import { AccountModel } from '../../models/account/account' 16import { AccountModel } from '../../models/account/account'
16import { ActorModel } from '../../models/activitypub/actor' 17import { ActorModel } from '../../models/activitypub/actor'
@@ -63,7 +64,7 @@ async function getOrCreateActorAndServerAndModel (actorUrl: string, recurseIfNee
63 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options) 64 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options)
64 } 65 }
65 66
66 return actor 67 return refreshActorIfNeeded(actor)
67} 68}
68 69
69function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { 70function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) {
@@ -84,6 +85,45 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU
84 }) 85 })
85} 86}
86 87
88async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) {
89 const followersCount = await fetchActorTotalItems(attributes.followers)
90 const followingCount = await fetchActorTotalItems(attributes.following)
91
92 actorInstance.set('type', attributes.type)
93 actorInstance.set('uuid', attributes.uuid)
94 actorInstance.set('preferredUsername', attributes.preferredUsername)
95 actorInstance.set('url', attributes.id)
96 actorInstance.set('publicKey', attributes.publicKey.publicKeyPem)
97 actorInstance.set('followersCount', followersCount)
98 actorInstance.set('followingCount', followingCount)
99 actorInstance.set('inboxUrl', attributes.inbox)
100 actorInstance.set('outboxUrl', attributes.outbox)
101 actorInstance.set('sharedInboxUrl', attributes.endpoints.sharedInbox)
102 actorInstance.set('followersUrl', attributes.followers)
103 actorInstance.set('followingUrl', attributes.following)
104}
105
106async function updateActorAvatarInstance (actorInstance: ActorModel, avatarName: string, t: Transaction) {
107 if (avatarName !== undefined) {
108 if (actorInstance.avatarId) {
109 try {
110 await actorInstance.Avatar.destroy({ transaction: t })
111 } catch (err) {
112 logger.error('Cannot remove old avatar of actor %s.', actorInstance.url, err)
113 }
114 }
115
116 const avatar = await AvatarModel.create({
117 filename: avatarName
118 }, { transaction: t })
119
120 actorInstance.set('avatarId', avatar.id)
121 actorInstance.Avatar = avatar
122 }
123
124 return actorInstance
125}
126
87async function fetchActorTotalItems (url: string) { 127async function fetchActorTotalItems (url: string) {
88 const options = { 128 const options = {
89 uri: url, 129 uri: url,
@@ -129,7 +169,9 @@ export {
129 buildActorInstance, 169 buildActorInstance,
130 setAsyncActorKeys, 170 setAsyncActorKeys,
131 fetchActorTotalItems, 171 fetchActorTotalItems,
132 fetchAvatarIfExists 172 fetchAvatarIfExists,
173 updateActorInstance,
174 updateActorAvatarInstance
133} 175}
134 176
135// --------------------------------------------------------------------------- 177// ---------------------------------------------------------------------------
@@ -263,3 +305,35 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
263 305
264 return videoChannel.save({ transaction: t }) 306 return videoChannel.save({ transaction: t })
265} 307}
308
309async function refreshActorIfNeeded (actor: ActorModel) {
310 if (!actor.isOutdated()) return actor
311
312 const actorUrl = await getUrlFromWebfinger(actor.preferredUsername, actor.getHost())
313 const result = await fetchRemoteActor(actorUrl)
314 if (result === undefined) throw new Error('Cannot fetch remote actor in refresh actor.')
315
316 return sequelizeTypescript.transaction(async t => {
317 updateInstanceWithAnother(actor, result.actor)
318
319 if (result.avatarName !== undefined) {
320 await updateActorAvatarInstance(actor, result.avatarName, t)
321 }
322
323 await actor.save({ transaction: t })
324
325 if (actor.Account) {
326 await actor.save({ transaction: t })
327
328 actor.Account.set('name', result.name)
329 await actor.Account.save({ transaction: t })
330 } else if (actor.VideoChannel) {
331 await actor.save({ transaction: t })
332
333 actor.VideoChannel.set('name', result.name)
334 await actor.VideoChannel.save({ transaction: t })
335 }
336
337 return actor
338 })
339}
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 05ea7d272..2c094f7ca 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -8,11 +8,10 @@ import { resetSequelizeInstance } from '../../../helpers/utils'
8import { sequelizeTypescript } from '../../../initializers' 8import { sequelizeTypescript } from '../../../initializers'
9import { AccountModel } from '../../../models/account/account' 9import { AccountModel } from '../../../models/account/account'
10import { ActorModel } from '../../../models/activitypub/actor' 10import { ActorModel } from '../../../models/activitypub/actor'
11import { AvatarModel } from '../../../models/avatar/avatar'
12import { TagModel } from '../../../models/video/tag' 11import { TagModel } from '../../../models/video/tag'
13import { VideoModel } from '../../../models/video/video' 12import { VideoModel } from '../../../models/video/video'
14import { VideoFileModel } from '../../../models/video/video-file' 13import { VideoFileModel } from '../../../models/video/video-file'
15import { fetchActorTotalItems, fetchAvatarIfExists, getOrCreateActorAndServerAndModel } from '../actor' 14import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
16import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 15import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
17 16
18async function processUpdateActivity (activity: ActivityUpdate) { 17async function processUpdateActivity (activity: ActivityUpdate) {
@@ -124,7 +123,6 @@ async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate)
124 const accountAttributesToUpdate = activity.object as ActivityPubActor 123 const accountAttributesToUpdate = activity.object as ActivityPubActor
125 124
126 logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid) 125 logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid)
127 let actorInstance: ActorModel
128 let accountInstance: AccountModel 126 let accountInstance: AccountModel
129 let actorFieldsSave: object 127 let actorFieldsSave: object
130 let accountFieldsSave: object 128 let accountFieldsSave: object
@@ -134,39 +132,14 @@ async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate)
134 132
135 try { 133 try {
136 await sequelizeTypescript.transaction(async t => { 134 await sequelizeTypescript.transaction(async t => {
137 actorInstance = await ActorModel.loadByUrl(accountAttributesToUpdate.id, t) 135 actorFieldsSave = actor.toJSON()
138 if (!actorInstance) throw new Error('Actor ' + accountAttributesToUpdate.id + ' not found.') 136 accountInstance = actor.Account
139 137 accountFieldsSave = actor.Account.toJSON()
140 actorFieldsSave = actorInstance.toJSON()
141 accountInstance = actorInstance.Account
142 accountFieldsSave = actorInstance.Account.toJSON()
143
144 const followersCount = await fetchActorTotalItems(accountAttributesToUpdate.followers)
145 const followingCount = await fetchActorTotalItems(accountAttributesToUpdate.following)
146
147 actorInstance.set('type', accountAttributesToUpdate.type)
148 actorInstance.set('uuid', accountAttributesToUpdate.uuid)
149 actorInstance.set('preferredUsername', accountAttributesToUpdate.preferredUsername)
150 actorInstance.set('url', accountAttributesToUpdate.id)
151 actorInstance.set('publicKey', accountAttributesToUpdate.publicKey.publicKeyPem)
152 actorInstance.set('followersCount', followersCount)
153 actorInstance.set('followingCount', followingCount)
154 actorInstance.set('inboxUrl', accountAttributesToUpdate.inbox)
155 actorInstance.set('outboxUrl', accountAttributesToUpdate.outbox)
156 actorInstance.set('sharedInboxUrl', accountAttributesToUpdate.endpoints.sharedInbox)
157 actorInstance.set('followersUrl', accountAttributesToUpdate.followers)
158 actorInstance.set('followingUrl', accountAttributesToUpdate.following)
159 138
160 if (avatarName !== undefined) { 139 await updateActorInstance(actor, accountAttributesToUpdate)
161 if (actorInstance.avatarId) {
162 await actorInstance.Avatar.destroy({ transaction: t })
163 }
164
165 const avatar = await AvatarModel.create({
166 filename: avatarName
167 }, { transaction: t })
168 140
169 actor.set('avatarId', avatar.id) 141 if (avatarName !== undefined) {
142 await updateActorAvatarInstance(actor, avatarName, t)
170 } 143 }
171 144
172 await actor.save({ transaction: t }) 145 await actor.save({ transaction: t })
@@ -177,8 +150,8 @@ async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate)
177 150
178 logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid) 151 logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid)
179 } catch (err) { 152 } catch (err) {
180 if (actorInstance !== undefined && actorFieldsSave !== undefined) { 153 if (actor !== undefined && actorFieldsSave !== undefined) {
181 resetSequelizeInstance(actorInstance, actorFieldsSave) 154 resetSequelizeInstance(actor, actorFieldsSave)
182 } 155 }
183 156
184 if (accountInstance !== undefined && accountFieldsSave !== undefined) { 157 if (accountInstance !== undefined && accountFieldsSave !== undefined) {
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index ed7fcfe27..707f140af 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -13,7 +13,7 @@ import {
13 isActorPublicKeyValid 13 isActorPublicKeyValid
14} from '../../helpers/custom-validators/activitypub/actor' 14} from '../../helpers/custom-validators/activitypub/actor'
15import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 15import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
16import { ACTIVITY_PUB_ACTOR_TYPES, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' 16import { ACTIVITY_PUB, ACTIVITY_PUB_ACTOR_TYPES, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
17import { AccountModel } from '../account/account' 17import { AccountModel } from '../account/account'
18import { AvatarModel } from '../avatar/avatar' 18import { AvatarModel } from '../avatar/avatar'
19import { ServerModel } from '../server/server' 19import { ServerModel } from '../server/server'
@@ -375,4 +375,15 @@ export class ActorModel extends Model<ActorModel> {
375 375
376 return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath() 376 return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath()
377 } 377 }
378
379 isOutdated () {
380 if (this.isOwned()) return false
381
382 const now = Date.now()
383 const createdAtTime = this.createdAt.getTime()
384 const updatedAtTime = this.updatedAt.getTime()
385
386 return (now - createdAtTime) > ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL &&
387 (now - updatedAtTime) > ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL
388 }
378} 389}