addAllKnowingSharedInboxRecipe

This commit is contained in:
tamaina 2024-03-05 12:30:22 +00:00
parent 430f0b7911
commit e4fea42436
4 changed files with 65 additions and 66 deletions

View file

@ -49,30 +49,29 @@ export class UserKeypairService implements OnApplicationShutdown {
/** /**
* *
* @param userIdOrHint user id or MiUserKeypair * @param userIdOrHint user id or MiUserKeypair
* @param preferType 'main' or 'ed25519'; If 'ed25519' is specified and ed25519 keypair is not exists, it will return main keypair * @param preferType If ed25519-like(`ed25519`, `01`, `11`) is specified, ed25519 keypair is returned if exists. Otherwise, main keypair is returned.
* @returns * @returns
*/ */
@bindThis @bindThis
public async getLocalUserKeypairWithKeyId( public async getLocalUserKeypairWithKeyId(
userIdOrHint: MiUser['id'] | MiUserKeypair, preferType: 'main' | 'ed25519' userIdOrHint: MiUser['id'] | MiUserKeypair, preferType?: string,
): Promise<{ keyId: string; publicKey: string; privateKey: string; }> { ): Promise<{ keyId: string; publicKey: string; privateKey: string; }> {
const keypair = typeof userIdOrHint === 'string' ? await this.getUserKeypair(userIdOrHint) : userIdOrHint; const keypair = typeof userIdOrHint === 'string' ? await this.getUserKeypair(userIdOrHint) : userIdOrHint;
if (preferType === 'ed25519' && keypair.ed25519PublicKey != null && keypair.ed25519PrivateKey != null) { if (
preferType && ['01', '11', 'ed25519'].includes(preferType.toLowerCase()) &&
keypair.ed25519PublicKey != null && keypair.ed25519PrivateKey != null
) {
return { return {
keyId: `${this.userEntityService.genLocalUserUri(keypair.userId)}#ed25519-key`, keyId: `${this.userEntityService.genLocalUserUri(keypair.userId)}#ed25519-key`,
publicKey: keypair.ed25519PublicKey, publicKey: keypair.ed25519PublicKey,
privateKey: keypair.ed25519PrivateKey, privateKey: keypair.ed25519PrivateKey,
}; };
} }
if (preferType === 'main') { return {
return { keyId: `${this.userEntityService.genLocalUserUri(keypair.userId)}#main-key`,
keyId: `${this.userEntityService.genLocalUserUri(keypair.userId)}#main-key`, publicKey: keypair.publicKey,
publicKey: keypair.publicKey, privateKey: keypair.privateKey,
privateKey: keypair.privateKey, };
};
}
throw new Error('invalid type');
} }
@bindThis @bindThis

View file

@ -3,27 +3,23 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Not, IsNull } from 'typeorm';
import type { FollowingsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { UserKeypairService } from './UserKeypairService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@Injectable() @Injectable()
export class UserSuspendService { export class UserSuspendService {
constructor( constructor(
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private queueService: QueueService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private userKeypairService: UserKeypairService,
private apDeliverManagerService: ApDeliverManagerService,
) { ) {
} }
@ -32,28 +28,12 @@ export class UserSuspendService {
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信
const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user));
const manager = this.apDeliverManagerService.createDeliverManager(user, content);
const queue: string[] = []; manager.addAllKnowingSharedInboxRecipe();
// process delivre時にはキーペアが消去されているはずなので、ここで挿入する
const followings = await this.followingsRepository.find({ const keypairs = await this.userKeypairService.getLocalUserKeypairWithKeyId(user.id, 'main');
where: [ manager.execute({ privateKey: { keyId: keypairs.keyId, privateKeyPem: keypairs.privateKey } });
{ followerSharedInbox: Not(IsNull()) },
{ followeeSharedInbox: Not(IsNull()) },
],
select: ['followerSharedInbox', 'followeeSharedInbox'],
});
const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox);
for (const inbox of inboxes) {
if (inbox != null && !queue.includes(inbox)) queue.push(inbox);
}
for (const inbox of queue) {
this.queueService.deliver(user, content, inbox, true);
}
} }
} }
@ -62,28 +42,12 @@ export class UserSuspendService {
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false });
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにUndo Delete配信
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user));
const manager = this.apDeliverManagerService.createDeliverManager(user, content);
const queue: string[] = []; manager.addAllKnowingSharedInboxRecipe();
// process delivre時にはキーペアが消去されているはずなので、ここで挿入する
const followings = await this.followingsRepository.find({ const keypairs = await this.userKeypairService.getLocalUserKeypairWithKeyId(user.id, 'main');
where: [ manager.execute({ privateKey: { keyId: keypairs.keyId, privateKeyPem: keypairs.privateKey } });
{ followerSharedInbox: Not(IsNull()) },
{ followeeSharedInbox: Not(IsNull()) },
],
select: ['followerSharedInbox', 'followeeSharedInbox'],
});
const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox);
for (const inbox of inboxes) {
if (inbox != null && !queue.includes(inbox)) queue.push(inbox);
}
for (const inbox of queue) {
this.queueService.deliver(user as any, content, inbox, true);
}
} }
} }
} }

