// src/services/user.ts // Copyright (C) 2026 DTP Technologies, LLC // All Rights Reserved import crypto from "crypto"; import { Types } from "mongoose"; import { DtpService } from "../lib/service.js"; import User, { IUser } from "../models/user.js"; import env from "../config/env.js"; export class UserService extends DtpService { get name(): string { return "UserService"; } get slug(): string { return "user"; } constructor() { super(); } async start(): Promise { this.log.info("service started"); } async stop(): Promise { this.log.info("service stopped"); } /** * Create a new user account */ async create( username: string, password: string, displayName: string, isAdmin: boolean = false, ): Promise { // Validate input if (!username || !password || !displayName) { throw new Error("Username, password, and display name are required"); } if (username.length < 3 || username.length > 12) { throw new Error("Username must be between 3 and 12 characters"); } if (password.length < 8) { throw new Error("Password must be at least 8 characters"); } if (displayName.length < 3 || displayName.length > 30) { throw new Error("Display name must be between 3 and 30 characters"); } // Check if user already exists const existingUser = await User.findOne({ username_lc: username.toLowerCase(), }); if (existingUser) { throw new Error("Username already taken"); } // Hash password with salt const passwordSalt = env.user.passwordSalt; const passwordHash = crypto .pbkdf2Sync(password, passwordSalt, 100000, 64, "sha512") .toString("hex"); // Create user const user = new User(); user.username = username; user.username_lc = username.toLowerCase(); user.password = passwordHash; user.passwordSalt = passwordSalt; user.displayName = displayName; user.flags = { isAdmin, isTest: false, isBanned: false, }; user.connections = { gab: { social: { apiToken: "" }, ai: { apiToken: "" }, }, ai: { providerIds: [], agentProviderId: null, agentModel: "", vectorProviderId: null, vectorModel: "", utilityProviderId: null, utilityModel: "", }, }; await user.save(); this.log.info("User created", { userId: user._id, username: user.username, }); return user; } /** * Get user by ID */ async getUserById(_id: Types.ObjectId): Promise { const user = await User.findById(_id); return user; } /** * Get user by username (case-insensitive) */ async getUserByUsername(username: string): Promise { const user = await User.findOne({ username_lc: username.toLowerCase(), }); return user; } /** * Get user by ID with sensitive fields (for auth purposes) */ async getUserByIdWithCredentials(_id: Types.ObjectId): Promise { const user = await User.findById(_id).select("+password +passwordSalt"); return user; } /** * Get user by username with sensitive fields (for auth purposes) */ async getUserByUsernameWithCredentials( username: string, ): Promise { const user = await User.findOne({ username_lc: username.toLowerCase(), }).select("+password +passwordSalt"); return user; } /** * Verify user password */ async verifyPassword(user: IUser, password: string): Promise { if (!user.password || !user.passwordSalt) { return false; } const passwordHash = crypto .pbkdf2Sync(password, user.passwordSalt, 100000, 64, "sha512") .toString("hex"); return passwordHash === user.password; } /** * Check if user is banned */ isUserBanned(user: IUser): boolean { return user.flags.isBanned; } /** * Get public user data (safe to expose to client) */ getPublicUserData(user: IUser): { _id: string; username: string; displayName: string; flags: IUser["flags"]; } { return { _id: user._id.toString(), username: user.username, displayName: user.displayName, flags: user.flags, }; } } export default new UserService();