Fix user notifications on new follow
authorChocobozzz <me@florianbigard.com>
Fri, 2 Aug 2019 08:53:36 +0000 (10:53 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 2 Aug 2019 08:53:36 +0000 (10:53 +0200)
17 files changed:
client/src/index.html
server/lib/activitypub/process/process-accept.ts
server/lib/activitypub/process/process-announce.ts
server/lib/activitypub/process/process-create.ts
server/lib/activitypub/process/process-delete.ts
server/lib/activitypub/process/process-dislike.ts
server/lib/activitypub/process/process-flag.ts
server/lib/activitypub/process/process-follow.ts
server/lib/activitypub/process/process-like.ts
server/lib/activitypub/process/process-reject.ts
server/lib/activitypub/process/process-undo.ts
server/lib/activitypub/process/process-update.ts
server/lib/activitypub/process/process-view.ts
server/lib/activitypub/process/process.ts
server/lib/job-queue/handlers/activitypub-http-fetcher.ts
server/lib/plugins/plugin-manager.ts
server/typings/activitypub-processor.model.ts [new file with mode: 0644]

index 0b610c55a0850fee514ce10d40c56e07bf7ae50b..16dcc02c8661caa1b4669556e536df40d7142057 100644 (file)
 
     <noscript>
       <p>You are blocking Javascript, and we totally get that. However this endpoint uses Angular, so the front end is in full JavaScript and won't work without it.
-      </br></br>
+
+      <br /><br />
       There will be other non JS-based clients to access PeerTube, but for now none is available. Be sure we will update this page with a list once alternative clients are developed. You can certainly develop you own in the meantime as our code is open source and libre software under GNU AGPLv3.0.
-      </br></br>
+
+      <br /><br />
       There might be numerous reasons you refuse to use JavaScript. If it has just to do with security (or lack thereof) of JavaScript-based webapps, then depending on your threat menace you might want to go through the code running on the node you are trying to access, and look for security audits.
       </p>
     </noscript>
index ebb275e348fe840cff7782dd976a8edc314ab4c0..72bb1975e8ac502b831e1a293dc401cb0f86e063 100644 (file)
@@ -2,8 +2,10 @@ import { ActivityAccept } from '../../../../shared/models/activitypub'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { addFetchOutboxJob } from '../actor'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processAcceptActivity (activity: ActivityAccept, targetActor: ActorModel, inboxActor?: ActorModel) {
+async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
+  const { byActor: targetActor, inboxActor } = options
   if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.')
 
   return processAccept(inboxActor, targetActor)
index 1fe347506c76128110cf84894ddbcce9a6add77f..7a59bb84dd8f0c36c8cbfbe7aaf0e18412bdb1a9 100644 (file)
@@ -8,9 +8,14 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { Notifier } from '../../notifier'
 import { VideoModel } from '../../../models/video/video'
 import { logger } from '../../../helpers/logger'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) {
-  return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity)
+async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
+  const { activity, byActor: actorAnnouncer } = options
+  // Only notify if it is not from a fetcher job
+  const notify = options.fromFetch !== true
+
+  return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity, notify)
 }
 
 // ---------------------------------------------------------------------------
@@ -21,7 +26,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
+async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce, notify: boolean) {
   const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
 
   let video: VideoModel
@@ -63,5 +68,5 @@ async function processVideoShare (actorAnnouncer: ActorModel, activity: Activity
     return undefined
   })
 
-  if (videoCreated) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
+  if (videoCreated && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
 }
