createUser endpoint

This commit is contained in:
Alex Holliday committed 2026-02-09 18:49:31 +00:00
1 parent 264e6f7d1f
commit 8b7fd826b3
6 files changed
+93 -4

No files matched your search

@@ -17,7 +17,11 @@ interface AddTeamMemberDialogProps {
}
interface RegisterPayload {
user: Omit<AddTeamMemberFormData, "confirm">;
firstName: string;
lastName: string;
email: string;
password: string;
role: string[];
}
export const AddTeamMemberDialog = ({
@@ -50,9 +54,9 @@ export const AddTeamMemberDialog = ({
if (loading) return;
const { confirm, ...userData } = data;
const payload: RegisterPayload = { user: userData };
const payload: RegisterPayload = userData;
const result = await post("/auth/register", payload);
const result = await post("/auth/users", payload);
if (result?.success) {
reset();
+24
View File
@@ -1,10 +1,13 @@
import { Request, Response, NextFunction } from "express";
import { AppError } from "@/utils/AppError.js";
import { requireTeamId, requireUserRoles } from "./controllerUtils.js";
import type { UserRole } from "@/types/user.js";
import {
registrationBodyValidation,
loginValidation,
editUserBodyValidation,
createUserBodyValidation,
recoveryValidation,
recoveryTokenBodyValidation,
newPasswordValidation,
@@ -50,6 +53,27 @@ class AuthController {
}
};
createUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const userData = req.body;
if (userData?.email) {
userData.email = userData.email.toLowerCase();
}
await createUserBodyValidation.validateAsync(userData);
const teamId = requireTeamId(req.user?.teamId);
const actorRoles = requireUserRoles(req.user?.role) as UserRole[];
const newUser = await this.userService.createUser(userData, teamId, actorRoles, req.file);
res.status(201).json({
success: true,
msg: "User created successfully",
data: newUser,
});
} catch (error) {
next(error);
}
};
loginUser = async (req: Request, res: Response, next: NextFunction) => {
try {
if (req.body?.email) {
@@ -15,7 +15,6 @@ class InviteController {
}
getInviteToken = async (req: Request, res: Response, next: NextFunction) => {
console.log(req.body);
try {
const teamId = requireTeamId(req.user?.teamId);
const userRoles = requireUserRoles(req.user?.role);
+1
View File
@@ -25,6 +25,7 @@ class AuthRoutes {
this.router.get("/users/superadmin", this.authController.checkSuperadminExists);
this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers);
this.router.post("/users", verifyJWT, isAllowed(["superadmin"]), upload.single("profileImage"), this.authController.createUser);
this.router.get("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.getUserById);
this.router.patch("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById);
this.router.patch("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById);
@@ -7,6 +7,7 @@ import {
ITeamsRepository,
} from "@/repositories/index.js";
import type { User } from "@/types/index.js";
import { canManageRole, type UserRole } from "@/types/user.js";
import bcrypt from "bcryptjs";
import { AppError } from "@/utils/AppError.js";
import { ISuperSimpleQueue } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
@@ -153,6 +154,43 @@ class UserService {
return { user: newUser, token };
};
createUser = async (userData: Partial<User>, teamId: string, actorRoles: UserRole[], file: any) => {
// Validate that the creator can assign the requested roles
const targetRoles = userData.role ?? [];
for (const targetRole of targetRoles) {
const canManage = actorRoles.some((actorRole) => canManageRole(actorRole, targetRole));
if (!canManage) {
throw new AppError({
message: "You do not have permission to assign this role",
service: SERVICE_NAME,
method: "createUser",
status: 403,
});
}
}
userData.teamId = teamId;
if (userData.password) {
userData.password = this.hashPassword(userData.password);
}
const newUser = await this.usersRepository.create(userData, file);
this.logger.debug({
message: "New user created by superadmin",
service: SERVICE_NAME,
method: "createUser",
details: newUser.id,
});
newUser.profileImage = undefined;
newUser.avatarImage = undefined;
newUser.password = "";
return newUser;
};
loginUser = async (email: string, password: string) => {
// Check if user exists
const user = await this.usersRepository.findByEmail(email);
+23
View File
@@ -712,6 +712,28 @@ const editUserPasswordByIdBodyValidation = joi.object({
password: joi.string().min(8).required().pattern(passwordPattern),
});
const createUserBodyValidation = joi.object({
firstName: nameValidation.required(),
lastName: nameValidation.required(),
email: joi
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message({ custom: "Email must be in lowercase" });
}
return lowercasedValue;
}),
password: joi.string().min(8).required().pattern(passwordPattern),
role: joi
.array()
.items(joi.string().valid(...UserRoles))
.min(1)
.required(),
});
export {
roleValidatior,
loginValidation,
@@ -781,4 +803,5 @@ export {
editUserByIdBodyValidation,
editSuperadminUserByIdBodyValidation,
editUserPasswordByIdBodyValidation,
createUserBodyValidation,
};