add RBAC on applications
This commit is contained in:
parent
cca250b275
commit
31e2fe590a
|
@ -42,11 +42,13 @@ export function SettingsDialog({ trigger, defaultTab = "account", open, onOpenCh
|
||||||
|
|
||||||
const [selectedTab, setSelectedTab] = useState(defaultTab)
|
const [selectedTab, setSelectedTab] = useState(defaultTab)
|
||||||
|
|
||||||
|
if (!user || !user.profile) return <div className="text-red-500">User not found</div>
|
||||||
|
|
||||||
// Get user display name
|
// Get user display name
|
||||||
const preferredName = user?.profile?.username || ""
|
const preferredName = user.profile.username || ""
|
||||||
const userEmail = user?.email || ""
|
const userEmail = user.email || ""
|
||||||
const displayName = preferredName || userEmail?.split("@")[0] || "User"
|
const displayName = preferredName || userEmail.split("@")[0] || "User"
|
||||||
const userAvatar = user?.profile?.avatar || ""
|
const userAvatar = user.profile.avatar || ""
|
||||||
|
|
||||||
const sections: SettingsSection[] = [
|
const sections: SettingsSection[] = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// cells/actions-cell.tsx
|
// cells/actions-cell.tsx
|
||||||
|
|
||||||
import React, { useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { MoreHorizontal, PenIcon as UserPen, Trash2, ShieldAlert, ShieldCheck } from "lucide-react"
|
import { MoreHorizontal, PenIcon as UserPen, Trash2, ShieldAlert, ShieldCheck } from "lucide-react"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -15,7 +15,10 @@ import { IUserSchema } from "@/src/entities/models/users/users.model"
|
||||||
import { useUserActionsHandler } from "../../../_handlers/actions/use-user-actions"
|
import { useUserActionsHandler } from "../../../_handlers/actions/use-user-actions"
|
||||||
import { BanUserDialog } from "../../dialogs/ban-user-dialog"
|
import { BanUserDialog } from "../../dialogs/ban-user-dialog"
|
||||||
import { useCreateUserColumn } from "../../../_handlers/use-create-user-column"
|
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"
|
||||||
|
|
||||||
interface ActionsCellProps {
|
interface ActionsCellProps {
|
||||||
user: IUserSchema
|
user: IUserSchema
|
||||||
|
@ -23,7 +26,6 @@ interface ActionsCellProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
|
export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
deleteDialogOpen,
|
deleteDialogOpen,
|
||||||
setDeleteDialogOpen,
|
setDeleteDialogOpen,
|
||||||
|
@ -41,6 +43,13 @@ export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
|
||||||
setSelectedUser,
|
setSelectedUser,
|
||||||
} = useCreateUserColumn()
|
} = useCreateUserColumn()
|
||||||
|
|
||||||
|
const { data: currentUser, isPending } = 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")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
@ -50,19 +59,24 @@ export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
|
{isAllowedToUpdate && (
|
||||||
<DropdownMenuItem onClick={() => onUpdate(user)}>
|
<DropdownMenuItem onClick={() => onUpdate(user)}>
|
||||||
<UserPen className="h-4 w-4 mr-2 text-blue-500" />
|
<UserPen className="h-4 w-4 mr-2 text-blue-500" />
|
||||||
Update
|
Update
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
)}
|
||||||
onClick={(e) => {
|
{isAllowedToDelete && (
|
||||||
setSelectedUser({ id: user.id, email: user.email! })
|
<DropdownMenuItem
|
||||||
setDeleteDialogOpen(true)
|
onClick={(e) => {
|
||||||
}}
|
setSelectedUser({ id: user.id, email: user.email! })
|
||||||
>
|
setDeleteDialogOpen(true)
|
||||||
<Trash2 className="h-4 w-4 mr-2 text-destructive" />
|
}}
|
||||||
Delete
|
>
|
||||||
</DropdownMenuItem>
|
<Trash2 className="h-4 w-4 mr-2 text-destructive" />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{isAllowedToUpdate && user.banned_until != null && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (user.banned_until != null) {
|
if (user.banned_until != null) {
|
||||||
|
@ -77,6 +91,7 @@ export const ActionsCell: React.FC<ActionsCellProps> = ({ user, onUpdate }) => {
|
||||||
<ShieldAlert className="h-4 w-4 mr-2 text-yellow-500" />
|
<ShieldAlert className="h-4 w-4 mr-2 text-yellow-500" />
|
||||||
{user.banned_until != null ? "Unban" : "Ban"}
|
{user.banned_until != null ? "Unban" : "Ban"}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// columns/actions-column.tsx
|
// columns/actions-column.tsx
|
||||||
"use client"
|
|
||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { IUserSchema } from "@/src/entities/models/users/users.model"
|
import { IUserSchema } from "@/src/entities/models/users/users.model"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// columns/index.ts
|
// columns/index.ts
|
||||||
"use client"
|
|
||||||
|
|
||||||
import type { ColumnDef } from "@tanstack/react-table"
|
import type { ColumnDef } from "@tanstack/react-table"
|
||||||
import { IUserSchema, IUserFilterOptionsSchema } from "@/src/entities/models/users/users.model"
|
import { IUserSchema, IUserFilterOptionsSchema } from "@/src/entities/models/users/users.model"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// filters/column-filter.tsx
|
// filters/column-filter.tsx
|
||||||
"use client"
|
|
||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { ListFilter } from "lucide-react"
|
import { ListFilter } from "lucide-react"
|
||||||
|
|
|
@ -1,43 +1,17 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useMemo, useEffect } from "react";
|
import { useState, useMemo, useEffect } from "react";
|
||||||
import {
|
|
||||||
PlusCircle,
|
|
||||||
Search,
|
|
||||||
MoreHorizontal,
|
|
||||||
X,
|
|
||||||
ChevronDown,
|
|
||||||
UserPlus,
|
|
||||||
Mail,
|
|
||||||
ShieldAlert,
|
|
||||||
ListFilter,
|
|
||||||
Trash2,
|
|
||||||
PenIcon as UserPen,
|
|
||||||
ShieldCheck,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { Button } from "@/app/_components/ui/button";
|
|
||||||
import { Input } from "@/app/_components/ui/input";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/app/_components/ui/dropdown-menu";
|
|
||||||
|
|
||||||
import { DataTable } from "../../../../../_components/data-table";
|
import { DataTable } from "../../../../../_components/data-table";
|
||||||
import { UserInformationSheet } from "./sheets/user-information-sheet";
|
import { UserInformationSheet } from "./sheets/user-information-sheet";
|
||||||
import { useGetUsersQuery } from "../_queries/queries";
|
import { useGetUsersQuery } from "../_queries/queries";
|
||||||
import { filterUsers, useUserManagementHandlers } from "../_handlers/use-user-management";
|
import { filterUsers, useUserManagementHandlers } from "../_handlers/use-user-management";
|
||||||
import { UserDialogs } from "./dialogs/user-dialogs";
|
|
||||||
import { useAddUserDialogHandler } from "../_handlers/use-add-user-dialog";
|
import { useAddUserDialogHandler } from "../_handlers/use-add-user-dialog";
|
||||||
import { useInviteUserHandler } from "../_handlers/use-invite-user";
|
import { useInviteUserHandler } from "../_handlers/use-invite-user";
|
||||||
import { AddUserDialog } from "./dialogs/add-user-dialog";
|
import { AddUserDialog } from "./dialogs/add-user-dialog";
|
||||||
import { InviteUserDialog } from "./dialogs/invite-user-dialog";
|
import { InviteUserDialog } from "./dialogs/invite-user-dialog";
|
||||||
import { ConfirmDialog } from "@/app/_components/confirm-dialog";
|
|
||||||
import { BanUserDialog } from "./dialogs/ban-user-dialog";
|
|
||||||
import { useBanUserHandler } from "../_handlers/actions/use-ban-user";
|
|
||||||
import { useDeleteUserHandler } from "../_handlers/actions/use-delete-user";
|
|
||||||
import { useUnbanUserHandler } from "../_handlers/actions/use-unban-user";
|
|
||||||
import { createUserColumns } from "./table/columns";
|
import { createUserColumns } from "./table/columns";
|
||||||
import { UserManagementToolbar } from "./toolbars/user-management-toolbar";
|
import { UserManagementToolbar } from "./toolbars/user-management-toolbar";
|
||||||
import { UpdateUserSheet } from "./sheets/update-user-sheet";
|
import { UpdateUserSheet } from "./sheets/update-user-sheet";
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const useCheckPermissionsHandler = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleCheckPermissions,
|
checkPermissions: handleCheckPermissions,
|
||||||
isPending,
|
isPending,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,51 +1,58 @@
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation, useQueries, useQuery } from "@tanstack/react-query"
|
||||||
import { checkPermissions, sendMagicLink, sendPasswordRecovery, signInPasswordless, signInWithPassword, signOut, verifyOtp } from "../action"
|
import { checkPermissionNew, checkPermissions, sendMagicLink, sendPasswordRecovery, signInPasswordless, signInWithPassword, signOut, verifyOtp } from "../action"
|
||||||
|
|
||||||
export const useSignInPasswordlessMutation = () => {
|
export const useSignInPasswordlessMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["signIn"],
|
mutationKey: ["signin"],
|
||||||
mutationFn: async (formData: FormData) => await signInPasswordless(formData),
|
mutationFn: async (formData: FormData) => await signInPasswordless(formData),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSignInWithPasswordMutation = () => {
|
export const useSignInWithPasswordMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["signInWithCredentials"],
|
mutationKey: ["signin", "credentials"],
|
||||||
mutationFn: async (formData: FormData) => await signInWithPassword(formData),
|
mutationFn: async (formData: FormData) => await signInWithPassword(formData),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSignOutMutation = () => {
|
export const useSignOutMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["signOut"],
|
mutationKey: ["signout"],
|
||||||
mutationFn: async () => await signOut(),
|
mutationFn: async () => await signOut(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSendMagicLinkMutation = () => {
|
export const useSendMagicLinkMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["sendMagicLink"],
|
mutationKey: ["send-magic-link"],
|
||||||
mutationFn: async (email: string) => await sendMagicLink(email),
|
mutationFn: async (email: string) => await sendMagicLink(email),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSendPasswordRecoveryMutation = () => {
|
export const useSendPasswordRecoveryMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["sendPasswordRecovery"],
|
mutationKey: ["send-password-recovery"],
|
||||||
mutationFn: async (email: string) => await sendPasswordRecovery(email),
|
mutationFn: async (email: string) => await sendPasswordRecovery(email),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useVerifyOtpMutation = () => {
|
export const useVerifyOtpMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["verifyOtp"],
|
mutationKey: ["verify-otp"],
|
||||||
mutationFn: async (formData: FormData) => await verifyOtp(formData),
|
mutationFn: async (formData: FormData) => await verifyOtp(formData),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCheckPermissionsMutation = () => {
|
export const useCheckPermissionsMutation = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["checkPermissions"],
|
mutationKey: ["check-permissions"],
|
||||||
mutationFn: async ({ userId, action, resource }: { userId: string; action: string; resource: string }) => await checkPermissions(userId, action, resource),
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { revalidatePath } from "next/cache"
|
||||||
import { InputParseError, NotFoundError } from "@/src/entities/errors/common"
|
import { InputParseError, NotFoundError } from "@/src/entities/errors/common"
|
||||||
import { AuthenticationError, UnauthenticatedError } from "@/src/entities/errors/auth"
|
import { AuthenticationError, UnauthenticatedError } from "@/src/entities/errors/auth"
|
||||||
import { createClient } from "@/app/_utils/supabase/server"
|
import { createClient } from "@/app/_utils/supabase/server"
|
||||||
|
import db from "@/prisma/db";
|
||||||
|
|
||||||
export async function signInPasswordless(formData: FormData) {
|
export async function signInPasswordless(formData: FormData) {
|
||||||
const instrumentationService = getInjection("IInstrumentationService")
|
const instrumentationService = getInjection("IInstrumentationService")
|
||||||
|
@ -234,8 +235,77 @@ export async function checkPermissions(userId: string, action: string, resource:
|
||||||
recordResponse: true
|
recordResponse: true
|
||||||
}, async () => {
|
}, async () => {
|
||||||
try {
|
try {
|
||||||
const checkPermissionsController = getInjection("ICheckPermissionsController")
|
|
||||||
return await checkPermissionsController({ userId, action, resource })
|
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) {
|
||||||
|
const instrumentationService = getInjection("IInstrumentationService")
|
||||||
|
return await instrumentationService.instrumentServerAction("checkPermissionNew", {
|
||||||
|
recordResponse: true
|
||||||
|
}, async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const user = await db.users.findUnique({
|
||||||
|
where: { email },
|
||||||
|
include: { role: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return { error: "User not found" }
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!permission) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!permission
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof InputParseError) {
|
if (err instanceof InputParseError) {
|
||||||
return { error: err.message }
|
return { error: err.message }
|
||||||
|
|
|
@ -96,6 +96,13 @@ export function createAuthenticationModule() {
|
||||||
DI_SYMBOLS.IUsersRepository,
|
DI_SYMBOLS.IUsersRepository,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
authenticationModule
|
||||||
|
.bind(DI_SYMBOLS.ICheckPermissionsUseCase)
|
||||||
|
.toHigherOrderFunction(signUpUseCase, [
|
||||||
|
DI_SYMBOLS.IInstrumentationService,
|
||||||
|
DI_SYMBOLS.IAuthenticationService,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
// Controllers
|
// Controllers
|
||||||
authenticationModule
|
authenticationModule
|
||||||
|
@ -141,6 +148,15 @@ export function createAuthenticationModule() {
|
||||||
DI_SYMBOLS.ISendPasswordRecoveryUseCase,
|
DI_SYMBOLS.ISendPasswordRecoveryUseCase,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
authenticationModule
|
||||||
|
.bind(DI_SYMBOLS.ICheckPermissionsController)
|
||||||
|
.toHigherOrderFunction(signUpUseCase, [
|
||||||
|
DI_SYMBOLS.IInstrumentationService,
|
||||||
|
DI_SYMBOLS.IAuthenticationService,
|
||||||
|
DI_SYMBOLS.IUsersRepository,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return authenticationModule;
|
return authenticationModule;
|
||||||
}
|
}
|
|
@ -77,6 +77,7 @@ import { IDeleteResourceController } from '@/src/interface-adapters/controllers/
|
||||||
import { IUpdateResourceController } from '@/src/interface-adapters/controllers/resources/update-resource.controller';
|
import { IUpdateResourceController } from '@/src/interface-adapters/controllers/resources/update-resource.controller';
|
||||||
import { ICreateResourceController } from '@/src/interface-adapters/controllers/resources/create-resource.controller';
|
import { ICreateResourceController } from '@/src/interface-adapters/controllers/resources/create-resource.controller';
|
||||||
import { ICheckPermissionsUseCase } from '@/src/application/use-cases/auth/check-permissions.use-case';
|
import { ICheckPermissionsUseCase } from '@/src/application/use-cases/auth/check-permissions.use-case';
|
||||||
|
import { ISignInWithPasswordUseCase } from '@/src/application/use-cases/auth/sign-in-with-password.use-case';
|
||||||
|
|
||||||
// Pastikan DI_SYMBOLS memiliki tipe yang sesuai dengan DI_RETURN_TYPES
|
// Pastikan DI_SYMBOLS memiliki tipe yang sesuai dengan DI_RETURN_TYPES
|
||||||
export const DI_SYMBOLS: { [K in keyof DI_RETURN_TYPES]: symbol } = {
|
export const DI_SYMBOLS: { [K in keyof DI_RETURN_TYPES]: symbol } = {
|
||||||
|
@ -96,6 +97,7 @@ export const DI_SYMBOLS: { [K in keyof DI_RETURN_TYPES]: symbol } = {
|
||||||
|
|
||||||
// Auth Use Cases
|
// Auth Use Cases
|
||||||
ISignInPasswordlessUseCase: Symbol.for('ISignInPasswordlessUseCase'),
|
ISignInPasswordlessUseCase: Symbol.for('ISignInPasswordlessUseCase'),
|
||||||
|
ISignInWithPasswordUseCase: Symbol.for('ISignInWithPasswordUseCase'),
|
||||||
ISignUpUseCase: Symbol.for('ISignUpUseCase'),
|
ISignUpUseCase: Symbol.for('ISignUpUseCase'),
|
||||||
IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'),
|
IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'),
|
||||||
ISignOutUseCase: Symbol.for('ISignOutUseCase'),
|
ISignOutUseCase: Symbol.for('ISignOutUseCase'),
|
||||||
|
@ -204,6 +206,7 @@ export interface DI_RETURN_TYPES {
|
||||||
|
|
||||||
// Auth Use Cases
|
// Auth Use Cases
|
||||||
ISignInPasswordlessUseCase: ISignInPasswordlessUseCase;
|
ISignInPasswordlessUseCase: ISignInPasswordlessUseCase;
|
||||||
|
ISignInWithPasswordUseCase: ISignInWithPasswordUseCase;
|
||||||
ISignUpUseCase: ISignUpUseCase;
|
ISignUpUseCase: ISignUpUseCase;
|
||||||
IVerifyOtpUseCase: IVerifyOtpUseCase;
|
IVerifyOtpUseCase: IVerifyOtpUseCase;
|
||||||
ISignOutUseCase: ISignOutUseCase;
|
ISignOutUseCase: ISignOutUseCase;
|
||||||
|
|
|
@ -19,8 +19,8 @@ export interface IUsersRepository {
|
||||||
createUser(input: ICreateUserSchema, tx?: ITransaction): Promise<IUserSupabaseSchema>;
|
createUser(input: ICreateUserSchema, tx?: ITransaction): Promise<IUserSupabaseSchema>;
|
||||||
inviteUser(credential: ICredentialsInviteUserSchema, tx?: ITransaction): Promise<IUserSupabaseSchema>;
|
inviteUser(credential: ICredentialsInviteUserSchema, tx?: ITransaction): Promise<IUserSupabaseSchema>;
|
||||||
updateUser(credential: ICredentialUpdateUserSchema, input: Partial<IUpdateUserSchema>, tx?: ITransaction): Promise<IUserSchema>;
|
updateUser(credential: ICredentialUpdateUserSchema, input: Partial<IUpdateUserSchema>, tx?: ITransaction): Promise<IUserSchema>;
|
||||||
deleteUser(credential: ICredentialsDeleteUserSchema, tx?: ITransaction): Promise<IUserSchema>;
|
deleteUser(credential: ICredentialsDeleteUserSchema, tx?: ITransaction): Promise<void>;
|
||||||
banUser(credential: ICredentialsBanUserSchema, input: IBanUserSchema, tx?: ITransaction): Promise<IUserSchema>;
|
banUser(credential: ICredentialsBanUserSchema, input: IBanUserSchema, tx?: ITransaction): Promise<void>;
|
||||||
unbanUser(credential: ICredentialsUnbanUserSchema, tx?: ITransaction): Promise<IUserSchema>;
|
unbanUser(credential: ICredentialsUnbanUserSchema, tx?: ITransaction): Promise<void>;
|
||||||
uploadAvatar(userId: string, file: File): Promise<string>;
|
uploadAvatar(userId: string, file: File): Promise<string>;
|
||||||
}
|
}
|
|
@ -2,10 +2,10 @@ import { z } from "zod";
|
||||||
import { UserSchema } from "@/src/entities/models/users/users.model";
|
import { UserSchema } from "@/src/entities/models/users/users.model";
|
||||||
|
|
||||||
export const SessionSchema = z.object({
|
export const SessionSchema = z.object({
|
||||||
user: UserSchema.pick({
|
user: z.object({
|
||||||
id: true,
|
id: z.string(),
|
||||||
email: true,
|
email: z.string().email().optional(),
|
||||||
roles_id: true,
|
role_id: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
expiresAt: z.number().optional(),
|
expiresAt: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,7 +39,7 @@ const timestampSchema = z.union([z.string(), z.date()]).nullable();
|
||||||
export const UserSchema = z.object({
|
export const UserSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
roles_id: z.string().optional(), // Sesuaikan dengan field di Prisma
|
roles_id: z.string().optional(), // Sesuaikan dengan field di Prisma
|
||||||
email: z.string().email().optional(),
|
email: z.string().email(),
|
||||||
email_confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
|
email_confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
|
||||||
encrypted_password: z.string().nullable().optional(),
|
encrypted_password: z.string().nullable().optional(),
|
||||||
invited_at: z.union([z.string(), z.date()]).nullable().optional(),
|
invited_at: z.union([z.string(), z.date()]).nullable().optional(),
|
||||||
|
|
|
@ -69,18 +69,28 @@ export class PermissionsRepository implements IPermissionsRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkPermission(role: string, action: string, resource: string): Promise<boolean> {
|
async checkPermission(role: string, action: string, resource: string): Promise<boolean> {
|
||||||
const result = await db.permissions.findFirst({
|
return await this.instrumentationService.startSpan({ name: "Check Permission" },
|
||||||
where: {
|
async () => {
|
||||||
role: {
|
try {
|
||||||
name: role
|
|
||||||
},
|
const result = await db.permissions.findFirst({
|
||||||
action: action,
|
where: {
|
||||||
resource: {
|
role: {
|
||||||
name: resource
|
name: role
|
||||||
|
},
|
||||||
|
action: action,
|
||||||
|
resource: {
|
||||||
|
name: resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!result;
|
||||||
|
} catch (err) {
|
||||||
|
this.crashReporterService.report(err);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
)
|
||||||
|
|
||||||
return !!result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -412,7 +412,7 @@ export class UsersRepository implements IUsersRepository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteUser(credential: ICredentialsDeleteUserSchema, tx?: ITransaction): Promise<IUserSchema> {
|
async deleteUser(credential: ICredentialsDeleteUserSchema, tx?: ITransaction): Promise<void> {
|
||||||
return await this.instrumentationService.startSpan({
|
return await this.instrumentationService.startSpan({
|
||||||
name: "UsersRepository > deleteUser",
|
name: "UsersRepository > deleteUser",
|
||||||
}, async () => {
|
}, async () => {
|
||||||
|
@ -435,10 +435,7 @@ export class UsersRepository implements IUsersRepository {
|
||||||
throw new DatabaseOperationError("Failed to delete user");
|
throw new DatabaseOperationError("Failed to delete user");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return;
|
||||||
...user,
|
|
||||||
id: credential.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.crashReporterService.report(err);
|
this.crashReporterService.report(err);
|
||||||
|
@ -447,7 +444,7 @@ export class UsersRepository implements IUsersRepository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async banUser(credential: ICredentialsBanUserSchema, input: IBanUserSchema, tx?: ITransaction): Promise<IUserSchema> {
|
async banUser(credential: ICredentialsBanUserSchema, input: IBanUserSchema, tx?: ITransaction): Promise<void> {
|
||||||
return await this.instrumentationService.startSpan({
|
return await this.instrumentationService.startSpan({
|
||||||
name: "UsersRepository > banUser",
|
name: "UsersRepository > banUser",
|
||||||
}, async () => {
|
}, async () => {
|
||||||
|
@ -472,10 +469,7 @@ export class UsersRepository implements IUsersRepository {
|
||||||
throw new DatabaseOperationError("Failed to ban user");
|
throw new DatabaseOperationError("Failed to ban user");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return;
|
||||||
...user,
|
|
||||||
id: credential.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.crashReporterService.report(err);
|
this.crashReporterService.report(err);
|
||||||
|
@ -485,7 +479,7 @@ export class UsersRepository implements IUsersRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async unbanUser(credential: ICredentialsUnbanUserSchema, tx?: ITransaction): Promise<IUserSchema> {
|
async unbanUser(credential: ICredentialsUnbanUserSchema, tx?: ITransaction): Promise<void> {
|
||||||
return await this.instrumentationService.startSpan({
|
return await this.instrumentationService.startSpan({
|
||||||
name: "UsersRepository > unbanUser",
|
name: "UsersRepository > unbanUser",
|
||||||
}, async () => {
|
}, async () => {
|
||||||
|
@ -510,10 +504,7 @@ export class UsersRepository implements IUsersRepository {
|
||||||
throw new DatabaseOperationError("Failed to unban user");
|
throw new DatabaseOperationError("Failed to unban user");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return;
|
||||||
...user,
|
|
||||||
id: credential.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.crashReporterService.report(err);
|
this.crashReporterService.report(err);
|
||||||
|
|
|
@ -279,22 +279,33 @@ export class AuthenticationService implements IAuthenticationService {
|
||||||
|
|
||||||
async checkPermission(userId: string, action: string, resource: string): Promise<boolean> {
|
async checkPermission(userId: string, action: string, resource: string): Promise<boolean> {
|
||||||
return await this.instrumentationService.startSpan({
|
return await this.instrumentationService.startSpan({
|
||||||
name: "checkPermission Use Case",
|
name: "",
|
||||||
}, async () => {
|
}, async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const user = await db.users.findUnique({
|
|
||||||
where: { id: userId },
|
|
||||||
include: { role: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
// const user = await db.users.findUnique({
|
||||||
return false;
|
// where: { id: userId },
|
||||||
}
|
// include: { role: true }
|
||||||
|
// });
|
||||||
|
|
||||||
const role = user.role.name;
|
// if (!user) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
return await this.permissionRepository.checkPermission(role, action, resource);
|
// const role = user.role.name;
|
||||||
|
|
||||||
|
// const permission = await db.permissions.findFirst({
|
||||||
|
// where: {
|
||||||
|
// role: { name: role },
|
||||||
|
// action,
|
||||||
|
// resource: {
|
||||||
|
// name: resource
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.crashReporterService.report(err)
|
this.crashReporterService.report(err)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { IUsersRepository } from "@/src/application/repositories/users.repository.interface";
|
||||||
import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface";
|
import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface";
|
||||||
import { ICheckPermissionsUseCase } from "@/src/application/use-cases/auth/check-permissions.use-case";
|
import { ICheckPermissionsUseCase } from "@/src/application/use-cases/auth/check-permissions.use-case";
|
||||||
import { InputParseError } from "@/src/entities/errors/common";
|
import { InputParseError } from "@/src/entities/errors/common";
|
||||||
|
@ -15,11 +16,19 @@ export type ICheckPermissionsController = ReturnType<typeof checkPermissionsCont
|
||||||
export const checkPermissionsController =
|
export const checkPermissionsController =
|
||||||
(
|
(
|
||||||
instrumentationService: IInstrumentationService,
|
instrumentationService: IInstrumentationService,
|
||||||
checkPermissionUseCase: ICheckPermissionsUseCase
|
checkPermissionUseCase: ICheckPermissionsUseCase,
|
||||||
|
usersRpository: IUsersRepository
|
||||||
) =>
|
) =>
|
||||||
async (input: Partial<z.infer<typeof checkPermissionInputSchema>>) => {
|
async (input: Partial<z.infer<typeof checkPermissionInputSchema>>) => {
|
||||||
return await instrumentationService.startSpan({ name: "checkPermission Controller" },
|
return await instrumentationService.startSpan({ name: "checkPermission Controller" },
|
||||||
async () => {
|
async () => {
|
||||||
|
|
||||||
|
// const session = await usersRpository.getCurrentUser()
|
||||||
|
|
||||||
|
// if (!session) {
|
||||||
|
// throw new InputParseError("User not found")
|
||||||
|
// }
|
||||||
|
|
||||||
const { data, error: inputParseError } = checkPermissionInputSchema.safeParse(input)
|
const { data, error: inputParseError } = checkPermissionInputSchema.safeParse(input)
|
||||||
|
|
||||||
if (inputParseError) {
|
if (inputParseError) {
|
||||||
|
|
Loading…
Reference in New Issue