addAllKnowingSharedInboxRecipe
This commit is contained in:
parent
430f0b7911
commit
e4fea42436
4 changed files with 65 additions and 66 deletions
|
@ -49,22 +49,24 @@ 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,
|
||||||
|
@ -72,9 +74,6 @@ export class UserKeypairService implements OnApplicationShutdown {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('invalid type');
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async refresh(userId: MiUser['id']): Promise<void> {
|
public async refresh(userId: MiUser['id']): Promise<void> {
|
||||||
return await this.cache.refresh(userId);
|
return await this.cache.refresh(userId);
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue