/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import type {IAdminRepository} from '@fluxer/api/src/admin/IAdminRepository'; import type {AdminAuditService} from '@fluxer/api/src/admin/services/AdminAuditService'; import {AdminBanManagementService} from '@fluxer/api/src/admin/services/AdminBanManagementService'; import {AdminUserBanService} from '@fluxer/api/src/admin/services/AdminUserBanService'; import {AdminUserDeletionService} from '@fluxer/api/src/admin/services/AdminUserDeletionService'; import {AdminUserLookupService} from '@fluxer/api/src/admin/services/AdminUserLookupService'; import {AdminUserProfileService} from '@fluxer/api/src/admin/services/AdminUserProfileService'; import {AdminUserSecurityService} from '@fluxer/api/src/admin/services/AdminUserSecurityService'; import {AdminUserUpdatePropagator} from '@fluxer/api/src/admin/services/AdminUserUpdatePropagator'; import type {AuthService} from '@fluxer/api/src/auth/AuthService'; import {createChannelID, createUserID, type UserID} from '@fluxer/api/src/BrandedTypes'; import type {IGuildRepositoryAggregate} from '@fluxer/api/src/guild/repositories/IGuildRepositoryAggregate'; import type {IDiscriminatorService} from '@fluxer/api/src/infrastructure/DiscriminatorService'; import type {EntityAssetService} from '@fluxer/api/src/infrastructure/EntityAssetService'; import type {IGatewayService} from '@fluxer/api/src/infrastructure/IGatewayService'; import type {KVBulkMessageDeletionQueueService} from '@fluxer/api/src/infrastructure/KVBulkMessageDeletionQueueService'; import type {UserCacheService} from '@fluxer/api/src/infrastructure/UserCacheService'; import type {BotMfaMirrorService} from '@fluxer/api/src/oauth/BotMfaMirrorService'; import type {IUserRepository} from '@fluxer/api/src/user/IUserRepository'; import type {UserContactChangeLogService} from '@fluxer/api/src/user/services/UserContactChangeLogService'; import type {ICacheService} from '@fluxer/cache/src/ICacheService'; import type {IEmailService} from '@fluxer/email/src/IEmailService'; import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError'; import type { BulkScheduleUserDeletionRequest, BulkUpdateUserFlagsRequest, CancelBulkMessageDeletionRequest, ChangeDobRequest, ChangeEmailRequest, ChangeUsernameRequest, ClearUserFieldsRequest, DeleteWebAuthnCredentialRequest, DisableForSuspiciousActivityRequest, DisableMfaRequest, ListUserChangeLogRequest, ListUserDmChannelsRequest, ListWebAuthnCredentialsRequest, LookupUserRequest, ResendVerificationEmailRequest, ScheduleAccountDeletionRequest, SendPasswordResetRequest, SetUserAclsRequest, SetUserBotStatusRequest, SetUserSystemStatusRequest, SetUserTraitsRequest, TempBanUserRequest, TerminateSessionsRequest, UnlinkPhoneRequest, UpdateSuspiciousActivityFlagsRequest, VerifyUserEmailRequest, } from '@fluxer/schema/src/domains/admin/AdminUserSchemas'; import {mapUserToAdminResponse} from '../models/UserTypes'; interface AdminUserServiceDeps { userRepository: IUserRepository; guildRepository: IGuildRepositoryAggregate; discriminatorService: IDiscriminatorService; authService: AuthService; emailService: IEmailService; entityAssetService: EntityAssetService; auditService: AdminAuditService; gatewayService: IGatewayService; userCacheService: UserCacheService; adminRepository: IAdminRepository; botMfaMirrorService: BotMfaMirrorService; contactChangeLogService: UserContactChangeLogService; bulkMessageDeletionQueue: KVBulkMessageDeletionQueueService; cacheService: ICacheService; } export class AdminUserService { private readonly lookupService: AdminUserLookupService; private readonly profileService: AdminUserProfileService; private readonly securityService: AdminUserSecurityService; private readonly banService: AdminUserBanService; private readonly deletionService: AdminUserDeletionService; private readonly banManagementService: AdminBanManagementService; private readonly updatePropagator: AdminUserUpdatePropagator; private readonly contactChangeLogService: UserContactChangeLogService; private readonly auditService: AdminAuditService; private readonly userRepository: IUserRepository; private readonly bulkMessageDeletionQueue: KVBulkMessageDeletionQueueService; private readonly cacheService: ICacheService; constructor(deps: AdminUserServiceDeps) { this.updatePropagator = new AdminUserUpdatePropagator({ userCacheService: deps.userCacheService, userRepository: deps.userRepository, guildRepository: deps.guildRepository, gatewayService: deps.gatewayService, }); this.userRepository = deps.userRepository; this.auditService = deps.auditService; this.bulkMessageDeletionQueue = deps.bulkMessageDeletionQueue; this.cacheService = deps.cacheService; this.lookupService = new AdminUserLookupService({ userRepository: deps.userRepository, cacheService: deps.cacheService, }); this.profileService = new AdminUserProfileService({ userRepository: deps.userRepository, discriminatorService: deps.discriminatorService, entityAssetService: deps.entityAssetService, auditService: deps.auditService, updatePropagator: this.updatePropagator, contactChangeLogService: deps.contactChangeLogService, cacheService: deps.cacheService, guildRepository: deps.guildRepository, }); this.securityService = new AdminUserSecurityService({ userRepository: deps.userRepository, authService: deps.authService, emailService: deps.emailService, auditService: deps.auditService, updatePropagator: this.updatePropagator, botMfaMirrorService: deps.botMfaMirrorService, contactChangeLogService: deps.contactChangeLogService, cacheService: deps.cacheService, }); this.banService = new AdminUserBanService({ userRepository: deps.userRepository, authService: deps.authService, emailService: deps.emailService, auditService: deps.auditService, updatePropagator: this.updatePropagator, cacheService: deps.cacheService, }); this.deletionService = new AdminUserDeletionService({ userRepository: deps.userRepository, authService: deps.authService, emailService: deps.emailService, auditService: deps.auditService, updatePropagator: this.updatePropagator, cacheService: deps.cacheService, }); this.banManagementService = new AdminBanManagementService({ adminRepository: deps.adminRepository, auditService: deps.auditService, cacheService: deps.cacheService, }); this.contactChangeLogService = deps.contactChangeLogService; } async lookupUser(data: LookupUserRequest) { return this.lookupService.lookupUser(data); } async clearUserFields(data: ClearUserFieldsRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.clearUserFields(data, adminUserId, auditLogReason); } async setUserBotStatus(data: SetUserBotStatusRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.setUserBotStatus(data, adminUserId, auditLogReason); } async setUserSystemStatus(data: SetUserSystemStatusRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.setUserSystemStatus(data, adminUserId, auditLogReason); } async verifyUserEmail(data: VerifyUserEmailRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.verifyUserEmail(data, adminUserId, auditLogReason); } async changeUsername(data: ChangeUsernameRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.changeUsername(data, adminUserId, auditLogReason); } async changeEmail(data: ChangeEmailRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.changeEmail(data, adminUserId, auditLogReason); } async changeDob(data: ChangeDobRequest, adminUserId: UserID, auditLogReason: string | null) { return this.profileService.changeDob(data, adminUserId, auditLogReason); } async updateUserFlags({ userId, data, adminUserId, auditLogReason, }: { userId: UserID; data: {addFlags: Array; removeFlags: Array}; adminUserId: UserID; auditLogReason: string | null; }) { return this.securityService.updateUserFlags({userId, data, adminUserId, auditLogReason}); } async disableMfa(data: DisableMfaRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.disableMfa(data, adminUserId, auditLogReason); } async sendPasswordReset(data: SendPasswordResetRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.sendPasswordReset(data, adminUserId, auditLogReason); } async resendVerificationEmail( data: ResendVerificationEmailRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.securityService.resendVerificationEmail(data, adminUserId, auditLogReason); } async terminateSessions(data: TerminateSessionsRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.terminateSessions(data, adminUserId, auditLogReason); } async setUserAcls(data: SetUserAclsRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.setUserAcls(data, adminUserId, auditLogReason); } async setUserTraits(data: SetUserTraitsRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.setUserTraits(data, adminUserId, auditLogReason); } async unlinkPhone(data: UnlinkPhoneRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.unlinkPhone(data, adminUserId, auditLogReason); } async updateSuspiciousActivityFlags( data: UpdateSuspiciousActivityFlagsRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.securityService.updateSuspiciousActivityFlags(data, adminUserId, auditLogReason); } async disableForSuspiciousActivity( data: DisableForSuspiciousActivityRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.securityService.disableForSuspiciousActivity(data, adminUserId, auditLogReason); } async bulkUpdateUserFlags(data: BulkUpdateUserFlagsRequest, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.bulkUpdateUserFlags(data, adminUserId, auditLogReason); } async listUserSessions(userId: bigint, adminUserId: UserID, auditLogReason: string | null) { return this.securityService.listUserSessions(userId, adminUserId, auditLogReason); } async listWebAuthnCredentials( data: ListWebAuthnCredentialsRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.securityService.listWebAuthnCredentials(data, adminUserId, auditLogReason); } async deleteWebAuthnCredential( data: DeleteWebAuthnCredentialRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.securityService.deleteWebAuthnCredential(data, adminUserId, auditLogReason); } async listUserDmChannels(data: ListUserDmChannelsRequest) { const userId = createUserID(data.user_id); const user = await this.userRepository.findUnique(userId); if (!user) { throw new UnknownUserError(); } const channels = await this.userRepository.listHistoricalDmChannelsPaginated(userId, { limit: data.limit, beforeChannelId: data.before ? createChannelID(data.before) : undefined, afterChannelId: data.after ? createChannelID(data.after) : undefined, }); return { channels: channels.map((channel) => ({ channel_id: channel.channelId.toString(), channel_type: channel.channelType, recipient_ids: channel.recipientIds.map((recipientId) => recipientId.toString()), last_message_id: channel.lastMessageId?.toString() ?? null, is_open: channel.open, })), }; } async listUserChangeLog(data: ListUserChangeLogRequest) { const entries = await this.contactChangeLogService.listLogs({ userId: createUserID(data.user_id), limit: data.limit, beforeEventId: data.page_token, }); const nextPageToken = entries.length === data.limit && entries.length > 0 ? entries.at(-1)!.event_id.toString() : null; return { entries: entries.map((entry) => ({ event_id: entry.event_id.toString(), field: entry.field, old_value: entry.old_value ?? null, new_value: entry.new_value ?? null, reason: entry.reason, actor_user_id: entry.actor_user_id ? entry.actor_user_id.toString() : null, event_at: entry.event_at.toISOString(), })), next_page_token: nextPageToken, }; } async tempBanUser(data: TempBanUserRequest, adminUserId: UserID, auditLogReason: string | null) { return this.banService.tempBanUser(data, adminUserId, auditLogReason); } async unbanUser(data: {user_id: bigint}, adminUserId: UserID, auditLogReason: string | null) { return this.banService.unbanUser(data, adminUserId, auditLogReason); } async scheduleAccountDeletion( data: ScheduleAccountDeletionRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.deletionService.scheduleAccountDeletion(data, adminUserId, auditLogReason); } async cancelAccountDeletion(data: {user_id: bigint}, adminUserId: UserID, auditLogReason: string | null) { return this.deletionService.cancelAccountDeletion(data, adminUserId, auditLogReason); } async cancelBulkMessageDeletion( data: CancelBulkMessageDeletionRequest, adminUserId: UserID, auditLogReason: string | null, ) { const userId = createUserID(data.user_id); const user = await this.userRepository.findUnique(userId); if (!user) { throw new UnknownUserError(); } const updatedUser = await this.userRepository.patchUpsert( userId, { pending_bulk_message_deletion_at: null, pending_bulk_message_deletion_channel_count: null, pending_bulk_message_deletion_message_count: null, }, user.toRow(), ); await this.bulkMessageDeletionQueue.removeFromQueue(userId); await this.auditService.createAuditLog({ adminUserId, targetType: 'user', targetId: BigInt(userId), action: 'cancel_bulk_message_deletion', auditLogReason, metadata: new Map(), }); return { user: await mapUserToAdminResponse(updatedUser, this.cacheService), }; } async bulkScheduleUserDeletion( data: BulkScheduleUserDeletionRequest, adminUserId: UserID, auditLogReason: string | null, ) { return this.deletionService.bulkScheduleUserDeletion(data, adminUserId, auditLogReason); } async banIp(data: {ip: string}, adminUserId: UserID, auditLogReason: string | null) { return this.banManagementService.banIp(data, adminUserId, auditLogReason); } async unbanIp(data: {ip: string}, adminUserId: UserID, auditLogReason: string | null) { return this.banManagementService.unbanIp(data, adminUserId, auditLogReason); } async checkIpBan(data: {ip: string}) { return this.banManagementService.checkIpBan(data); } async banEmail(data: {email: string}, adminUserId: UserID, auditLogReason: string | null) { return this.banManagementService.banEmail(data, adminUserId, auditLogReason); } async unbanEmail(data: {email: string}, adminUserId: UserID, auditLogReason: string | null) { return this.banManagementService.unbanEmail(data, adminUserId, auditLogReason); } async checkEmailBan(data: {email: string}) { return this.banManagementService.checkEmailBan(data); } async banPhone(data: {phone: string}, adminUserId: UserID, auditLogReason: string | null) { return this.banManagementService.banPhone(data, adminUserId, auditLogReason); } async unbanPhone(data: {phone: string}, adminUserId: UserID, auditLogReason: string | null) { return this.banManagementService.unbanPhone(data, adminUserId, auditLogReason); } async checkPhoneBan(data: {phone: string}) { return this.banManagementService.checkPhoneBan(data); } async listIpBans(data: {limit: number}) { return this.banManagementService.listIpBans(data); } async listEmailBans(data: {limit: number}) { return this.banManagementService.listEmailBans(data); } async listPhoneBans(data: {limit: number}) { return this.banManagementService.listPhoneBans(data); } }