gadget/docs/archive/services/user.ts

189 lines
4.3 KiB
TypeScript

// 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<void> {
this.log.info("service started");
}
async stop(): Promise<void> {
this.log.info("service stopped");
}
/**
* Create a new user account
*/
async create(
username: string,
password: string,
displayName: string,
isAdmin: boolean = false,
): Promise<IUser> {
// 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<IUser | null> {
const user = await User.findById(_id);
return user;
}
/**
* Get user by username (case-insensitive)
*/
async getUserByUsername(username: string): Promise<IUser | null> {
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<IUser | null> {
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<IUser | null> {
const user = await User.findOne({
username_lc: username.toLowerCase(),
}).select("+password +passwordSalt");
return user;
}
/**
* Verify user password
*/
async verifyPassword(user: IUser, password: string): Promise<boolean> {
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();