index 1e893cdebda534ebc7d08c929b1d59ca67a41a29..a979771b678a1bfbb54afb36e9eb4cf656e8d26e 100644 (file)
@@ -12,17 +12,22 @@ import { Notifier } from '../../notifier'
 import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
 import { createOrUpdateVideoPlaylist } from '../playlist'
 import { VideoModel } from '../../../models/video/video'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) {
+async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
+  const { activity, byActor } = options
+
+  // Only notify if it is not from a fetcher job
+  const notify = options.fromFetch !== true
   const activityObject = activity.object
   const activityType = activityObject.type
 
   if (activityType === 'Video') {
-    return processCreateVideo(activity)
+    return processCreateVideo(activity, notify)
   }
 
   if (activityType === 'Note') {
-    return retryTransactionWrapper(processCreateVideoComment, activity, byActor)
+    return retryTransactionWrapper(processCreateVideoComment, activity, byActor, notify)
   }
 
   if (activityType === 'CacheFile') {
@@ -45,12 +50,12 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processCreateVideo (activity: ActivityCreate) {
+async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
   const videoToCreateData = activity.object as VideoTorrentObject
 
   const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
 
-  if (created) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
+  if (created && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
 
   return video
 }
@@ -71,7 +76,7 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: ActorM
   }
 }
 
-async function processCreateVideoComment (activity: ActivityCreate, byActor: ActorModel) {
+async function processCreateVideoComment (activity: ActivityCreate, byActor: ActorModel, notify: boolean) {
   const commentObject = activity.object as VideoCommentObject
   const byAccount = byActor.Account
 
@@ -99,7 +104,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Act
     await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
   }
 
-  if (created === true) Notifier.Instance.notifyOnNewComment(comment)
+  if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
 }
 
 async function processCreatePlaylist (activity: ActivityCreate, byActor: ActorModel) {
index 6f10a50bdc3411317c2d0cde0f144e3b69ecb354..845a7b249a3384b9aa9988ebe2a60224f923a5b4 100644 (file)
@@ -9,8 +9,11 @@ import { VideoChannelModel } from '../../../models/video/video-channel'
 import { VideoCommentModel } from '../../../models/video/video-comment'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { VideoPlaylistModel } from '../../../models/video/video-playlist'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
+
+async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
+  const { activity, byActor } = options
 
-async function processDeleteActivity (activity: ActivityDelete, byActor: ActorModel) {
   const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
 
   if (activity.actor === objectUrl) {
index f06269f8b13f68f41cb14f2392d99e846879569c..a457e5f17ac0a21becc8f354da448a1b95bc0dec 100644 (file)
@@ -7,8 +7,10 @@ import { ActorModel } from '../../../models/activitypub/actor'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { getVideoDislikeActivityPubUrl } from '../url'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processDislikeActivity (activity: ActivityCreate | ActivityDislike, byActor: ActorModel) {
+async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
+  const { activity, byActor } = options
   return retryTransactionWrapper(processDislike, activity, byActor)
 }
 
index 8faab051eaf50c80991772d68707a717e566b6a0..532545e589ab627e40e6dd5b47b4ed28eb2e25b8 100644 (file)
@@ -8,8 +8,10 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { Notifier } from '../../notifier'
 import { getAPId } from '../../../helpers/activitypub'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processFlagActivity (activity: ActivityCreate | ActivityFlag, byActor: ActorModel) {
+async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
+  const { activity, byActor } = options
   return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor)
 }
 
index ed16ba172688d5f11d1d3aeade1439706b766f94..8fe9975f64a0071ee2cb3cc5806ad96b9d0473fb 100644 (file)
@@ -9,8 +9,10 @@ import { Notifier } from '../../notifier'
 import { getAPId } from '../../../helpers/activitypub'
 import { getServerActor } from '../../../helpers/utils'
 import { CONFIG } from '../../../initializers/config'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) {
+async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
+  const { activity, byActor } = options
   const activityObject = getAPId(activity.object)
 
   return retryTransactionWrapper(processFollow, byActor, activityObject)
index bba54a19b1e8de893b2efb960909afc75c166915..706e63c410b0c2c2346a0bc2d21fa7ce71e9c55a 100644 (file)
@@ -7,8 +7,10 @@ import { forwardVideoRelatedActivity } from '../send/utils'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { getVideoLikeActivityPubUrl } from '../url'
 import { getAPId } from '../../../helpers/activitypub'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) {
+async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
+  const { activity, byActor } = options
   return retryTransactionWrapper(processLikeVideo, byActor, activity)
 }
 
index 709a6509628d5a867f2418e3896010a12ad6f1ce..9181906b45efdc97bcebe97957dbd1b6e2af9838 100644 (file)
@@ -2,8 +2,10 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity'
 import { sequelizeTypescript } from '../../../initializers'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processRejectActivity (activity: ActivityReject, targetActor: ActorModel, inboxActor?: ActorModel) {
+async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
+  const { byActor: targetActor, inboxActor } = options
   if (inboxActor === undefined) throw new Error('Need to reject on explicit inbox.')
 
   return processReject(inboxActor, targetActor)
index 692c51904305aed542fc6c6b0d9fbcadbb1b2650..7a0f90adfc3821d6fab4c364269fb986310f7fad 100644 (file)
@@ -10,8 +10,10 @@ import { forwardVideoRelatedActivity } from '../send/utils'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processUndoActivity (activity: ActivityUndo, byActor: ActorModel) {
+async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
+  const { activity, byActor } = options
   const activityToUndo = activity.object
 
   if (activityToUndo.type === 'Like') {
index 71a16daccd92cd83335dffcef51b493223372bb8..1e11dd1fdfa7c756578e86749775d5b4ada4acfa 100644 (file)
@@ -14,8 +14,11 @@ import { createOrUpdateCacheFile } from '../cache-file'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
 import { createOrUpdateVideoPlaylist } from '../playlist'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
+
+async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
+  const { activity, byActor } = options
 
-async function processUpdateActivity (activity: ActivityUpdate, byActor: ActorModel) {
   const objectType = activity.object.type
 
   if (objectType === 'Video') {
index 8f66d3630ecdbea67096f6237ba65203aa55bcf0..0170b74f4502492a8cf438a27fd6e8bc4d185b8b 100644 (file)
@@ -3,8 +3,10 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { Redis } from '../../redis'
 import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-async function processViewActivity (activity: ActivityView | ActivityCreate, byActor: ActorModel) {
+async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) {
+  const { activity, byActor } = options
   return processCreateView(activity, byActor)
 }
 
index 9dd241402dfd1a0bc227792d7e61cf1044c0534d..f4a92e341dc022f3846bd1de0759b8586e23a955 100644 (file)
@@ -15,8 +15,9 @@ import { getOrCreateActorAndServerAndModel } from '../actor'
 import { processDislikeActivity } from './process-dislike'
 import { processFlagActivity } from './process-flag'
 import { processViewActivity } from './process-view'
+import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
 
-const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: ActorModel, inboxActor?: ActorModel) => Promise<any> } = {
+const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
   Create: processCreateActivity,
   Update: processUpdateActivity,
   Delete: processDeleteActivity,
@@ -37,11 +38,15 @@ async function processActivities (
     signatureActor?: ActorModel
     inboxActor?: ActorModel
     outboxUrl?: string
-  } = {}) {
+    fromFetch?: boolean
+  } = {}
+) {
+  const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
+
   const actorsCache: { [ url: string ]: ActorModel } = {}
 
   for (const activity of activities) {
-    if (!options.signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
+    if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
       logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
       continue
     }
@@ -49,17 +54,17 @@ async function processActivities (
     const actorUrl = getAPId(activity.actor)
 
     // When we fetch remote data, we don't have signature
-    if (options.signatureActor && actorUrl !== options.signatureActor.url) {
-      logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, options.signatureActor.url)
+    if (signatureActor && actorUrl !== signatureActor.url) {
+      logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, signatureActor.url)
       continue
     }
 
-    if (options.outboxUrl && checkUrlsSameHost(options.outboxUrl, actorUrl) !== true) {
-      logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', options.outboxUrl, actorUrl)
+    if (outboxUrl && checkUrlsSameHost(outboxUrl, actorUrl) !== true) {
+      logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', outboxUrl, actorUrl)
       continue
     }
 
-    const byActor = options.signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl)
+    const byActor = signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl)
     actorsCache[actorUrl] = byActor
 
     const activityProcessor = processActivity[activity.type]
@@ -69,7 +74,7 @@ async function processActivities (
     }
 
     try {
-      await activityProcessor(activity, byActor, options.inboxActor)
+      await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch })
     } catch (err) {
       logger.warn('Cannot process activity %s.', activity.type, { err })
     }
index 23d33c26fd47e5e51779cb05a944510d3f32dc1c..4da645f072e0e8d475e1277f666bbb546e79b06f 100644 (file)
@@ -33,7 +33,7 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
   if (payload.accountId) account = await AccountModel.load(payload.accountId)
 
   const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
-    'activity': items => processActivities(items, { outboxUrl: payload.uri }),
+    'activity': items => processActivities(items, { outboxUrl: payload.uri, fromFetch: true }),
     'video-likes': items => createRates(items, video, 'like'),
     'video-dislikes': items => createRates(items, video, 'dislike'),
     'video-shares': items => addVideoShares(items, video),
index c9beae2689fd875b7aa819bb68a3f21cf598daa2..444162a0328ac1e1aa415a549f9bc571b1e66643 100644 (file)
@@ -408,9 +408,7 @@ export class PluginManager implements ServerHook {
   private async regeneratePluginGlobalCSS () {
     await this.resetCSSGlobalFile()
 
-    for (const key of Object.keys(this.getRegisteredPlugins())) {
-      const plugin = this.registeredPlugins[key]
-
+    for (const plugin of this.getRegisteredPlugins()) {
       await this.addCSSToGlobalFile(plugin.path, plugin.css)
     }
   }
diff --git a/server/typings/activitypub-processor.model.ts b/server/typings/activitypub-processor.model.ts
new file mode 100644 (file)
index 0000000..7c70251
--- /dev/null
@@ -0,0 +1,9 @@
+import { Activity } from '../../shared/models/activitypub'
+import { ActorModel } from '../models/activitypub/actor'
+
+export type APProcessorOptions<T extends Activity> = {
+  activity: T
+  byActor: ActorModel
+  inboxActor?: ActorModel
+  fromFetch?: boolean
+}