2022-09-17 18:27:08 +00:00
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
|
|
import escapeRegexp from 'escape-regexp';
|
|
|
|
import { DI } from '@/di-symbols.js';
|
|
|
|
import { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
|
|
|
import { Config } from '@/config.js';
|
|
|
|
import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js';
|
|
|
|
import { Cache } from '@/misc/cache.js';
|
|
|
|
import type { UserPublickey } from '@/models/entities/UserPublickey.js';
|
|
|
|
import { UserCacheService } from '@/core/UserCacheService.js';
|
|
|
|
import type { Note } from '@/models/entities/Note.js';
|
|
|
|
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
|
|
|
|
import { getApId } from './type.js';
|
|
|
|
import { ApPersonService } from './models/ApPersonService.js';
|
|
|
|
import type { IObject } from './type.js';
|
|
|
|
|
|
|
|
export type UriParseResult = {
|
|
|
|
/** wether the URI was generated by us */
|
|
|
|
local: true;
|
|
|
|
/** id in DB */
|
|
|
|
id: string;
|
|
|
|
/** hint of type, e.g. "notes", "users" */
|
|
|
|
type: string;
|
|
|
|
/** any remaining text after type and id, not including the slash after id. undefined if empty */
|
|
|
|
rest?: string;
|
|
|
|
} | {
|
|
|
|
/** wether the URI was generated by us */
|
|
|
|
local: false;
|
|
|
|
/** uri in DB */
|
|
|
|
uri: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class ApDbResolverService {
|
2022-09-18 18:11:50 +00:00
|
|
|
private publicKeyCache: Cache<UserPublickey | null>;
|
|
|
|
private publicKeyByUserIdCache: Cache<UserPublickey | null>;
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
@Inject(DI.config)
|
|
|
|
private config: Config,
|
|
|
|
|
|
|
|
@Inject(DI.usersRepository)
|
|
|
|
private usersRepository: UsersRepository,
|
|
|
|
|
|
|
|
@Inject(DI.messagingMessagesRepository)
|
|
|
|
private messagingMessagesRepository: MessagingMessagesRepository,
|
|
|
|
|
|
|
|
@Inject(DI.notesRepository)
|
|
|
|
private notesRepository: NotesRepository,
|
|
|
|
|
|
|
|
@Inject(DI.userPublickeysRepository)
|
|
|
|
private userPublickeysRepository: UserPublickeysRepository,
|
|
|
|
|
|
|
|
private userCacheService: UserCacheService,
|
|
|
|
private apPersonService: ApPersonService,
|
|
|
|
) {
|
2022-09-18 18:11:50 +00:00
|
|
|
this.publicKeyCache = new Cache<UserPublickey | null>(Infinity);
|
|
|
|
this.publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
|
2022-09-17 18:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public parseUri(value: string | IObject): UriParseResult {
|
|
|
|
const uri = getApId(value);
|
|
|
|
|
|
|
|
// the host part of a URL is case insensitive, so use the 'i' flag.
|
|
|
|
const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i');
|
|
|
|
const matchLocal = uri.match(localRegex);
|
|
|
|
|
|
|
|
if (matchLocal) {
|
|
|
|
return {
|
|
|
|
local: true,
|
|
|
|
type: matchLocal[1],
|
|
|
|
id: matchLocal[2],
|
|
|
|
rest: matchLocal[3],
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
local: false,
|
|
|
|
uri,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* AP Note => Misskey Note in DB
|
|
|
|
*/
|
|
|
|
public async getNoteFromApId(value: string | IObject): Promise<Note | null> {
|
|
|
|
const parsed = this.parseUri(value);
|
|
|
|
|
|
|
|
if (parsed.local) {
|
|
|
|
if (parsed.type !== 'notes') return null;
|
|
|
|
|
|
|
|
return await this.notesRepository.findOneBy({
|
|
|
|
id: parsed.id,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return await this.notesRepository.findOneBy({
|
|
|
|
uri: parsed.uri,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> {
|
|
|
|
const parsed = this.parseUri(value);
|
|
|
|
|
|
|
|
if (parsed.local) {
|
|
|
|
if (parsed.type !== 'notes') return null;
|
|
|
|
|
|
|
|
return await this.messagingMessagesRepository.findOneBy({
|
|
|
|
id: parsed.id,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return await this.messagingMessagesRepository.findOneBy({
|
|
|
|
uri: parsed.uri,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* AP Person => Misskey User in DB
|
|
|
|
*/
|
|
|
|
public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
|
|
|
|
const parsed = this.parseUri(value);
|
|
|
|
|
|
|
|
if (parsed.local) {
|
|
|
|
if (parsed.type !== 'users') return null;
|
|
|
|
|
|
|
|
return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
|
|
|
|
id: parsed.id,
|
|
|
|
}).then(x => x ?? undefined)) ?? null;
|
|
|
|
} else {
|
|
|
|
return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
|
|
|
|
uri: parsed.uri,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* AP KeyId => Misskey User and Key
|
|
|
|
*/
|
|
|
|
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
|
|
|
user: CacheableRemoteUser;
|
|
|
|
key: UserPublickey;
|
|
|
|
} | null> {
|
2022-09-18 18:11:50 +00:00
|
|
|
const key = await this.publicKeyCache.fetch(keyId, async () => {
|
2022-09-17 18:27:08 +00:00
|
|
|
const key = await this.userPublickeysRepository.findOneBy({
|
|
|
|
keyId,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (key == null) return null;
|
|
|
|
|
|
|
|
return key;
|
|
|
|
}, key => key != null);
|
|
|
|
|
|
|
|
if (key == null) return null;
|
|
|
|
|
|
|
|
return {
|
|
|
|
user: await this.userCacheService.userByIdCache.fetch(key.userId, () => this.usersRepository.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser,
|
|
|
|
key,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* AP Actor id => Misskey User and Key
|
|
|
|
*/
|
|
|
|
public async getAuthUserFromApId(uri: string): Promise<{
|
|
|
|
user: CacheableRemoteUser;
|
|
|
|
key: UserPublickey | null;
|
|
|
|
} | null> {
|
|
|
|
const user = await this.apPersonService.resolvePerson(uri) as CacheableRemoteUser;
|
|
|
|
|
|
|
|
if (user == null) return null;
|
|
|
|
|
2022-09-18 18:11:50 +00:00
|
|
|
const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null);
|
2022-09-17 18:27:08 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
user,
|
|
|
|
key,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|