mirror of
https://activitypub.software/TransFem-org/Sharkey.git
synced 2025-04-13 09:44:40 +00:00
don't cache stale actor keys
This commit is contained in:
parent
92382b2ed4
commit
1e3e0caa20
2 changed files with 77 additions and 28 deletions
|
@ -14,10 +14,10 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||||
|
import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
||||||
import { getApId } from './type.js';
|
import { getApId } from './type.js';
|
||||||
import { ApPersonService } from './models/ApPersonService.js';
|
import { ApPersonService } from './models/ApPersonService.js';
|
||||||
import type { IObject } from './type.js';
|
import type { IObject } from './type.js';
|
||||||
import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
|
||||||
|
|
||||||
export type UriParseResult = {
|
export type UriParseResult = {
|
||||||
/** wether the URI was generated by us */
|
/** wether the URI was generated by us */
|
||||||
|
@ -37,9 +37,6 @@ export type UriParseResult = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApDbResolverService implements OnApplicationShutdown {
|
export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
private publicKeyCache: MemoryKVCache<MiUserPublickey | null>;
|
|
||||||
private publicKeyByUserIdCache: MemoryKVCache<MiUserPublickey | null>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
@ -58,8 +55,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
private apLoggerService: ApLoggerService,
|
private apLoggerService: ApLoggerService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
) {
|
) {
|
||||||
this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
// Caches moved to ApPersonService to avoid circular dependency
|
||||||
this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -131,15 +127,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
user: MiRemoteUser;
|
user: MiRemoteUser;
|
||||||
key: MiUserPublickey;
|
key: MiUserPublickey;
|
||||||
} | null> {
|
} | null> {
|
||||||
const key = await this.publicKeyCache.fetch(keyId, async () => {
|
const key = await this.apPersonService.findPublicKeyByKeyId(keyId);
|
||||||
const key = await this.userPublickeysRepository.findOneBy({
|
|
||||||
keyId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (key == null) return null;
|
|
||||||
|
|
||||||
return key;
|
|
||||||
}, key => key != null);
|
|
||||||
|
|
||||||
if (key == null) return null;
|
if (key == null) return null;
|
||||||
|
|
||||||
|
@ -164,11 +152,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser;
|
const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser;
|
||||||
if (user.isDeleted) return null;
|
if (user.isDeleted) return null;
|
||||||
|
|
||||||
const key = await this.publicKeyByUserIdCache.fetch(
|
const key = await this.apPersonService.findPublicKeyByUserId(user.id);
|
||||||
user.id,
|
|
||||||
() => this.userPublickeysRepository.findOneBy({ userId: user.id }),
|
|
||||||
v => v != null,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
@ -184,21 +168,19 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
this.apLoggerService.logger.debug('Re-fetching public key for user', { userId: user.id, uri: user.uri });
|
this.apLoggerService.logger.debug('Re-fetching public key for user', { userId: user.id, uri: user.uri });
|
||||||
await this.apPersonService.updatePerson(user.uri);
|
await this.apPersonService.updatePerson(user.uri);
|
||||||
|
|
||||||
const key = await this.userPublickeysRepository.findOneBy({ userId: user.id });
|
const key = await this.apPersonService.findPublicKeyByUserId(user.id);
|
||||||
this.publicKeyByUserIdCache.set(user.id, key);
|
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
this.apLoggerService.logger.info('Re-fetched public key for user', { userId: user.id, uri: user.uri });
|
this.apLoggerService.logger.info('Re-fetched public key for user', { userId: user.id, uri: user.uri });
|
||||||
} else {
|
} else {
|
||||||
this.apLoggerService.logger.warn('Failed to re-fetch key for user', { userId: user.id, uri: user.uri });
|
this.apLoggerService.logger.warn('Failed to re-fetch key for user', { userId: user.id, uri: user.uri });
|
||||||
}
|
}
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.publicKeyCache.dispose();
|
|
||||||
this.publicKeyByUserIdCache.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import promiseLimit from 'promise-limit';
|
import promiseLimit from 'promise-limit';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
|
@ -40,6 +40,7 @@ import { RoleService } from '@/core/RoleService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
|
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
|
||||||
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import { getApId, getApType, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
import { getApId, getApType, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||||
import { extractApHashtags } from './tag.js';
|
import { extractApHashtags } from './tag.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
|
@ -57,7 +58,11 @@ const summaryLength = 2048;
|
||||||
type Field = Record<'name' | 'value', string>;
|
type Field = Record<'name' | 'value', string>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApPersonService implements OnModuleInit {
|
export class ApPersonService implements OnModuleInit, OnApplicationShutdown {
|
||||||
|
// Moved from ApDbResolverService
|
||||||
|
private readonly publicKeyByKeyIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
||||||
|
private readonly publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
|
||||||
|
|
||||||
private utilityService: UtilityService;
|
private utilityService: UtilityService;
|
||||||
private userEntityService: UserEntityService;
|
private userEntityService: UserEntityService;
|
||||||
private driveFileEntityService: DriveFileEntityService;
|
private driveFileEntityService: DriveFileEntityService;
|
||||||
|
@ -132,6 +137,10 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onApplicationShutdown(): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and convert to actor object
|
* Validate and convert to actor object
|
||||||
* @param x Fetched object
|
* @param x Fetched object
|
||||||
|
@ -355,6 +364,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
let user: MiRemoteUser | null = null;
|
let user: MiRemoteUser | null = null;
|
||||||
|
let publicKey: MiUserPublickey | null = null;
|
||||||
|
|
||||||
//#region カスタム絵文字取得
|
//#region カスタム絵文字取得
|
||||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host)
|
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host)
|
||||||
|
@ -435,7 +445,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (person.publicKey) {
|
if (person.publicKey) {
|
||||||
await transactionalEntityManager.save(new MiUserPublickey({
|
publicKey = await transactionalEntityManager.save(new MiUserPublickey({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
keyId: person.publicKey.id,
|
keyId: person.publicKey.id,
|
||||||
keyPem: person.publicKey.publicKeyPem.trim(),
|
keyPem: person.publicKey.publicKeyPem.trim(),
|
||||||
|
@ -450,6 +460,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
if (u == null) throw new UnrecoverableError(`already registered a user with conflicting data: ${uri}`);
|
if (u == null) throw new UnrecoverableError(`already registered a user with conflicting data: ${uri}`);
|
||||||
|
|
||||||
user = u as MiRemoteUser;
|
user = u as MiRemoteUser;
|
||||||
|
publicKey = await this.userPublickeysRepository.findOneBy({ userId: user.id });
|
||||||
} else {
|
} else {
|
||||||
this.logger.error(e instanceof Error ? e : new Error(e as string));
|
this.logger.error(e instanceof Error ? e : new Error(e as string));
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -461,6 +472,11 @@ export class ApPersonService implements OnModuleInit {
|
||||||
// Register to the cache
|
// Register to the cache
|
||||||
this.cacheService.uriPersonCache.set(user.uri, user);
|
this.cacheService.uriPersonCache.set(user.uri, user);
|
||||||
|
|
||||||
|
// Register public key to the cache.
|
||||||
|
// Value may be null, which indicates that the user has no defined key. (optimization)
|
||||||
|
this.publicKeyByUserIdCache.set(user.id, publicKey);
|
||||||
|
if (publicKey) this.publicKeyByKeyIdCache.set(publicKey.keyId, publicKey);
|
||||||
|
|
||||||
// Register host
|
// Register host
|
||||||
if (this.meta.enableStatsForFederatedInstances) {
|
if (this.meta.enableStatsForFederatedInstances) {
|
||||||
this.federatedInstanceService.fetchOrRegister(host).then(i => {
|
this.federatedInstanceService.fetchOrRegister(host).then(i => {
|
||||||
|
@ -611,10 +627,27 @@ export class ApPersonService implements OnModuleInit {
|
||||||
await this.usersRepository.update(exist.id, updates);
|
await this.usersRepository.update(exist.id, updates);
|
||||||
|
|
||||||
if (person.publicKey) {
|
if (person.publicKey) {
|
||||||
await this.userPublickeysRepository.update({ userId: exist.id }, {
|
const publicKey = new MiUserPublickey({
|
||||||
|
userId: exist.id,
|
||||||
keyId: person.publicKey.id,
|
keyId: person.publicKey.id,
|
||||||
keyPem: person.publicKey.publicKeyPem,
|
keyPem: person.publicKey.publicKeyPem,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create or update key
|
||||||
|
await this.userPublickeysRepository.save(publicKey);
|
||||||
|
|
||||||
|
this.publicKeyByKeyIdCache.set(person.publicKey.id, publicKey);
|
||||||
|
this.publicKeyByUserIdCache.set(exist.id, publicKey);
|
||||||
|
} else {
|
||||||
|
const existingPublicKey = await this.userPublickeysRepository.findOneBy({ userId: exist.id });
|
||||||
|
if (existingPublicKey) {
|
||||||
|
// Delete key
|
||||||
|
await this.userPublickeysRepository.delete({ userId: exist.id });
|
||||||
|
this.publicKeyByKeyIdCache.delete(existingPublicKey.keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null indicates that the user has no key. (optimization)
|
||||||
|
this.publicKeyByUserIdCache.set(exist.id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _description: string | null = null;
|
let _description: string | null = null;
|
||||||
|
@ -825,4 +858,38 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async findPublicKeyByUserId(userId: string): Promise<MiUserPublickey | null> {
|
||||||
|
const publicKey = this.publicKeyByUserIdCache.get(userId) ?? await this.userPublickeysRepository.findOneBy({ userId });
|
||||||
|
|
||||||
|
// This can technically keep a key cached "forever" if it's used enough, but that's ok.
|
||||||
|
// We can never have stale data because the publicKey caches are coherent. (cache updates whenever data changes)
|
||||||
|
if (publicKey) {
|
||||||
|
this.publicKeyByUserIdCache.set(publicKey.userId, publicKey);
|
||||||
|
this.publicKeyByKeyIdCache.set(publicKey.keyId, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async findPublicKeyByKeyId(keyId: string): Promise<MiUserPublickey | null> {
|
||||||
|
const publicKey = this.publicKeyByKeyIdCache.get(keyId) ?? await this.userPublickeysRepository.findOneBy({ keyId });
|
||||||
|
|
||||||
|
// This can technically keep a key cached "forever" if it's used enough, but that's ok.
|
||||||
|
// We can never have stale data because the publicKey caches are coherent. (cache updates whenever data changes)
|
||||||
|
if (publicKey) {
|
||||||
|
this.publicKeyByUserIdCache.set(publicKey.userId, publicKey);
|
||||||
|
this.publicKeyByKeyIdCache.set(publicKey.keyId, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
this.publicKeyByUserIdCache.dispose();
|
||||||
|
this.publicKeyByKeyIdCache.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue