diff options
author | Chocobozzz <me@florianbigard.com> | 2021-02-26 14:22:25 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-02-26 14:22:25 +0100 |
commit | 8795d6f254bd8f88c385bf77b82cc6f177c94df9 (patch) | |
tree | 88f8f4fced9e0c12db5b43dcac6e44bd94eb8b1a /server | |
parent | 92315d979c3f424d81f8fca3c8831d81e4e2a6d6 (diff) | |
download | PeerTube-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
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/users/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 7 | ||||
-rw-r--r-- | server/initializers/constants.ts | 7 | ||||
-rw-r--r-- | server/initializers/migrations/0605-actor-missing-keys.ts | 34 | ||||
-rw-r--r-- | server/lib/activitypub/actor.ts | 20 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/actor-keys.ts | 20 | ||||
-rw-r--r-- | server/lib/job-queue/job-queue.ts | 5 | ||||
-rw-r--r-- | server/lib/user.ts | 8 |
8 files changed, 80 insertions, 23 deletions
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 | ||
227 | async function registerUser (req: express.Request, res: express.Response) { | 227 | async 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' | |||
3 | import { getServerActor } from '@server/models/application/application' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { MChannelAccountDefault } from '@server/types/models' | 4 | import { MChannelAccountDefault } from '@server/types/models' |
5 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | 5 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
6 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
6 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' | 7 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' |
7 | import { resetSequelizeInstance } from '../../helpers/database-utils' | 8 | import { resetSequelizeInstance } from '../../helpers/database-utils' |
8 | import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 9 | import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
@@ -11,7 +12,6 @@ import { getFormattedObjects } from '../../helpers/utils' | |||
11 | import { CONFIG } from '../../initializers/config' | 12 | import { CONFIG } from '../../initializers/config' |
12 | import { MIMETYPES } from '../../initializers/constants' | 13 | import { MIMETYPES } from '../../initializers/constants' |
13 | import { sequelizeTypescript } from '../../initializers/database' | 14 | import { sequelizeTypescript } from '../../initializers/database' |
14 | import { setAsyncActorKeys } from '../../lib/activitypub/actor' | ||
15 | import { sendUpdateActor } from '../../lib/activitypub/send' | 15 | import { sendUpdateActor } from '../../lib/activitypub/send' |
16 | import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar' | 16 | import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar' |
17 | import { JobQueue } from '../../lib/job-queue' | 17 | import { JobQueue } from '../../lib/job-queue' |
@@ -39,7 +39,6 @@ import { AccountModel } from '../../models/account/account' | |||
39 | import { VideoModel } from '../../models/video/video' | 39 | import { VideoModel } from '../../models/video/video' |
40 | import { VideoChannelModel } from '../../models/video/video-channel' | 40 | import { VideoChannelModel } from '../../models/video/video-channel' |
41 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 41 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
42 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
43 | 42 | ||
44 | const auditLogger = auditLoggerFactory('channels') | 43 | const auditLogger = auditLoggerFactory('channels') |
45 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 44 | const 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 @@ | |||
1 | import { randomInt } from '../../shared/core-utils/miscs/miscs' | ||
2 | import { CronRepeatOptions, EveryRepeatOptions } from 'bull' | 1 | import { CronRepeatOptions, EveryRepeatOptions } from 'bull' |
3 | import { randomBytes } from 'crypto' | 2 | import { randomBytes } from 'crypto' |
4 | import { invert } from 'lodash' | 3 | import { invert } from 'lodash' |
5 | import { join } from 'path' | 4 | import { join } from 'path' |
5 | import { randomInt } from '../../shared/core-utils/miscs/miscs' | ||
6 | import { | 6 | import { |
7 | AbuseState, | 7 | AbuseState, |
8 | JobType, | 8 | JobType, |
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
24 | 24 | ||
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const LAST_MIGRATION_VERSION = 600 | 27 | const 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { createPrivateKey, getPublicKey } from '../../helpers/core-utils' | ||
3 | import { PRIVATE_RSA_KEY_SIZE } from '../constants' | ||
4 | |||
5 | async 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 | |||
27 | function down (options) { | ||
28 | throw new Error('Not implemented.') | ||
29 | } | ||
30 | |||
31 | export { | ||
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' | |||
39 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 39 | import { 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 |
42 | function setAsyncActorKeys <T extends MActor> (actor: T) { | 42 | async 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 | ||
55 | function getOrCreateActorAndServerAndModel ( | 51 | function getOrCreateActorAndServerAndModel ( |
@@ -346,7 +342,7 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel | |||
346 | export { | 342 | export { |
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 @@ | |||
1 | import * as Bull from 'bull' | ||
2 | import { generateAndSaveActorKeys } from '@server/lib/activitypub/actor' | ||
3 | import { ActorModel } from '@server/models/activitypub/actor' | ||
4 | import { ActorKeysPayload } from '@shared/models' | ||
5 | import { logger } from '../../../helpers/logger' | ||
6 | |||
7 | async 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 | |||
18 | export { | ||
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 | |||
25 | import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' | 26 | import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' |
26 | import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' | 27 | import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' |
27 | import { refreshAPObject } from './handlers/activitypub-refresher' | 28 | import { refreshAPObject } from './handlers/activitypub-refresher' |
29 | import { processActorKeys } from './handlers/actor-keys' | ||
28 | import { processEmail } from './handlers/email' | 30 | import { processEmail } from './handlers/email' |
29 | import { processVideoFileImport } from './handlers/video-file-import' | 31 | import { processVideoFileImport } from './handlers/video-file-import' |
30 | import { processVideoImport } from './handlers/video-import' | 32 | import { 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 | ||
49 | type CreateJobOptions = { | 52 | type 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 | |||
10 | import { ActorModel } from '../models/activitypub/actor' | 10 | import { ActorModel } from '../models/activitypub/actor' |
11 | import { MAccountDefault, MActorDefault, MChannelActor } from '../types/models' | 11 | import { MAccountDefault, MActorDefault, MChannelActor } from '../types/models' |
12 | import { MUser, MUserDefault, MUserId } from '../types/models/user' | 12 | import { MUser, MUserDefault, MUserId } from '../types/models/user' |
13 | import { buildActorInstance, setAsyncActorKeys } from './activitypub/actor' | 13 | import { buildActorInstance, generateAndSaveActorKeys } from './activitypub/actor' |
14 | import { getLocalAccountActivityPubUrl } from './activitypub/url' | 14 | import { getLocalAccountActivityPubUrl } from './activitypub/url' |
15 | import { Emailer } from './emailer' | 15 | import { Emailer } from './emailer' |
16 | import { LiveManager } from './live-manager' | 16 | import { 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 | } |