wip
This commit is contained in:
parent
dc1102510b
commit
21d6aacea8
@ -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"`);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
export class Cache<T> {
|
||||
private cache: Map<string | null, { date: number; value: T; }>;
|
||||
public cache: Map<string | null, { date: number; value: T; }>;
|
||||
private lifetime: number;
|
||||
|
||||
constructor(lifetime: Cache<never>['lifetime']) {
|
||||
|
@ -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;
|
||||
|
@ -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<UserPublickey | null>(Infinity);
|
||||
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(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,
|
||||
};
|
||||
}
|
||||
|
@ -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<CacheableUser | null>(Infinity);
|
||||
import { uriPersonCache } from '@/services/user-cache.js';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
|
@ -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<CacheableLocalUser | null>(1000 * 60 * 5);
|
||||
const userByIdCache = new Cache<CacheableLocalUser>(1000 * 60 * 5);
|
||||
const appCache = new Cache<App>(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<ILocalUser | null>);
|
||||
|
||||
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<ILocalUser>);
|
||||
|
@ -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,17 +49,13 @@ 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 && !fullUser.isAdmin) {
|
||||
if (ep.meta.requireAdmin && !user!.isAdmin) {
|
||||
throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
|
||||
}
|
||||
|
||||
if (ep.meta.requireModerator && !fullUser.isAdmin && !fullUser.isModerator) {
|
||||
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)) {
|
||||
throw new ApiError({
|
||||
@ -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<IEndpoint['meta']['limit']> } }, user!).catch(e => {
|
||||
throw new ApiError({
|
||||
|
39
packages/backend/src/services/user-cache.ts
Normal file
39
packages/backend/src/services/user-cache.ts
Normal file
@ -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<CacheableUser>(Infinity);
|
||||
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser | null>(Infinity);
|
||||
export const localUserByIdCache = new Cache<CacheableLocalUser>(Infinity);
|
||||
export const uriPersonCache = new Cache<CacheableUser | null>(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;
|
||||
}
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user