refactor: consolidate permission checking logic and update related components

This commit is contained in:
vergiLgood1 2025-04-12 13:56:49 +07:00
parent 31e2fe590a
commit 0c663753f6
14 changed files with 130 additions and 153 deletions

View File

@ -15,10 +15,10 @@ import { IUserSchema } from "@/src/entities/models/users/users.model"
import { useUserActionsHandler } from "../../../_handlers/actions/use-user-actions"
import { BanUserDialog } from "../../dialogs/ban-user-dialog"
import { useCreateUserColumn } from "../../../_handlers/use-create-user-column"
import { useCheckPermissionsHandler } from "@/app/(pages)/(auth)/_handlers/use-check-permissions"
import { useCheckPermissionsNewQuery } from "@/app/(pages)/(auth)/_queries/mutations"
import { useGetCurrentUserQuery } from "../../../_queries/queries"
import { Badge } from "@/app/_components/ui/badge"
import { useCheckPermissionsQuery } from "@/app/(pages)/(auth)/_queries/queries"
interface ActionsCellProps {
user: IUserSchema
@ -43,12 +43,12 @@ export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
setSelectedUser,
} = useCreateUserColumn()
const { data: currentUser, isPending } = useGetCurrentUserQuery()
const { data: currentUser } = useGetCurrentUserQuery()
if (!currentUser) return <Badge variant={"destructive"}>user not found</Badge>
let { data: isAllowedToDelete } = useCheckPermissionsNewQuery(currentUser.email, "delete", "users")
let { data: isAllowedToUpdate } = useCheckPermissionsNewQuery(currentUser.email, "update", "users")
let { data: isAllowedToDelete } = useCheckPermissionsQuery(currentUser.email, "delete", "users")
let { data: isAllowedToUpdate } = useCheckPermissionsQuery(currentUser.email, "update", "users")
return (
<div onClick={(e) => e.stopPropagation()}>
@ -76,7 +76,7 @@ export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
Delete
</DropdownMenuItem>
)}
{isAllowedToUpdate && user.banned_until != null && (
{isAllowedToUpdate && (
<DropdownMenuItem
onClick={(e) => {
if (user.banned_until != null) {

View File

@ -1,14 +0,0 @@
import { useCheckPermissionsMutation } from "../_queries/mutations"
export const useCheckPermissionsHandler = () => {
const { mutateAsync: checkPermissions, isPending } = useCheckPermissionsMutation()
const handleCheckPermissions = async (userId: string, action: string, resource: string) => {
return await checkPermissions({ userId, action, resource })
}
return {
checkPermissions: handleCheckPermissions,
isPending,
}
}

View File

@ -1,5 +1,5 @@
import { useMutation, useQueries, useQuery } from "@tanstack/react-query"
import { checkPermissionNew, checkPermissions, sendMagicLink, sendPasswordRecovery, signInPasswordless, signInWithPassword, signOut, verifyOtp } from "../action"
import { checkPermissions, sendMagicLink, sendPasswordRecovery, signInPasswordless, signInWithPassword, signOut, verifyOtp } from "../action"
export const useSignInPasswordlessMutation = () => {
return useMutation({
@ -42,17 +42,3 @@ export const useVerifyOtpMutation = () => {
mutationFn: async (formData: FormData) => await verifyOtp(formData),
})
}
export const useCheckPermissionsMutation = () => {
return useMutation({
mutationKey: ["check-permissions"],
mutationFn: async ({ userId, action, resource }: { userId: string; action: string; resource: string }) => await checkPermissions(userId, action, resource),
})
}
export const useCheckPermissionsNewQuery = (email: string, action: string, resource: string) => {
return useQuery({
queryKey: ["check-permissions", email, action, resource],
queryFn: async () => await checkPermissionNew(email, action, resource),
})
}

View File

@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query"
import { checkPermissions } from "../action"
export const useCheckPermissionsQuery = (email: string, action: string, resource: string) => {
return useQuery({
queryKey: ["check-permissions", email, action, resource],
queryFn: async () => await checkPermissions(email, action, resource),
})
}

View File

@ -229,82 +229,42 @@ export async function sendPasswordRecovery(email: string) {
})
}
export async function checkPermissions(userId: string, action: string, resource: string) {
const instrumentationService = getInjection("IInstrumentationService")
return await instrumentationService.instrumentServerAction("checkPermissions", {
recordResponse: true
}, async () => {
try {
const user = await db.users.findUnique({
where: { id: userId },
include: {
role: true
}
})
console.log("Checking permissions for user:", user?.role.name, "action:", action, "resource:", resource)
const permission = await db.permissions.findFirst({
where: {
role_id: user?.role.id,
action: action,
resource: {
name: resource
}
}
})
return !!permission
} catch (err) {
if (err instanceof InputParseError) {
return { error: err.message }
}
const crashReporterService = getInjection("ICrashReporterService")
crashReporterService.report(err)
return {
error: "An error occurred during permissions check. Please try again later.",
}
}
})
}
export async function checkPermissionNew(email: string, action: string, resource: string) {
export async function checkPermissions(email: string, action: string, resource: string) {
const instrumentationService = getInjection("IInstrumentationService")
return await instrumentationService.instrumentServerAction("checkPermissionNew", {
recordResponse: true
}, async () => {
try {
const user = await db.users.findUnique({
where: { email },
include: { role: true }
})
// const user = await db.users.findUnique({
// where: { email },
// include: { role: true }
// })
if (!user) {
return { error: "User not found" }
}
// if (!user) {
// return { error: "User not found" }
// }
console.log("Checking permissions for user:", user?.role.name, "action:", action, "resource:", resource)
// console.log("Checking permissions for user:", user?.role.name, "action:", action, "resource:", resource)
const permission = await db.permissions.findFirst({
where: {
role_id: user.role.id,
action: action,
resource: {
name: resource
}
}
})
// const permission = await db.permissions.findFirst({
// where: {
// role_id: user.role.id,
// action: action,
// resource: {
// name: resource
// }
// }
// })
if (!permission) {
return false
}
// if (!permission) {
// return false
// }
return !!permission
// return !!permission
const checkPermissionsController = getInjection("ICheckPermissionsController")
return await checkPermissionsController({ email, action, resource })
} catch (err) {
if (err instanceof InputParseError) {
@ -315,7 +275,7 @@ export async function checkPermissionNew(email: string, action: string, resource
crashReporterService.report(err)
return {
error: "An error occurred during permissions check. Please try again later.",
error: err instanceof Error ? err.message : "An unknown error occurred",
}
}
})

View File

@ -17,6 +17,8 @@ import { sendMagicLinkController } from '@/src/interface-adapters/controllers/au
import { sendPasswordRecoveryController } from '@/src/interface-adapters/controllers/auth/send-password-recovery.controller';
import { signInWithPasswordUseCase } from '@/src/application/use-cases/auth/sign-in-with-password.use-case';
import { signInWithPasswordController } from '@/src/interface-adapters/controllers/auth/sign-in-with-password.controller';
import { checkPermissionsUseCase } from '@/src/application/use-cases/auth/check-permissions.use-case';
import { checkPermissionsController } from '@/src/interface-adapters/controllers/auth/check-permissions.controller';
export function createAuthenticationModule() {
const authenticationModule = createModule();
@ -28,15 +30,19 @@ export function createAuthenticationModule() {
authenticationModule
.bind(DI_SYMBOLS.IAuthenticationService)
.toClass(AuthenticationService, [
DI_SYMBOLS.IUsersRepository,
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.ICrashReporterService,
DI_SYMBOLS.IPermissionsRepository,
DI_SYMBOLS.IUsersRepository,
]);
} else {
authenticationModule
.bind(DI_SYMBOLS.IAuthenticationService)
.toClass(AuthenticationService, [
DI_SYMBOLS.IUsersRepository,
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.ICrashReporterService,
DI_SYMBOLS.IPermissionsRepository,
DI_SYMBOLS.IUsersRepository,
]);
}
@ -98,7 +104,7 @@ export function createAuthenticationModule() {
authenticationModule
.bind(DI_SYMBOLS.ICheckPermissionsUseCase)
.toHigherOrderFunction(signUpUseCase, [
.toHigherOrderFunction(checkPermissionsUseCase, [
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.IAuthenticationService,
]);
@ -150,9 +156,9 @@ export function createAuthenticationModule() {
authenticationModule
.bind(DI_SYMBOLS.ICheckPermissionsController)
.toHigherOrderFunction(signUpUseCase, [
.toHigherOrderFunction(checkPermissionsController, [
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.IAuthenticationService,
DI_SYMBOLS.ICheckPermissionsUseCase,
DI_SYMBOLS.IUsersRepository,
]);

View File

@ -13,10 +13,27 @@ import { getPermissionByIdController } from '@/src/interface-adapters/controller
import { getPermissionByRoleController } from '@/src/interface-adapters/controllers/permissions/get-permission-by-role.controller';
import { updatePermissionController } from '@/src/interface-adapters/controllers/permissions/update-permission.controller';
import { deletePermissionController } from '@/src/interface-adapters/controllers/permissions/delete-permission.controller';
import { PermissionsRepository } from '@/src/infrastructure/repositories/permissions.repository';
export function createPermissionsModule() {
const permissionsModule = createModule();
if (process.env.NODE_ENV === 'test') {
permissionsModule
.bind(DI_SYMBOLS.IPermissionsRepository)
.toClass(PermissionsRepository, [
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.ICrashReporterService,
]);
} else {
permissionsModule
.bind(DI_SYMBOLS.IPermissionsRepository)
.toClass(PermissionsRepository, [
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.ICrashReporterService,
]);
}
// Use Cases
permissionsModule
.bind(DI_SYMBOLS.ICreatePermissionUseCase)

View File

@ -11,10 +11,23 @@ import { getResourceByIdController } from '@/src/interface-adapters/controllers/
import { getResourcesByTypeController } from '@/src/interface-adapters/controllers/resources/get-resources-by-type.controller';
import { updateResourceController } from '@/src/interface-adapters/controllers/resources/update-resource.controller';
import { deleteResourceController } from '@/src/interface-adapters/controllers/resources/delete-resource.controller';
import { ResourcesRepository } from '@/src/infrastructure/repositories/resources.repository';
export function createResourcesModule() {
const resourcesModule = createModule();
if (process.env.NODE_ENV === 'test') {
// resourcesModule
// .bind(DI_SYMBOLS.IResourcesRepository)
// .toClass(MockResourcesRepository, [DI_SYMBOLS.IInstrumentationService]);
} else {
resourcesModule
.bind(DI_SYMBOLS.IResourcesRepository)
.toClass(ResourcesRepository, [
DI_SYMBOLS.IInstrumentationService,
]);
}
// Use Cases
resourcesModule
.bind(DI_SYMBOLS.ICreateResourceUseCase)

View File

@ -9,10 +9,23 @@ import { createRoleController } from '@/src/interface-adapters/controllers/roles
import { getRoleByIdController } from '@/src/interface-adapters/controllers/roles/get-role-by-id.controller';
import { updateRoleController } from '@/src/interface-adapters/controllers/roles/update-role.controller';
import { deleteRoleController } from '@/src/interface-adapters/controllers/roles/delete-role.controller';
import { RolesRepository } from '@/src/infrastructure/repositories/roles.repository';
export function createRolesModule() {
const rolesModule = createModule();
if (process.env.NODE_ENV === 'test') {
// rolesModule
// .bind(DI_SYMBOLS.IRolesRepository)
// .toClass(MockRolesRepository, [DI_SYMBOLS.IInstrumentationService]);
} else {
rolesModule
.bind(DI_SYMBOLS.IRolesRepository)
.toClass(RolesRepository, [
DI_SYMBOLS.IInstrumentationService,
]);
}
// Use Cases
rolesModule
.bind(DI_SYMBOLS.ICreateRoleUseCase)

View File

@ -17,5 +17,5 @@ export interface IAuthenticationService {
sendMagicLink(credentials: ISendMagicLinkSchema): Promise<void>
sendPasswordRecovery(credentials: ISendPasswordRecoverySchema): Promise<void>
verifyOtp(credentials: IVerifyOtpSchema): Promise<void>
checkPermission(userId: string, action: string, resource: string): Promise<boolean>;
checkPermission(email: string, action: string, resource: string): Promise<boolean>;
}

View File

@ -1,21 +1,19 @@
import { IInstrumentationService } from "../../services/instrumentation.service.interface";
import { NotFoundError } from "@/src/entities/errors/common";
import { IAuthenticationService } from "../../services/authentication.service.interface";
import { IUsersRepository } from "../../repositories/users.repository.interface";
export type ICheckPermissionsUseCase = ReturnType<typeof checkPermissionsUseCase>;
export const checkPermissionsUseCase = (
instrumentationService: IInstrumentationService,
authenticationService: IAuthenticationService,
) => async (userId: string, action: string, resource: string): Promise<boolean> => {
) => async (email: string, action: string, resource: string): Promise<boolean> => {
return await instrumentationService.startSpan({ name: "Check Permission Use Case", op: "function" },
async () => {
const permission = await authenticationService.checkPermission(userId, action, resource);
const permission = await authenticationService.checkPermission(email, action, resource);
if (!permission) {
throw new NotFoundError("Permission not found");
return false;
}
return permission;

View File

@ -73,19 +73,22 @@ export class PermissionsRepository implements IPermissionsRepository {
async () => {
try {
const result = await db.permissions.findFirst({
const permission = await db.permissions.findFirst({
where: {
role: {
name: role
},
action: action,
resource: {
name: resource
role: { name: role },
action,
resource: { name: resource }
}
}
});
})
if (!permission) {
return false
}
console.log("Permission", permission)
return true
return !!result;
} catch (err) {
this.crashReporterService.report(err);
throw err;

View File

@ -17,10 +17,9 @@ import { IUserSchema } from "@/src/entities/models/users/users.model";
export class AuthenticationService implements IAuthenticationService {
constructor(
private readonly usersRepository: IUsersRepository,
private readonly instrumentationService: IInstrumentationService,
private readonly crashReporterService: ICrashReporterService,
private readonly permissionRepository: IPermissionsRepository,
private readonly permissionsRepository: IPermissionsRepository,
private readonly supabaseAdmin = createAdminClient(),
private readonly supabaseServer = createClient()
) { }
@ -277,35 +276,22 @@ export class AuthenticationService implements IAuthenticationService {
})
}
async checkPermission(userId: string, action: string, resource: string): Promise<boolean> {
async checkPermission(email: string, action: string, resource: string): Promise<boolean> {
return await this.instrumentationService.startSpan({
name: "",
name: "Check Permission Use Case",
}, async () => {
try {
const user = await db.users.findUnique({
where: { email },
include: { role: true }
});
// const user = await db.users.findUnique({
// where: { id: userId },
// include: { role: true }
// });
if (!user) {
return false;
}
// if (!user) {
// return false;
// }
// const role = user.role.name;
// const permission = await db.permissions.findFirst({
// where: {
// role: { name: role },
// action,
// resource: {
// name: resource
// }
// }
// })
return true
return await this.permissionsRepository.checkPermission(user.role.name, action, resource);
} catch (err) {
this.crashReporterService.report(err)

View File

@ -6,7 +6,7 @@ import { z } from "zod";
const checkPermissionInputSchema = z.object({
userId: z.string().uuid("Please enter a valid user ID"),
email: z.string().email("Please enter a valid email"),
action: z.string().nonempty("Please enter an action"),
resource: z.string().nonempty("Please enter a resource"),
})
@ -23,11 +23,11 @@ export const checkPermissionsController =
return await instrumentationService.startSpan({ name: "checkPermission Controller" },
async () => {
// const session = await usersRpository.getCurrentUser()
const session = await usersRpository.getCurrentUser()
// if (!session) {
// throw new InputParseError("User not found")
// }
if (!session) {
throw new InputParseError("User not found")
}
const { data, error: inputParseError } = checkPermissionInputSchema.safeParse(input)
@ -36,7 +36,7 @@ export const checkPermissionsController =
}
return await checkPermissionUseCase(
data.userId,
data.email,
data.action,
data.resource
)