forked from FoundKeyGang/FoundKey
server: properly handle logical deletion
closes FoundKeyGang/FoundKey#329
This commit is contained in:
parent
71dfd229b0
commit
b14f3e8cdc
6 changed files with 56 additions and 29 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { IsNull, Not } from 'typeorm';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { User } from '@/models/entities/user.js';
|
import { User } from '@/models/entities/user.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
|
@ -28,8 +29,12 @@ export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null)
|
||||||
/**
|
/**
|
||||||
* Get user for API processing
|
* Get user for API processing
|
||||||
*/
|
*/
|
||||||
export async function getUser(userId: User['id']) {
|
export async function getUser(userId: User['id'], includeSuspended = false) {
|
||||||
const user = await Users.findOneBy({ id: userId });
|
const user = await Users.findOneBy(
|
||||||
|
id: userId,
|
||||||
|
isDeleted: false,
|
||||||
|
isSuspended: !includeSuspended,
|
||||||
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new ApiError('NO_SUCH_USER');
|
throw new ApiError('NO_SUCH_USER');
|
||||||
|
@ -41,10 +46,15 @@ export async function getUser(userId: User['id']) {
|
||||||
/**
|
/**
|
||||||
* Get remote user for API processing
|
* Get remote user for API processing
|
||||||
*/
|
*/
|
||||||
export async function getRemoteUser(userId: User['id']) {
|
export async function getRemoteUser(userId: User['id'], includeSuspended = false) {
|
||||||
const user = await getUser(userId);
|
const user = await Users.findOneBy(
|
||||||
|
id: userId,
|
||||||
|
host: Not(IsNull()),
|
||||||
|
isDeleted: false,
|
||||||
|
isSuspended: !includedSuspended,
|
||||||
|
});
|
||||||
|
|
||||||
if (!Users.isRemoteUser(user)) {
|
if (user == null) {
|
||||||
throw new ApiError('NO_SUCH_USER');
|
throw new ApiError('NO_SUCH_USER');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +64,15 @@ export async function getRemoteUser(userId: User['id']) {
|
||||||
/**
|
/**
|
||||||
* Get local user for API processing
|
* Get local user for API processing
|
||||||
*/
|
*/
|
||||||
export async function getLocalUser(userId: User['id']) {
|
export async function getLocalUser(userId: User['id'], includeSuspended = false) {
|
||||||
const user = await getUser(userId);
|
const user = await Users.findOneBy(
|
||||||
|
id: userId,
|
||||||
|
host: IsNull(),
|
||||||
|
isDeleted: false,
|
||||||
|
isSuspended: !includeSuspended,
|
||||||
|
});
|
||||||
|
|
||||||
if (!Users.isLocalUser(user)) {
|
if (user == null) {
|
||||||
throw new ApiError('NO_SUCH_USER');
|
throw new ApiError('NO_SUCH_USER');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Users } from '@/models/index.js';
|
||||||
import { ApiError } from '@/server/api/error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
import { deleteAccount } from '@/services/delete-account.js';
|
import { deleteAccount } from '@/services/delete-account.js';
|
||||||
import define from '@/server/api/define.js';
|
import define from '@/server/api/define.js';
|
||||||
|
import { getUser } from '@/server/api/common/getters.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
@ -22,14 +23,9 @@ export const paramDef = {
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default define(meta, paramDef, async (ps) => {
|
export default define(meta, paramDef, async (ps) => {
|
||||||
const user = await Users.findOneBy({
|
const user = await getUser(ps.userId, true);
|
||||||
id: ps.userId,
|
|
||||||
isDeleted: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user == null) {
|
if (user.isAdmin) {
|
||||||
throw new ApiError('NO_SUCH_USER');
|
|
||||||
} else if (user.isAdmin) {
|
|
||||||
throw new ApiError('IS_ADMIN');
|
throw new ApiError('IS_ADMIN');
|
||||||
} else if (user.isModerator) {
|
} else if (user.isModerator) {
|
||||||
throw new ApiError('IS_MODERATOR');
|
throw new ApiError('IS_MODERATOR');
|
||||||
|
|
|
@ -47,6 +47,7 @@ export default async (ctx: Koa.Context) => {
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
|
isDeleted: false,
|
||||||
}) as ILocalUser;
|
}) as ILocalUser;
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
|
|
@ -223,6 +223,7 @@ const getFeed = async (acct: string) => {
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
|
isDeleted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return user && await packFeed(user);
|
return user && await packFeed(user);
|
||||||
|
@ -272,6 +273,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
|
isDeleted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -304,6 +306,7 @@ router.get('/users/:user', async ctx => {
|
||||||
id: ctx.params.user,
|
id: ctx.params.user,
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
|
isDeleted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -419,6 +422,8 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
|
isSuspended: false,
|
||||||
|
isDeleted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Users } from '@/models/index.js';
|
import { AccessTokens, Users } from '@/models/index.js';
|
||||||
import { createDeleteAccountJob } from '@/queue/index.js';
|
import { createDeleteAccountJob } from '@/queue/index.js';
|
||||||
import { publishUserEvent } from './stream.js';
|
import { publishUserEvent } from './stream.js';
|
||||||
import { doPostSuspend } from './suspend-user.js';
|
import { doPostSuspend } from './suspend-user.js';
|
||||||
|
@ -7,9 +7,15 @@ export async function deleteAccount(user: {
|
||||||
id: string;
|
id: string;
|
||||||
host: string | null;
|
host: string | null;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await Users.update(user.id, {
|
await Promise.all([
|
||||||
|
Users.update(user.id, {
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
});
|
}),
|
||||||
|
// revoke all of the users access tokens to block API access
|
||||||
|
AccessTokens.delete({
|
||||||
|
userId: user.id,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
// Terminate streaming
|
// Terminate streaming
|
||||||
|
|
|
@ -6,15 +6,15 @@ import { subscriber } from '@/db/redis.js';
|
||||||
|
|
||||||
export const userByIdCache = new Cache<User>(
|
export const userByIdCache = new Cache<User>(
|
||||||
Infinity,
|
Infinity,
|
||||||
async (id) => await Users.findOneBy({ id }) ?? undefined,
|
async (id) => await Users.findOneBy({ id, isDeleted: false }) ?? undefined,
|
||||||
);
|
);
|
||||||
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser>(
|
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser>(
|
||||||
Infinity,
|
Infinity,
|
||||||
async (token) => await Users.findOneBy({ token, host: IsNull() }) as ILocalUser | null ?? undefined,
|
async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: false }) as ILocalUser | null ?? undefined,
|
||||||
);
|
);
|
||||||
export const uriPersonCache = new Cache<User>(
|
export const uriPersonCache = new Cache<User>(
|
||||||
Infinity,
|
Infinity,
|
||||||
async (uri) => await Users.findOneBy({ uri }) ?? undefined,
|
async (uri) => await Users.findOneBy({ uri, isDeleted: false }) ?? undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
subscriber.on('message', async (_, data) => {
|
subscriber.on('message', async (_, data) => {
|
||||||
|
@ -28,15 +28,19 @@ subscriber.on('message', async (_, data) => {
|
||||||
case 'userChangeModeratorState':
|
case 'userChangeModeratorState':
|
||||||
case 'remoteUserUpdated': {
|
case 'remoteUserUpdated': {
|
||||||
const user = await Users.findOneByOrFail({ id: body.id });
|
const user = await Users.findOneByOrFail({ id: body.id });
|
||||||
|
if (user.isDeleted) {
|
||||||
|
userByIdCache.delete(user.id);
|
||||||
|
uriPersonCache.delete(user.uri);
|
||||||
|
if (Users.isLocalUser(user)) {
|
||||||
|
localUserByNativeTokenCache.delete(user.token);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
userByIdCache.set(user.id, user);
|
userByIdCache.set(user.id, user);
|
||||||
for (const [k, v] of uriPersonCache.cache.entries()) {
|
uriPersonCache.set(user.uri, user);
|
||||||
if (v.value.id === user.id) {
|
|
||||||
uriPersonCache.set(k, user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
localUserByNativeTokenCache.set(user.token, user);
|
localUserByNativeTokenCache.set(user.token, user);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'userTokenRegenerated': {
|
case 'userTokenRegenerated': {
|
||||||
|
|
Loading…
Reference in a new issue