mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(admin): omit synthetic accounts from user lookup and search (#2705)
This commit is contained in:
@@ -5,6 +5,7 @@ import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidat
|
||||
import type {WorkerJobPayload} from '@pkgs/worker/src/contracts/WorkerTypes';
|
||||
import type {ApiContext} from '../../ApiContext';
|
||||
import {createGuildID, createUserID, type UserID} from '../../BrandedTypes';
|
||||
import {isSyntheticUserId} from '../../constants/Core';
|
||||
import type {IGuildRepositoryAggregate} from '../../guild/repositories/IGuildRepositoryAggregate';
|
||||
import {Logger} from '../../Logger';
|
||||
import {getGuildSearchService, getUserSearchService} from '../../SearchFactory';
|
||||
@@ -129,12 +130,11 @@ export class AdminSearchService {
|
||||
throw new FeatureTemporarilyDisabledError();
|
||||
}
|
||||
const query = data.query?.trim() || '';
|
||||
const isIdQuery = /^\d+$/.test(query);
|
||||
const directUserId = /^\d+$/.test(query) ? createUserID(BigInt(query)) : null;
|
||||
const canResolveDirectUser = directUserId !== null && !isSyntheticUserId(directUserId) && data.offset === 0;
|
||||
const [searchResult, directUser] = await Promise.all([
|
||||
userSearchService.search(query, {}, {limit: data.limit, offset: data.offset}),
|
||||
isIdQuery && data.offset === 0
|
||||
? userRepository.findUnique(createUserID(BigInt(query))).catch(() => null)
|
||||
: Promise.resolve(null),
|
||||
canResolveDirectUser ? userRepository.findUnique(directUserId).catch(() => null) : Promise.resolve(null),
|
||||
]);
|
||||
const {hits, total} = searchResult;
|
||||
const userIds = hits.map((hit) => createUserID(BigInt(hit.id)));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type {LookupUserRequest} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
|
||||
import type {ApiContext} from '../../ApiContext';
|
||||
import {createUserID} from '../../BrandedTypes';
|
||||
import {isSyntheticUserId} from '../../constants/Core';
|
||||
import {Logger} from '../../Logger';
|
||||
import {mapUserToAdminResponse} from '../models/UserTypes';
|
||||
|
||||
@@ -32,7 +33,7 @@ export class AdminUserLookupService {
|
||||
} else if (/^\d+$/.test(query)) {
|
||||
try {
|
||||
const userId = createUserID(BigInt(query));
|
||||
user = await userRepository.findUnique(userId);
|
||||
user = isSyntheticUserId(userId) ? null : await userRepository.findUnique(userId);
|
||||
} catch (error) {
|
||||
Logger.debug({query, error}, 'Failed to lookup user by numeric ID, invalid ID format');
|
||||
user = null;
|
||||
|
||||
@@ -21,6 +21,8 @@ interface UserListResponse {
|
||||
total: number;
|
||||
}
|
||||
|
||||
const SYNTHETIC_USER_IDS = ['0', '1'];
|
||||
|
||||
async function setLastActiveIp(harness: ApiTestHarness, token: string, ip: string): Promise<void> {
|
||||
await createBuilder(harness, `${token}`)
|
||||
.get('/users/@me')
|
||||
@@ -170,5 +172,35 @@ describe('Admin user directory', () => {
|
||||
expect(result.users.map((user) => user.id)).toEqual([target.userId]);
|
||||
expect(result.users[0]?.email).toBeNull();
|
||||
});
|
||||
test.each(SYNTHETIC_USER_IDS)('omits the synthetic account %s from the resolve selector', async (userId) => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.USER_LOOKUP]);
|
||||
const result = await createBuilder<UserListResponse>(harness, `${admin.token}`)
|
||||
.get(`/admin/users?resolve=${userId}`)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result.users).toEqual([]);
|
||||
expect(result.total).toBe(0);
|
||||
});
|
||||
test.each(SYNTHETIC_USER_IDS)('omits the synthetic account %s from the q selector', async (userId) => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.USER_LOOKUP]);
|
||||
const result = await createBuilder<UserListResponse>(harness, `${admin.token}`)
|
||||
.get(`/admin/users?q=${userId}`)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result.users.map((user) => user.id)).not.toContain(userId);
|
||||
});
|
||||
});
|
||||
describe('GET /admin/users/:user_id', () => {
|
||||
test.each(SYNTHETIC_USER_IDS)('reports no user for the synthetic account %s', async (userId) => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, [AdminACLs.AUTHENTICATE, AdminACLs.USER_LOOKUP]);
|
||||
const result = await createBuilder<UserListResponse>(harness, `${admin.token}`)
|
||||
.get(`/admin/users/${userId}`)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result.users).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createUserID} from '../BrandedTypes';
|
||||
import {DELETED_USER_ID} from '@fluxer/constants/src/UserConstants';
|
||||
import {createUserID, type UserID} from '../BrandedTypes';
|
||||
|
||||
export const SYSTEM_USER_ID = createUserID(0n);
|
||||
|
||||
export function isSyntheticUserId(userId: UserID): boolean {
|
||||
return userId === SYSTEM_USER_ID || userId === DELETED_USER_ID;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user