View file

@ -30,12 +30,19 @@ interface IDirectRecipe extends IRecipe {
to: MiRemoteUser; to: MiRemoteUser;
} }
interface IAllKnowingSharedInboxRecipe extends IRecipe {
type: 'AllKnowingSharedInbox';
}
const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe => const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe =>
recipe.type === 'Followers'; recipe.type === 'Followers';
const isDirect = (recipe: IRecipe): recipe is IDirectRecipe => const isDirect = (recipe: IRecipe): recipe is IDirectRecipe =>
recipe.type === 'Direct'; recipe.type === 'Direct';
const isAllKnowingSharedInbox = (recipe: IRecipe): recipe is IAllKnowingSharedInboxRecipe =>
recipe.type === 'AllKnowingSharedInbox';
class DeliverManager { class DeliverManager {
private actor: ThinUser; private actor: ThinUser;
private activity: IActivity | null; private activity: IActivity | null;
@ -96,6 +103,18 @@ class DeliverManager {
this.addRecipe(recipe); this.addRecipe(recipe);
} }
/**
* Add recipe for all-knowing shared inbox deliver
*/
@bindThis
public addAllKnowingSharedInboxRecipe(): void {
const deliver: IAllKnowingSharedInboxRecipe = {
type: 'AllKnowingSharedInbox',
};
this.addRecipe(deliver);
}
/** /**
* Add recipe * Add recipe
* @param recipe Recipe * @param recipe Recipe
@ -120,16 +139,33 @@ class DeliverManager {
// createdが存在するということは新規作成されたということなので、フォロワーに配信する // createdが存在するということは新規作成されたということなので、フォロワーに配信する
this.logger.info(`ed25519 key pair created for user ${this.actor.id} and publishing to followers`); this.logger.info(`ed25519 key pair created for user ${this.actor.id} and publishing to followers`);
// リモートに配信 // リモートに配信
const keyPair = await this.userKeypairService.getLocalUserKeypairWithKeyId(created, 'ed25519'); const keyPair = await this.userKeypairService.getLocalUserKeypairWithKeyId(created, 'main');
await this.accountUpdateService.publishToFollowers(this.actor.id, { keyId: keyPair.keyId, privateKeyPem: keyPair.privateKey }); await this.accountUpdateService.publishToFollowers(this.actor.id, { keyId: keyPair.keyId, privateKeyPem: keyPair.privateKey });
} }
} }
//#endregion //#endregion
//#region correct inboxes by recipes
// The value flags whether it is shared or not. // The value flags whether it is shared or not.
// key: inbox URL, value: whether it is sharedInbox // key: inbox URL, value: whether it is sharedInbox
const inboxes = new Map<string, boolean>(); const inboxes = new Map<string, boolean>();
if (this.recipes.some(r => isAllKnowingSharedInbox(r))) {
// all-knowing shared inbox
const followings = await this.followingsRepository.find({
where: [
{ followerSharedInbox: Not(IsNull()) },
{ followeeSharedInbox: Not(IsNull()) },
],
select: ['followerSharedInbox', 'followeeSharedInbox'],
});
for (const following of followings) {
if (following.followeeSharedInbox) inboxes.set(following.followeeSharedInbox, true);
if (following.followerSharedInbox) inboxes.set(following.followerSharedInbox, true);
}
}
// build inbox list // build inbox list
// Process follower recipes first to avoid duplication when processing direct recipes later. // Process follower recipes first to avoid duplication when processing direct recipes later.
if (this.recipes.some(r => isFollowers(r))) { if (this.recipes.some(r => isFollowers(r))) {
@ -163,6 +199,7 @@ class DeliverManager {
inboxes.set(recipe.to.inbox, false); inboxes.set(recipe.to.inbox, false);
} }
//#endregion
// deliver // deliver
await this.queueService.deliverMany(this.actor, this.activity, inboxes, opts?.privateKey); await this.queueService.deliverMany(this.actor, this.activity, inboxes, opts?.privateKey);

View file

@ -87,8 +87,7 @@ export class ApRequestService {
*/ */
@bindThis @bindThis
private async getPrivateKey(userId: MiUser['id'], level: string): Promise<PrivateKey> { private async getPrivateKey(userId: MiUser['id'], level: string): Promise<PrivateKey> {
const type = level === '00' || level === '10' ? 'ed25519' : 'main'; const keypair = await this.userKeypairService.getLocalUserKeypairWithKeyId(userId, level);
const keypair = await this.userKeypairService.getLocalUserKeypairWithKeyId(userId, type);
return { return {
keyId: keypair.keyId, keyId: keypair.keyId,