import * as plugins from '../../plugins.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import { requireValidIdentity, requireAdminIdentity } from '../helpers/guards.ts'; import { User } from '../../models/user.ts'; import { Session } from '../../models/session.ts'; import { AuthService } from '../../services/auth.service.ts'; export class UserHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); private authService = new AuthService(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } /** * Helper to format user to IUser interface */ private formatUser(user: User): interfaces.data.IUser { return { id: user.id, email: user.email, username: user.username, displayName: user.displayName, avatarUrl: user.avatarUrl, isSystemAdmin: user.isSystemAdmin, isActive: user.isActive, createdAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : String(user.createdAt), lastLoginAt: user.lastLoginAt instanceof Date ? user.lastLoginAt.toISOString() : user.lastLoginAt ? String(user.lastLoginAt) : undefined, }; } private registerHandlers(): void { // Get Users (admin only) this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getUsers', async (dataArg) => { await requireAdminIdentity(this.opsServerRef.authHandler, dataArg); try { const users = await User.getInstances({}); return { users: users.map((u) => this.formatUser(u)), }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to list users'); } }, ), ); // Get User this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getUser', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const { userId } = dataArg; // Users can view their own profile, admins can view any if (userId !== dataArg.identity.userId && !dataArg.identity.isSystemAdmin) { throw new plugins.typedrequest.TypedResponseError('Access denied'); } const user = await User.findById(userId); if (!user) { throw new plugins.typedrequest.TypedResponseError('User not found'); } return { user: this.formatUser(user) }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to get user'); } }, ), ); // Update User this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateUser', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const { userId, displayName, avatarUrl, password, isActive, isSystemAdmin } = dataArg; // Users can update their own profile, admins can update any if (userId !== dataArg.identity.userId && !dataArg.identity.isSystemAdmin) { throw new plugins.typedrequest.TypedResponseError('Access denied'); } const user = await User.findById(userId); if (!user) { throw new plugins.typedrequest.TypedResponseError('User not found'); } if (displayName !== undefined) user.displayName = displayName; if (avatarUrl !== undefined) user.avatarUrl = avatarUrl; // Only admins can change these if (dataArg.identity.isSystemAdmin) { if (isActive !== undefined) user.status = isActive ? 'active' : 'suspended'; if (isSystemAdmin !== undefined) user.isPlatformAdmin = isSystemAdmin; } // Password change if (password) { user.passwordHash = await AuthService.hashPassword(password); } await user.save(); return { user: this.formatUser(user) }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to update user'); } }, ), ); // Get User Sessions this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getUserSessions', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const sessions = await Session.getUserSessions(dataArg.identity.userId); return { sessions: sessions.map((s) => ({ id: s.id, userId: s.userId, userAgent: s.userAgent, ipAddress: s.ipAddress, isValid: s.isValid, lastActivityAt: s.lastActivityAt instanceof Date ? s.lastActivityAt.toISOString() : String(s.lastActivityAt), createdAt: s.createdAt instanceof Date ? s.createdAt.toISOString() : String(s.createdAt), })), }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to get sessions'); } }, ), ); // Revoke Session this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'revokeSession', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { await this.authService.logout(dataArg.sessionId, { userId: dataArg.identity.userId, }); return { message: 'Session revoked successfully' }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to revoke session'); } }, ), ); // Change Password this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'changePassword', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const { currentPassword, newPassword } = dataArg; if (!currentPassword || !newPassword) { throw new plugins.typedrequest.TypedResponseError( 'Current password and new password are required', ); } const user = await User.findById(dataArg.identity.userId); if (!user) { throw new plugins.typedrequest.TypedResponseError('User not found'); } // Verify current password const isValid = await user.verifyPassword(currentPassword); if (!isValid) { throw new plugins.typedrequest.TypedResponseError('Current password is incorrect'); } // Hash and set new password user.passwordHash = await AuthService.hashPassword(newPassword); await user.save(); return { message: 'Password changed successfully' }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to change password'); } }, ), ); // Delete Account this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteAccount', async (dataArg) => { await requireValidIdentity(this.opsServerRef.authHandler, dataArg); try { const { password } = dataArg; if (!password) { throw new plugins.typedrequest.TypedResponseError( 'Password is required to delete account', ); } const user = await User.findById(dataArg.identity.userId); if (!user) { throw new plugins.typedrequest.TypedResponseError('User not found'); } // Verify password const isValid = await user.verifyPassword(password); if (!isValid) { throw new plugins.typedrequest.TypedResponseError('Password is incorrect'); } // Soft delete - deactivate instead of removing user.status = 'suspended'; await user.save(); // Invalidate all sessions await Session.invalidateAllUserSessions(user.id, 'account_deleted'); return { message: 'Account deactivated successfully' }; } catch (error) { if (error instanceof plugins.typedrequest.TypedResponseError) throw error; throw new plugins.typedrequest.TypedResponseError('Failed to delete account'); } }, ), ); } }