refactor: consolidate permission checking logic and update related components
This commit is contained in:
parent
31e2fe590a
commit
0c663753f6
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue