From 38014f7138099befb7a5098a9a14a24525013735 Mon Sep 17 00:00:00 2001 From: Akhil Meka Date: Wed, 23 Apr 2025 16:29:33 +0530 Subject: [PATCH] updated roles, only superAdmin can create an admin --- src/user/user.controller.ts | 19 ++-- src/user/user.schema.ts | 27 ++--- src/user/user.service.ts | 34 +++--- src/utils/errors.ts | 12 +-- src/utils/roles.ts | 200 +++++++++++++++--------------------- 5 files changed, 132 insertions(+), 160 deletions(-) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 76e6d93..a05c352 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,12 +1,13 @@ -import { FastifyReply, FastifyRequest } from "fastify"; +import { FastifyReply, FastifyRequest } from 'fastify'; import { createUser, deleteUser, + ErrOpNotValid, getUser, listUsers, updateUser, -} from "./user.service"; -import { CreateUserInput, UpdateUserInput } from "./user.schema"; +} from './user.service'; +import { CreateUserInput, UpdateUserInput } from './user.schema'; export async function createUserHandler( req: FastifyRequest, @@ -18,6 +19,8 @@ export async function createUserHandler( const user = await createUser(body, req.user); return res.code(201).send(user); } catch (err) { + if (err instanceof Error && err.message == ErrOpNotValid.message) + return res.code(400).send(err.message); return err; } } @@ -26,14 +29,14 @@ export async function getCurrentUserHandler( req: FastifyRequest, res: FastifyReply ) { - if (req.user.type !== "user") { + if (req.user.type !== 'user') { return res.code(400).send(); } try { const user = await getUser(req.user.userId); if (user == null) - return res.code(404).send({ error: "resource not found" }); + return res.code(404).send({ error: 'resource not found' }); return res.code(200).send(user); } catch (err) { @@ -47,7 +50,7 @@ export async function getUserHandler(req: FastifyRequest, res: FastifyReply) { try { const user = await getUser(userId); if (user == null) - return res.code(404).send({ error: "resource not found" }); + return res.code(404).send({ error: 'resource not found' }); return res.code(200).send(user); } catch (err) { @@ -73,7 +76,7 @@ export async function updateUserHandler( try { const updatedUser = await updateUser(userId, input); - if (!updateUser) return res.code(404).send({ error: "resource not found" }); + if (!updateUser) return res.code(404).send({ error: 'resource not found' }); return res.code(200).send(updatedUser); } catch (err) { @@ -90,7 +93,7 @@ export async function deleteUserHandler( try { const deleteResult = await deleteUser(userId, req.user.tenantId); if (deleteResult.deletedCount == 0) - return res.code(404).send({ error: "resource not found" }); + return res.code(404).send({ error: 'resource not found' }); return res.code(204).send(); } catch (err) { diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts index 29dffee..920dc99 100644 --- a/src/user/user.schema.ts +++ b/src/user/user.schema.ts @@ -1,7 +1,7 @@ -import { buildJsonSchemas } from "fastify-zod"; -import mongoose, { InferSchemaType } from "mongoose"; -import { z } from "zod"; -import { roles } from "../utils/roles"; +import { buildJsonSchemas } from 'fastify-zod'; +import mongoose, { InferSchemaType } from 'mongoose'; +import { z } from 'zod'; +import { roles } from '../utils/roles'; const userSchema = new mongoose.Schema({ tenantId: { @@ -30,7 +30,7 @@ const userSchema = new mongoose.Schema({ }, defaultClient: { type: mongoose.Types.ObjectId, - ref: "organization", + ref: 'organization', }, passKeys: [new mongoose.Schema({}, { _id: false, strict: false })], challenge: new mongoose.Schema( @@ -50,9 +50,10 @@ const userSchema = new mongoose.Schema({ createdAt: Date, createdBy: mongoose.Types.ObjectId, lastLogin: Date, + dev: Boolean, }); -export const userModel = mongoose.model("user", userSchema); +export const userModel = mongoose.model('user', userSchema); export type User = InferSchemaType; @@ -61,8 +62,8 @@ const userCore = { lastName: z.string().max(30), email: z .string({ - required_error: "Email is required", - invalid_type_error: "Email must be a valid string", + required_error: 'Email is required', + invalid_type_error: 'Email must be a valid string', }) .email(), avatar: z.string().optional(), @@ -75,9 +76,9 @@ const createUserInput = z ...userCore, }) .superRefine((data, ctx) => { - if (data.role == "builder" && !data.orgId) { + if (data.role == 'builder' && !data.orgId) { ctx.addIssue({ - path: ["orgId"], + path: ['orgId'], message: 'orgId is required when role is "builder"', code: z.ZodIssueCode.custom, }); @@ -89,8 +90,8 @@ const updateUserInput = z.object({ lastName: z.string().max(30).optional(), email: z .string({ - required_error: "Email is required", - invalid_type_error: "Email must be a valid string", + required_error: 'Email is required', + invalid_type_error: 'Email must be a valid string', }) .email() .optional(), @@ -124,5 +125,5 @@ export const { schemas: userSchemas, $ref: $user } = buildJsonSchemas( updateUserInput, userResponse, }, - { $id: "user" } + { $id: 'user' } ); diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 2eaa608..d2287a4 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,37 +1,43 @@ -import mongoose from "mongoose"; -import { generateId, generateToken } from "../utils/id"; -import { CreateUserInput, UpdateUserInput, userModel } from "./user.schema"; -import { sendMail } from "../utils/mail"; -import { AuthenticatedUser } from "../auth"; +import mongoose from 'mongoose'; +import { generateId, generateToken } from '../utils/id'; +import { CreateUserInput, UpdateUserInput, userModel } from './user.schema'; +import { sendMail } from '../utils/mail'; +import { AuthenticatedUser } from '../auth'; + +export const ErrOpNotValid = new Error('operation is not valid'); export async function createUser( input: CreateUserInput, user: AuthenticatedUser ) { + if (input.role == 'admin' && user.role != 'superAdmin') { + throw ErrOpNotValid; + } + const token = await generateToken(); const newUser = await userModel.create({ tenantId: user.tenantId, pid: generateId(), - name: input.firstName + " " + input.lastName, + name: input.firstName + ' ' + input.lastName, createdAt: new Date(), createdBy: user.userId, token: { value: token, expiry: new Date(Date.now() + 3600 * 48 * 1000), }, - status: "invited", + status: 'invited', ...input, }); const sent = await sendMail( input.email, - "You have been invited to Quicker Permtis.", + 'You have been invited to Quicker Permtis.', `Click here to register.` ); @@ -50,7 +56,7 @@ export async function getUser(userId: string) { } export async function getUserByToken(token: string) { - return await userModel.findOne({ "token.value": token }); + return await userModel.findOne({ 'token.value': token }); } export async function getUserByEmail(email: string) { @@ -59,9 +65,9 @@ export async function getUserByEmail(email: string) { export async function listUsers(tenantId: string) { return await userModel - .find({ $and: [{ tenantId: tenantId }, { role: { $ne: "tester" } }] }) + .find({ $and: [{ tenantId: tenantId }, { dev: false }] }) .select( - "_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin" + '_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin' ); } @@ -71,7 +77,7 @@ export async function updateUser(userId: string, input: UpdateUserInput) { new: true, }) .select( - "_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin" + '_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin' ); } diff --git a/src/utils/errors.ts b/src/utils/errors.ts index ee00885..a381cfd 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,5 +1,5 @@ -import { FastifyReply, FastifyRequest } from "fastify"; -import mongoose from "mongoose"; +import { FastifyReply, FastifyRequest } from 'fastify'; +import mongoose from 'mongoose'; export function errorHandler( error: any, @@ -12,7 +12,7 @@ export function errorHandler( if (error.validation) { const errMsg = { - type: "validation_error", + type: 'validation_error', path: error.validation[0].instancePath, context: error.validationContext, msg: error.validation[0].message, @@ -25,9 +25,9 @@ export function errorHandler( if (error instanceof mongoose.mongo.MongoServerError) { if (error.code === 11000) { return res.code(400).send({ - type: "duplicate_key", - context: "body", - msg: "value already exists", + type: 'duplicate_key', + context: 'body', + msg: 'value already exists', params: error.keyValue, }); } diff --git a/src/utils/roles.ts b/src/utils/roles.ts index 8e8627f..915c37b 100644 --- a/src/utils/roles.ts +++ b/src/utils/roles.ts @@ -1,141 +1,103 @@ -import { Claim } from "./claims"; +import { Claim } from './claims'; export const rules: Record< string, { claims: Claim[]; hiddenFields: Record> } > = { - tester: { + superAdmin: { claims: [ - "user:read", - "user:write", - "org:read", - "org:write", - "org:delete", - "permit:read", - "permit:write", - "permit:delete", - "file:upload", - "file:download", - "file:delete", - "rts:read", - "rts:write", - "rts:delete", - "task:read", - "task:write", - "task:delete", - "notification:read", - "notification:write", - "notification:delete", - "config:read", - "config:write", - "mail:all", - "view:read", - "view:write", - "view:delete", - "token:read", - "token:write", - "token:delete", + 'user:read', + 'user:write', + 'user:delete', + 'org:read', + 'org:write', + 'org:delete', + 'permit:read', + 'permit:write', + 'permit:delete', + 'file:upload', + 'file:download', + 'file:delete', + 'rts:read', + 'rts:write', + 'rts:delete', + 'task:read', + 'task:write', + 'task:delete', + 'notification:read', + 'notification:write', + 'notification:delete', + 'config:read', + 'config:write', + 'mail:all', + 'view:read', + 'view:write', + 'view:delete', ], hiddenFields: { - orgs: ["__v"], - permits: ["__v"], - rts: ["__v"], - tasks: ["__v"], - users: ["__v"], + orgs: ['__v'], + permits: ['__v'], + rts: ['__v'], + tasks: ['__v'], + users: ['__v'], }, }, admin: { claims: [ - "user:read", - "user:write", - "org:read", - "org:write", - "org:delete", - "permit:read", - "permit:write", - "permit:delete", - "file:upload", - "file:download", - "file:delete", - "rts:read", - "rts:write", - "rts:delete", - "task:read", - "task:write", - "task:delete", - "notification:read", - "notification:write", - "notification:delete", - "config:read", - "config:write", - "mail:all", - "view:read", - "view:write", - "view:delete", + 'user:read', + 'user:write', + 'org:read', + 'permit:read', + 'file:upload', + 'file:download', + 'file:delete', + 'rts:read', + 'rts:write', + 'rts:delete', + 'task:read', + 'task:write', + 'task:delete', + 'notification:read', + 'notification:delete', + 'config:read', + 'config:write', + 'mail:all', + 'view:read', + 'view:write', + 'view:delete', ], hiddenFields: { - orgs: ["__v"], - permits: ["__v"], - rts: ["__v"], - tasks: ["__v"], - users: ["__v"], + orgs: ['__v', 'isClient', 'name'], + permits: ['__v'], + rts: ['__v'], + tasks: ['__v'], + users: ['__v'], }, }, - builder: { + team: { claims: [ - "permit:read", - "file:upload", - "file:download", - "org:read", - "config:read", + 'org:read', + 'permit:read', + 'file:upload', + 'file:download', + 'rts:read', + 'rts:write', + 'task:read', + 'task:write', + 'notification:read', + 'notification:delete', + 'config:read', + 'mail:all', + 'view:read', + 'view:write', + 'view:delete', ], hiddenFields: { - orgs: ["__v", "isClient", "name"], - permits: ["__v"], - rts: ["__v"], - tasks: ["__v"], - users: ["__v"], - }, - }, - staff: { - claims: [ - "org:read", - "org:write", - "org:delete", - "permit:read", - "permit:write", - "permit:delete", - "file:upload", - "file:download", - "file:delete", - ], - hiddenFields: { - orgs: [], - permits: [], - rts: [], - tasks: [], - users: [], - }, - }, - supervisor: { - claims: [ - "user:read", - "org:read", - "org:write", - "org:delete", - "permit:read", - "permit:write", - "permit:delete", - "file:upload", - "file:download", - "file:delete", - ], - hiddenFields: { - orgs: [], - permits: [], - rts: [], - tasks: [], - users: [], + orgs: ['__v', 'isClient', 'name'], + permits: ['__v'], + rts: ['__v'], + tasks: ['__v'], + users: ['__v'], }, }, };