rudeshark.net/packages/backend/src/remote/activitypub/db-resolver.ts

218 lines
4.6 KiB
TypeScript

import escapeRegexp from "escape-regexp";
import config from "@/config/index.js";
import type { Note } from "@/models/entities/note.js";
import type {
CacheableRemoteUser,
CacheableUser,
} from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
Notes,
Users,
UserPublickeys,
MessagingMessages,
} from "@/models/index.js";
import { Cache } from "@/misc/cache.js";
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
import type { IObject } from "./type.js";
import { getApId } from "./type.js";
import { resolvePerson, updatePerson } from "./models/person.js";
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
"publicKeyByUserId",
60 * 30,
);
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;
};
export function 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(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,
};
}
}
export default class DbResolver {
/**
* AP Note => Firefish Note in DB
*/
public async getNoteFromApId(value: string | IObject): Promise<Note | null> {
const parsed = parseUri(value);
if (parsed.local) {
if (parsed.type !== "notes") return null;
return await Notes.findOneBy({
id: parsed.id,
});
} else {
return await Notes.findOne({
where: [
{
uri: parsed.uri,
},
{
url: parsed.uri,
},
],
});
}
}
public async getMessageFromApId(
value: string | IObject,
): Promise<MessagingMessage | null> {
const parsed = parseUri(value);
if (parsed.local) {
if (parsed.type !== "notes") return null;
return await MessagingMessages.findOneBy({
id: parsed.id,
});
} else {
return await MessagingMessages.findOneBy({
uri: parsed.uri,
});
}
}
/**
* AP Person => Firefish User in DB
*/
public async getUserFromApId(
value: string | IObject,
): Promise<CacheableUser | null> {
const parsed = parseUri(value);
if (parsed.local) {
if (parsed.type !== "users") return null;
return (
(await userByIdCache.fetchMaybe(
parsed.id,
() =>
Users.findOneBy({
id: parsed.id,
}).then((x) => x ?? undefined),
true,
)) ?? null
);
} else {
return await uriPersonCache.fetch(
parsed.uri,
() =>
Users.findOneBy({
uri: parsed.uri,
}),
true,
);
}
}
/**
* AP KeyId => Firefish User and Key
*/
public async getAuthUserFromKeyId(keyId: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey | null;
} | null> {
const key = await publicKeyCache.fetch(
keyId,
async () => {
const key = await UserPublickeys.findOneBy({
keyId,
});
if (key == null) return null;
return key;
},
true,
(key) => key != null,
);
if (key == null) return null;
return {
user: (await userByIdCache.fetch(
key.userId,
() => Users.findOneByOrFail({ id: key.userId }),
true,
)) as CacheableRemoteUser,
key,
};
}
/**
* AP Actor id => Firefish User and Key
*/
public async getAuthUserFromApId(uri: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey | null;
} | null> {
const user = (await resolvePerson(uri)) as CacheableRemoteUser;
if (user == null) return null;
const key = await publicKeyByUserIdCache.fetch(
user.id,
() => UserPublickeys.findOneBy({ userId: user.id }),
true,
(v) => v != null,
);
return {
user,
key,
};
}
public async refetchPublicKeyForApId(
user: CacheableRemoteUser,
): Promise<UserPublickey | null> {
await updatePerson(user.uri!, undefined, undefined, user);
const key = await UserPublickeys.findOneBy({ userId: user.id });
if (key != null) {
await publicKeyByUserIdCache.set(user.id, key);
}
return key;
}
}