/* * 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 '~/admin/IAdminRepository'; import type {AuthService} from '~/auth/AuthService'; import {createUserID, type UserID} from '~/BrandedTypes'; import {UnknownUserError} from '~/Errors'; import type {IGuildRepository} from '~/guild/IGuildRepository'; import type {IDiscriminatorService} from '~/infrastructure/DiscriminatorService'; import type {EntityAssetService} from '~/infrastructure/EntityAssetService'; import type {ICacheService} from '~/infrastructure/ICacheService'; import type {IEmailService} from '~/infrastructure/IEmailService'; import type {IGatewayService} from '~/infrastructure/IGatewayService'; import type {PendingJoinInviteStore} from '~/infrastructure/PendingJoinInviteStore'; import type {RedisBulkMessageDeletionQueueService} from '~/infrastructure/RedisBulkMessageDeletionQueueService'; import type {UserCacheService} from '~/infrastructure/UserCacheService'; import type {InviteService} from '~/invite/InviteService'; import type {BotMfaMirrorService} from '~/oauth/BotMfaMirrorService'; import type {IUserRepository} from '~/user/IUserRepository'; import type {UserContactChangeLogService} from '~/user/services/UserContactChangeLogService'; import type { BulkScheduleUserDeletionRequest, BulkUpdateUserFlagsRequest, CancelBulkMessageDeletionRequest, ChangeDobRequest, ChangeEmailRequest, ChangeUsernameRequest, ClearUserFieldsRequest, DisableForSuspiciousActivityRequest, DisableMfaRequest, ListUserChangeLogRequest, ScheduleAccountDeletionRequest, SendPasswordResetRequest, SetUserAclsRequest, SetUserBotStatusRequest, SetUserSystemStatusRequest, TempBanUserRequest, TerminateSessionsRequest, UnlinkPhoneRequest, UpdateSuspiciousActivityFlagsRequest, VerifyUserEmailRequest, } from '../AdminModel'; import type {AdminAuditService} from './AdminAuditService'; import {AdminBanManagementService} from './AdminBanManagementService'; import {AdminUserBanService} from './AdminUserBanService'; import {AdminUserDeletionService} from './AdminUserDeletionService'; import {AdminUserLookupService} from './AdminUserLookupService'; import {AdminUserProfileService} from './AdminUserProfileService'; import {AdminUserRegistrationService} from './AdminUserRegistrationService'; import {AdminUserSecurityService} from './AdminUserSecurityService'; import {AdminUserUpdatePropagator} from './AdminUserUpdatePropagator'; interface LookupUserRequest { query: string; } interface AdminUserServiceDeps { userRepository: IUserRepository; guildRepository: IGuildRepository; discriminatorService: IDiscriminatorService; authService: AuthService; emailService: IEmailService; entityAssetService: EntityAssetService; auditService: AdminAuditService; gatewayService: IGatewayService; userCacheService: UserCacheService; adminRepository: IAdminRepository; botMfaMirrorService: BotMfaMirrorService; contactChangeLogService: UserContactChangeLogService; bulkMessageDeletionQueue: RedisBulkMessageDeletionQueueService; inviteService: InviteService; pendingJoinInviteStore: PendingJoinInviteStore; 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 registrationService: AdminUserRegistrationService; private readonly updatePropagator: AdminUserUpdatePropagator; private readonly contactChangeLogService: UserContactChangeLogService; private readonly auditService: AdminAuditService; private readonly userRepository: IUserRepository; private readonly bulkMessageDeletionQueue: RedisBulkMessageDeletionQueueService; 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.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, }); 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, }); this.deletionService = new AdminUserDeletionService({ userRepository: deps.userRepository, authService: deps.authService, emailService: deps.emailService, auditService: deps.auditService, updatePropagator: this.updatePropagator, }); this.banManagementService = new AdminBanManagementService({ adminRepository: deps.adminRepository, auditService: deps.auditService, }); this.registrationService = new AdminUserRegistrationService({ userRepository: deps.userRepository, adminRepository: deps.adminRepository, emailService: deps.emailService, auditService: deps.auditService, updatePropagator: this.updatePropagator, inviteService: deps.inviteService, pendingJoinInviteStore: deps.pendingJoinInviteStore, 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 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 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 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(); } 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, }); await this.bulkMessageDeletionQueue.removeFromQueue(userId); await this.auditService.createAuditLog({ adminUserId, targetType: 'user', targetId: BigInt(userId), action: 'cancel_bulk_message_deletion', auditLogReason, metadata: new Map(), }); } 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 listPendingVerifications(limit: number = 100) { return this.registrationService.listPendingVerifications(limit); } async approveRegistration(userId: UserID, adminUserId: UserID, auditLogReason: string | null) { return this.registrationService.approveRegistration(userId, adminUserId, auditLogReason); } async rejectRegistration(userId: UserID, adminUserId: UserID, auditLogReason: string | null) { return this.registrationService.rejectRegistration(userId, adminUserId, auditLogReason); } async bulkApproveRegistrations(userIds: Array, adminUserId: UserID, auditLogReason: string | null) { return this.registrationService.bulkApproveRegistrations(userIds, adminUserId, auditLogReason); } async bulkRejectRegistrations(userIds: Array, adminUserId: UserID, auditLogReason: string | null) { return this.registrationService.bulkRejectRegistrations(userIds, adminUserId, auditLogReason); } }