aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-02-26 14:22:25 +0100
committerChocobozzz <me@florianbigard.com>2021-02-26 14:22:25 +0100
commit8795d6f254bd8f88c385bf77b82cc6f177c94df9 (patch)
tree88f8f4fced9e0c12db5b43dcac6e44bd94eb8b1a
parent92315d979c3f424d81f8fca3c8831d81e4e2a6d6 (diff)
downloadPeerTube-8795d6f254bd8f88c385bf77b82cc6f177c94df9.tar.gz
PeerTube-8795d6f254bd8f88c385bf77b82cc6f177c94df9.tar.zst
PeerTube-8795d6f254bd8f88c385bf77b82cc6f177c94df9.zip
Fix broken local actors
Some channels can't federate because they don't have public/private keys, maybe because the generation failed for various reasons
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.ts2
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending-header.component.ts6
-rw-r--r--server/controllers/api/users/index.ts2
-rw-r--r--server/controllers/api/video-channel.ts7
-rw-r--r--server/initializers/constants.ts7
-rw-r--r--server/initializers/migrations/0605-actor-missing-keys.ts34
-rw-r--r--server/lib/activitypub/actor.ts20
-rw-r--r--server/lib/job-queue/handlers/actor-keys.ts20
-rw-r--r--server/lib/job-queue/job-queue.ts5
-rw-r--r--server/lib/user.ts8
-rw-r--r--shared/models/server/job.model.ts5
11 files changed, 90 insertions, 26 deletions
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts
index 38b4066a4..9f5c04406 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.ts
+++ b/client/src/app/+admin/system/jobs/jobs.component.ts
@@ -22,11 +22,13 @@ export class JobsComponent extends RestTable implements OnInit {
22 jobType: JobTypeClient = 'all' 22 jobType: JobTypeClient = 'all'
23 jobTypes: JobTypeClient[] = [ 23 jobTypes: JobTypeClient[] = [
24 'all', 24 'all',
25
25 'activitypub-follow', 26 'activitypub-follow',
26 'activitypub-http-broadcast', 27 'activitypub-http-broadcast',
27 'activitypub-http-fetcher', 28 'activitypub-http-fetcher',
28 'activitypub-http-unicast', 29 'activitypub-http-unicast',
29 'activitypub-refresher', 30 'activitypub-refresher',
31 'actor-keys',
30 'email', 32 'email',
31 'video-file-import', 33 'video-file-import',
32 'video-import', 34 'video-import',
diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
index a4a1e358f..46e65f305 100644
--- a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
+++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
@@ -39,21 +39,21 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
39 label: $localize`:A variant of Trending videos based on the number of recent interactions, minus user history:Best`, 39 label: $localize`:A variant of Trending videos based on the number of recent interactions, minus user history:Best`,
40 iconName: 'award', 40 iconName: 'award',
41 value: 'best', 41 value: 'best',
42 tooltip: $localize`Videos totalizing the most interactions for recent videos, minus user history`, 42 tooltip: $localize`Videos with the most interactions for recent videos, minus user history`,
43 hidden: true 43 hidden: true
44 }, 44 },
45 { 45 {
46 label: $localize`:A variant of Trending videos based on the number of recent interactions:Hot`, 46 label: $localize`:A variant of Trending videos based on the number of recent interactions:Hot`,
47 iconName: 'flame', 47 iconName: 'flame',
48 value: 'hot', 48 value: 'hot',
49 tooltip: $localize`Videos totalizing the most interactions for recent videos`, 49 tooltip: $localize`Videos with the most interactions for recent videos`,
50 hidden: true 50 hidden: true
51 }, 51 },
52 { 52 {
53 label: $localize`:Main variant of Trending videos based on number of recent views:Views`, 53 label: $localize`:Main variant of Trending videos based on number of recent views:Views`,
54 iconName: 'trending', 54 iconName: 'trending',
55 value: 'most-viewed', 55 value: 'most-viewed',
56 tooltip: $localize`Videos totalizing the most views during the last 24 hours` 56 tooltip: $localize`Videos with the most views during the last 24 hours`
57 }, 57 },
58 { 58 {
59 label: $localize`:A variant of Trending videos based on the number of likes:Likes`, 59 label: $localize`:A variant of Trending videos based on the number of likes:Likes`,
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index fa0688a9e..3be1d55ae 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -221,7 +221,7 @@ async function createUser (req: express.Request, res: express.Response) {
221 id: account.id 221 id: account.id
222 } 222 }
223 } 223 }
224 }).end() 224 })
225} 225}
226 226
227async function registerUser (req: express.Request, res: express.Response) { 227async function registerUser (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 14bd64730..03617dc8d 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -3,6 +3,7 @@ import { Hooks } from '@server/lib/plugins/hooks'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { MChannelAccountDefault } from '@server/types/models' 4import { MChannelAccountDefault } from '@server/types/models'
5import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 5import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
6import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 7import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
7import { resetSequelizeInstance } from '../../helpers/database-utils' 8import { resetSequelizeInstance } from '../../helpers/database-utils'
8import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 9import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
@@ -11,7 +12,6 @@ import { getFormattedObjects } from '../../helpers/utils'
11import { CONFIG } from '../../initializers/config' 12import { CONFIG } from '../../initializers/config'
12import { MIMETYPES } from '../../initializers/constants' 13import { MIMETYPES } from '../../initializers/constants'
13import { sequelizeTypescript } from '../../initializers/database' 14import { sequelizeTypescript } from '../../initializers/database'
14import { setAsyncActorKeys } from '../../lib/activitypub/actor'
15import { sendUpdateActor } from '../../lib/activitypub/send' 15import { sendUpdateActor } from '../../lib/activitypub/send'
16import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar' 16import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar'
17import { JobQueue } from '../../lib/job-queue' 17import { JobQueue } from '../../lib/job-queue'
@@ -39,7 +39,6 @@ import { AccountModel } from '../../models/account/account'
39import { VideoModel } from '../../models/video/video' 39import { VideoModel } from '../../models/video/video'
40import { VideoChannelModel } from '../../models/video/video-channel' 40import { VideoChannelModel } from '../../models/video/video-channel'
41import { VideoPlaylistModel } from '../../models/video/video-playlist' 41import { VideoPlaylistModel } from '../../models/video/video-playlist'
42import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
43 42
44const auditLogger = auditLoggerFactory('channels') 43const auditLogger = auditLoggerFactory('channels')
45const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 44const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
@@ -168,8 +167,8 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
168 return createLocalVideoChannel(videoChannelInfo, account, t) 167 return createLocalVideoChannel(videoChannelInfo, account, t)
169 }) 168 })
170 169
171 setAsyncActorKeys(videoChannelCreated.Actor) 170 const payload = { actorId: videoChannelCreated.actorId }
172 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.url, { err })) 171 await JobQueue.Instance.createJobWithPromise({ type: 'actor-keys', payload })
173 172
174 auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())) 173 auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON()))
175 logger.info('Video channel %s created.', videoChannelCreated.Actor.url) 174 logger.info('Video channel %s created.', videoChannelCreated.Actor.url)
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index bbf55d7a4..74192d590 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,8 +1,8 @@
1import { randomInt } from '../../shared/core-utils/miscs/miscs'
2import { CronRepeatOptions, EveryRepeatOptions } from 'bull' 1import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
3import { randomBytes } from 'crypto' 2import { randomBytes } from 'crypto'
4import { invert } from 'lodash' 3import { invert } from 'lodash'
5import { join } from 'path' 4import { join } from 'path'
5import { randomInt } from '../../shared/core-utils/miscs/miscs'
6import { 6import {
7 AbuseState, 7 AbuseState,
8 JobType, 8 JobType,
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
24 24
25// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
26 26
27const LAST_MIGRATION_VERSION = 600 27const LAST_MIGRATION_VERSION = 605
28 28
29// --------------------------------------------------------------------------- 29// ---------------------------------------------------------------------------
30 30
@@ -141,6 +141,7 @@ const JOB_ATTEMPTS: { [id in JobType]: number } = {
141 'video-transcoding': 1, 141 'video-transcoding': 1,
142 'video-import': 1, 142 'video-import': 1,
143 'email': 5, 143 'email': 5,
144 'actor-keys': 3,
144 'videos-views': 1, 145 'videos-views': 1,
145 'activitypub-refresher': 1, 146 'activitypub-refresher': 1,
146 'video-redundancy': 1, 147 'video-redundancy': 1,
@@ -153,6 +154,7 @@ const JOB_CONCURRENCY: { [id in JobType]?: number } = {
153 'activitypub-follow': 1, 154 'activitypub-follow': 1,
154 'video-file-import': 1, 155 'video-file-import': 1,
155 'email': 5, 156 'email': 5,
157 'actor-keys': 1,
156 'videos-views': 1, 158 'videos-views': 1,
157 'activitypub-refresher': 1, 159 'activitypub-refresher': 1,
158 'video-redundancy': 1, 160 'video-redundancy': 1,
@@ -167,6 +169,7 @@ const JOB_TTL: { [id in JobType]: number } = {
167 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long 169 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long
168 'video-import': 1000 * 3600 * 2, // 2 hours 170 'video-import': 1000 * 3600 * 2, // 2 hours
169 'email': 60000 * 10, // 10 minutes 171 'email': 60000 * 10, // 10 minutes
172 'actor-keys': 60000 * 20, // 20 minutes
170 'videos-views': undefined, // Unlimited 173 'videos-views': undefined, // Unlimited
171 'activitypub-refresher': 60000 * 10, // 10 minutes 174 'activitypub-refresher': 60000 * 10, // 10 minutes
172 'video-redundancy': 1000 * 3600 * 3, // 3 hours 175 'video-redundancy': 1000 * 3600 * 3, // 3 hours
diff --git a/server/initializers/migrations/0605-actor-missing-keys.ts b/server/initializers/migrations/0605-actor-missing-keys.ts
new file mode 100644
index 000000000..72d9b359d
--- /dev/null
+++ b/server/initializers/migrations/0605-actor-missing-keys.ts
@@ -0,0 +1,34 @@
1import * as Sequelize from 'sequelize'
2import { createPrivateKey, getPublicKey } from '../../helpers/core-utils'
3import { PRIVATE_RSA_KEY_SIZE } from '../constants'
4
5async function up (utils: {
6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize
9 db: any
10}): Promise<void> {
11
12 {
13 const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL'
14 const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
15 const actors = await utils.sequelize.query<any>(query, options)
16
17 for (const actor of actors) {
18 const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE)
19 const { publicKey } = await getPublicKey(key)
20
21 const queryUpdate = `UPDATE "actor" SET "publicKey" = '${publicKey}', "privateKey" = '${key}' WHERE id = ${actor.id}`
22 await utils.sequelize.query(queryUpdate)
23 }
24 }
25}
26
27function down (options) {
28 throw new Error('Not implemented.')
29}
30
31export {
32 up,
33 down
34}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index dbb243d3a..a726f9e20 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -39,17 +39,13 @@ import { getServerActor } from '@server/models/application/application'
39import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 39import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
40 40
41// Set account keys, this could be long so process after the account creation and do not block the client 41// Set account keys, this could be long so process after the account creation and do not block the client
42function setAsyncActorKeys <T extends MActor> (actor: T) { 42async function generateAndSaveActorKeys <T extends MActor> (actor: T) {
43 return createPrivateAndPublicKeys() 43 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
44 .then(({ publicKey, privateKey }) => { 44
45 actor.publicKey = publicKey 45 actor.publicKey = publicKey
46 actor.privateKey = privateKey 46 actor.privateKey = privateKey
47 return actor.save() 47
48 }) 48 return actor.save()
49 .catch(err => {
50 logger.error('Cannot set public/private keys of actor %d.', actor.url, { err })
51 return actor
52 })
53} 49}
54 50
55function getOrCreateActorAndServerAndModel ( 51function getOrCreateActorAndServerAndModel (
@@ -346,7 +342,7 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel
346export { 342export {
347 getOrCreateActorAndServerAndModel, 343 getOrCreateActorAndServerAndModel,
348 buildActorInstance, 344 buildActorInstance,
349 setAsyncActorKeys, 345 generateAndSaveActorKeys,
350 fetchActorTotalItems, 346 fetchActorTotalItems,
351 getAvatarInfoIfExists, 347 getAvatarInfoIfExists,
352 updateActorInstance, 348 updateActorInstance,
diff --git a/server/lib/job-queue/handlers/actor-keys.ts b/server/lib/job-queue/handlers/actor-keys.ts
new file mode 100644
index 000000000..8da549640
--- /dev/null
+++ b/server/lib/job-queue/handlers/actor-keys.ts
@@ -0,0 +1,20 @@
1import * as Bull from 'bull'
2import { generateAndSaveActorKeys } from '@server/lib/activitypub/actor'
3import { ActorModel } from '@server/models/activitypub/actor'
4import { ActorKeysPayload } from '@shared/models'
5import { logger } from '../../../helpers/logger'
6
7async function processActorKeys (job: Bull.Job) {
8 const payload = job.data as ActorKeysPayload
9 logger.info('Processing email in job %d.', job.id)
10
11 const actor = await ActorModel.load(payload.actorId)
12
13 await generateAndSaveActorKeys(actor)
14}
15
16// ---------------------------------------------------------------------------
17
18export {
19 processActorKeys
20}
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index 72fed6072..efda2e038 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -7,6 +7,7 @@ import {
7 ActivitypubHttpBroadcastPayload, 7 ActivitypubHttpBroadcastPayload,
8 ActivitypubHttpFetcherPayload, 8 ActivitypubHttpFetcherPayload,
9 ActivitypubHttpUnicastPayload, 9 ActivitypubHttpUnicastPayload,
10 ActorKeysPayload,
10 EmailPayload, 11 EmailPayload,
11 JobState, 12 JobState,
12 JobType, 13 JobType,
@@ -25,6 +26,7 @@ import { processActivityPubHttpBroadcast } from './handlers/activitypub-http-bro
25import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' 26import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher'
26import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' 27import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast'
27import { refreshAPObject } from './handlers/activitypub-refresher' 28import { refreshAPObject } from './handlers/activitypub-refresher'
29import { processActorKeys } from './handlers/actor-keys'
28import { processEmail } from './handlers/email' 30import { processEmail } from './handlers/email'
29import { processVideoFileImport } from './handlers/video-file-import' 31import { processVideoFileImport } from './handlers/video-file-import'
30import { processVideoImport } from './handlers/video-import' 32import { processVideoImport } from './handlers/video-import'
@@ -44,6 +46,7 @@ type CreateJobArgument =
44 { type: 'activitypub-refresher', payload: RefreshPayload } | 46 { type: 'activitypub-refresher', payload: RefreshPayload } |
45 { type: 'videos-views', payload: {} } | 47 { type: 'videos-views', payload: {} } |
46 { type: 'video-live-ending', payload: VideoLiveEndingPayload } | 48 { type: 'video-live-ending', payload: VideoLiveEndingPayload } |
49 { type: 'actor-keys', payload: ActorKeysPayload } |
47 { type: 'video-redundancy', payload: VideoRedundancyPayload } 50 { type: 'video-redundancy', payload: VideoRedundancyPayload }
48 51
49type CreateJobOptions = { 52type CreateJobOptions = {
@@ -63,6 +66,7 @@ const handlers: { [id in JobType]: (job: Bull.Job) => Promise<any> } = {
63 'videos-views': processVideosViews, 66 'videos-views': processVideosViews,
64 'activitypub-refresher': refreshAPObject, 67 'activitypub-refresher': refreshAPObject,
65 'video-live-ending': processVideoLiveEnding, 68 'video-live-ending': processVideoLiveEnding,
69 'actor-keys': processActorKeys,
66 'video-redundancy': processVideoRedundancy 70 'video-redundancy': processVideoRedundancy
67} 71}
68 72
@@ -78,6 +82,7 @@ const jobTypes: JobType[] = [
78 'videos-views', 82 'videos-views',
79 'activitypub-refresher', 83 'activitypub-refresher',
80 'video-redundancy', 84 'video-redundancy',
85 'actor-keys',
81 'video-live-ending' 86 'video-live-ending'
82] 87]
83 88
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 6b0fd9b88..e1892f22c 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -10,7 +10,7 @@ import { UserNotificationSettingModel } from '../models/account/user-notificatio
10import { ActorModel } from '../models/activitypub/actor' 10import { ActorModel } from '../models/activitypub/actor'
11import { MAccountDefault, MActorDefault, MChannelActor } from '../types/models' 11import { MAccountDefault, MActorDefault, MChannelActor } from '../types/models'
12import { MUser, MUserDefault, MUserId } from '../types/models/user' 12import { MUser, MUserDefault, MUserId } from '../types/models/user'
13import { buildActorInstance, setAsyncActorKeys } from './activitypub/actor' 13import { buildActorInstance, generateAndSaveActorKeys } from './activitypub/actor'
14import { getLocalAccountActivityPubUrl } from './activitypub/url' 14import { getLocalAccountActivityPubUrl } from './activitypub/url'
15import { Emailer } from './emailer' 15import { Emailer } from './emailer'
16import { LiveManager } from './live-manager' 16import { LiveManager } from './live-manager'
@@ -55,8 +55,8 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
55 }) 55 })
56 56
57 const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([ 57 const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([
58 setAsyncActorKeys(account.Actor), 58 generateAndSaveActorKeys(account.Actor),
59 setAsyncActorKeys(videoChannel.Actor) 59 generateAndSaveActorKeys(videoChannel.Actor)
60 ]) 60 ])
61 61
62 account.Actor = accountActorWithKeys 62 account.Actor = accountActorWithKeys
@@ -101,7 +101,7 @@ async function createApplicationActor (applicationId: number) {
101 type: 'Application' 101 type: 'Application'
102 }) 102 })
103 103
104 accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor) 104 accountCreated.Actor = await generateAndSaveActorKeys(accountCreated.Actor)
105 105
106 return accountCreated 106 return accountCreated
107} 107}
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index 07d7efe22..c693827b0 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -17,6 +17,7 @@ export type JobType =
17 | 'activitypub-refresher' 17 | 'activitypub-refresher'
18 | 'video-redundancy' 18 | 'video-redundancy'
19 | 'video-live-ending' 19 | 'video-live-ending'
20 | 'actor-keys'
20 21
21export interface Job { 22export interface Job {
22 id: number 23 id: number
@@ -131,3 +132,7 @@ export type VideoTranscodingPayload =
131export interface VideoLiveEndingPayload { 132export interface VideoLiveEndingPayload {
132 videoId: number 133 videoId: number
133} 134}
135
136export interface ActorKeysPayload {
137 actorId: number
138}