diff --git a/packages/backend/migration/1647777116829-user-indexes.js b/packages/backend/migration/1647777116829-user-indexes.js deleted file mode 100644 index 06b950854..000000000 --- a/packages/backend/migration/1647777116829-user-indexes.js +++ /dev/null @@ -1,17 +0,0 @@ -export class userIndexes1647777116829 { - name = 'userIndexes1647777116829' - - async up(queryRunner) { - await queryRunner.query(`CREATE INDEX "IDX_8977c6037a7bc2cb0c84b6d4db" ON "user" ("isSuspended") `); - await queryRunner.query(`CREATE INDEX "IDX_391e03c755cc3dffa85d85536f" ON "user" ("isSilenced") `); - await queryRunner.query(`CREATE INDEX "IDX_b2033a3235871353c93700a0b6" ON "user" ("isAdmin") `); - await queryRunner.query(`CREATE INDEX "IDX_dfb7b092897e7b354f73e7ae25" ON "user" ("isModerator") `); - } - - async down(queryRunner) { - await queryRunner.query(`DROP INDEX "public"."IDX_dfb7b092897e7b354f73e7ae25"`); - await queryRunner.query(`DROP INDEX "public"."IDX_b2033a3235871353c93700a0b6"`); - await queryRunner.query(`DROP INDEX "public"."IDX_391e03c755cc3dffa85d85536f"`); - await queryRunner.query(`DROP INDEX "public"."IDX_8977c6037a7bc2cb0c84b6d4db"`); - } -} diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index e9966b778..9ce5c3e8b 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,5 +1,5 @@ export class Cache { - private cache: Map; + public cache: Map; private lifetime: number; constructor(lifetime: Cache['lifetime']) { diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index b07e22929..c76824c97 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -106,14 +106,12 @@ export class User { }) public tags: string[]; - @Index() @Column('boolean', { default: false, comment: 'Whether the User is suspended.', }) public isSuspended: boolean; - @Index() @Column('boolean', { default: false, comment: 'Whether the User is silenced.', @@ -138,14 +136,12 @@ export class User { }) public isCat: boolean; - @Index() @Column('boolean', { default: false, comment: 'Whether the User is the admin.', }) public isAdmin: boolean; - @Index() @Column('boolean', { default: false, comment: 'Whether the User is a moderator.', @@ -239,28 +235,8 @@ export interface IRemoteUser extends User { host: string; } -export type CacheableLocalUser = { - id: ILocalUser['id']; - createdAt: ILocalUser['createdAt']; - host: ILocalUser['host']; - username: ILocalUser['username']; - uri: ILocalUser['uri']; - inbox: ILocalUser['inbox']; - sharedInbox: ILocalUser['sharedInbox']; +export type CacheableLocalUser = ILocalUser; - showTimelineReplies: ILocalUser['showTimelineReplies']; -}; - -export type CacheableRemoteUser = { - id: IRemoteUser['id']; - createdAt: IRemoteUser['createdAt']; - host: IRemoteUser['host']; - username: IRemoteUser['username']; - uri: IRemoteUser['uri']; - inbox: IRemoteUser['inbox']; - sharedInbox: IRemoteUser['sharedInbox']; - featured: IRemoteUser['featured']; - followersUri: IRemoteUser['followersUri']; -}; +export type CacheableRemoteUser = IRemoteUser; export type CacheableUser = CacheableLocalUser | CacheableRemoteUser; diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 67ee5663a..3e7d2655f 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -8,8 +8,9 @@ import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index. import { IObject, getApId } from './type.js'; import { resolvePerson } from './models/person.js'; import { Cache } from '@/misc/cache.js'; +import { userByIdCache } from '@/services/user-cache.js'; -const publicKeyCache = new Cache<(UserPublickey & { user: User }) | null>(Infinity); +const publicKeyCache = new Cache(Infinity); const publicKeyByUserIdCache = new Cache(Infinity); export default class DbResolver { @@ -86,19 +87,17 @@ export default class DbResolver { const key = await publicKeyCache.fetch(keyId, async () => { const key = await UserPublickeys.findOne({ keyId, - }, { - relations: ['user'], }); if (key == null) return null; - return key as UserPublickey & { user: User }; + return key; }, key => key != null); if (key == null) return null; return { - user: key.user as IRemoteUser, + user: await userByIdCache.fetch(key.userId, () => Users.findOneOrFail(key.userId)) as CacheableRemoteUser, key, }; } diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 9ea879705..6fde9ef11 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -30,9 +30,7 @@ import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { truncate } from '@/misc/truncate.js'; import { StatusError } from '@/misc/fetch.js'; -import { Cache } from '@/misc/cache.js'; - -const uriPersonCache = new Cache(Infinity); +import { uriPersonCache } from '@/services/user-cache.js'; const logger = apLogger; diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 4e2267ab2..8fb397ca5 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -1,12 +1,11 @@ import isNativeToken from './common/is-native-token.js'; -import { CacheableLocalUser, ILocalUser, User } from '@/models/entities/user.js'; +import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; import { Users, AccessTokens, Apps } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { Cache } from '@/misc/cache.js'; import { App } from '@/models/entities/app.js'; +import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; -const userByNativeTokenCache = new Cache(1000 * 60 * 5); -const userByIdCache = new Cache(1000 * 60 * 5); const appCache = new Cache(Infinity); export class AuthenticationError extends Error { @@ -23,7 +22,7 @@ export default async (token: string | null): Promise<[CacheableLocalUser | null if (isNativeToken(token)) { // TODO: typeorm 3.0にしたら .then(x => x || null) は消せる - const user = await userByNativeTokenCache.fetch(token, + const user = await localUserByNativeTokenCache.fetch(token, () => Users.findOne({ token }).then(x => x || null) as Promise); if (user == null) { @@ -48,7 +47,7 @@ export default async (token: string | null): Promise<[CacheableLocalUser | null lastUsedAt: new Date(), }); - const user = await userByIdCache.fetch(accessToken.userId, + const user = await localUserByIdCache.fetch(accessToken.userId, () => Users.findOne({ id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため }) as Promise); diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index fe9fb352c..9a85e4565 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -6,7 +6,6 @@ import endpoints, { IEndpoint } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; import { AccessToken } from '@/models/entities/access-token.js'; -import { Users } from '@/models/index.js'; const accessDenied = { message: 'Access denied.', @@ -50,16 +49,12 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi }); } - if (ep.meta.requireAdmin || ep.meta.requireModerator) { - const fullUser = await Users.findOneOrFail(user!.id); + if (ep.meta.requireAdmin && !user!.isAdmin) { + throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); + } - if (ep.meta.requireAdmin && !fullUser.isAdmin) { - throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); - } - - if (ep.meta.requireModerator && !fullUser.isAdmin && !fullUser.isModerator) { - throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); - } + if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) { + throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); } if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) { @@ -70,7 +65,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi }); } - if (ep.meta.requireCredential && ep.meta.limit) { + if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) { // Rate limit await limiter(ep as IEndpoint & { meta: { limit: NonNullable } }, user!).catch(e => { throw new ApiError({ diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts new file mode 100644 index 000000000..9b4147acb --- /dev/null +++ b/packages/backend/src/services/user-cache.ts @@ -0,0 +1,39 @@ +import { CacheableLocalUser, CacheableUser, User } from '@/models/entities/user.js'; +import { Users } from '@/models/index.js'; +import { Cache } from '@/misc/cache.js'; +import { subsdcriber } from '@/db/redis.js'; + +export const userByIdCache = new Cache(Infinity); +export const localUserByNativeTokenCache = new Cache(Infinity); +export const localUserByIdCache = new Cache(Infinity); +export const uriPersonCache = new Cache(Infinity); + +subsdcriber.on('message', async (_, data) => { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'userChangeSuspendedState': + case 'userChangeSilencedState': + case 'userChangeModeratorState': + const user = await Users.findOneOrFail(body.id); + userByIdCache.set(user.id, user); + for (const [k, v] of uriPersonCache.cache.entries()) { + if (v.value?.id === user.id) { + uriPersonCache.set(k, user); + } + } + if (Users.isLocalUser(user)) { + localUserByNativeTokenCache.set(user.token, user); + localUserByIdCache.set(user.id, user); + } + break; + case 'userTokenRegenerated': + + break; + default: + break; + } + } +});