rbac-shield 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -341
- package/bin/templates.js +6 -2
- package/dist/factory.d.mts +11 -2
- package/dist/factory.d.ts +11 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +160 -39
- package/dist/factory.mjs +160 -39
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.mts +3 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,126 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/rbac-shield)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[]()
|
|
6
5
|
[](https://www.typescriptlang.org/)
|
|
7
6
|
|
|
8
|
-
> [!WARNING] > **Public Beta**: ensuring strict type safety and performance. API is stable but minor breaking changes might occur before v1.0.
|
|
9
|
-
|
|
10
7
|
**The production-ready, type-safe Role-Based Access Control (RBAC) system for Next.js applications.**
|
|
11
8
|
|
|
12
|
-
Built for modern web development with **React 19**, **TypeScript 5**, and **Next.js App Router** compatibility. RBAC Shield provides a seamless, multi-tenant permission system that
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## 📑 Table of Contents
|
|
17
|
-
|
|
18
|
-
- [Features](#-features)
|
|
19
|
-
- [Quick Setup (CLI)](#-quick-setup-recommended)
|
|
20
|
-
- [Manual Installation](#-manual-installation)
|
|
21
|
-
- [Quick Start](#-quick-start)
|
|
22
|
-
- [Guides & Patterns](#-guides--patterns)
|
|
23
|
-
- [Roles as Permissions](#roles-as-permissions)
|
|
24
|
-
- [Complex Logic (AND/OR)](#complex-logic-andor)
|
|
25
|
-
- [Customizing UX](#customizing-ux-loading--redirects)
|
|
26
|
-
- [SSR & Hydration](#ssr--hydration-eliminate-loading-states)
|
|
27
|
-
- [Logic Switching (Dynamic APIs)](#logic-switching-dynamic-apis)
|
|
28
|
-
- [Server Action Guards](#server-action-guards)
|
|
29
|
-
- [API Reference](#-api-reference)
|
|
30
|
-
- [Security & Best Practices](#-security--best-practices)
|
|
31
|
-
- [Troubleshooting](#-troubleshooting)
|
|
9
|
+
Built for modern web development with **React 19**, **TypeScript 5**, and **Next.js App Router** compatibility. RBAC Shield provides a seamless, multi-tenant permission system that supports both **Role-based** and **Permission-based** strategies.
|
|
32
10
|
|
|
33
11
|
---
|
|
34
12
|
|
|
35
13
|
## ✨ Features
|
|
36
14
|
|
|
37
|
-
- 🎯 **Type-Safe Permissions**: Typescript "Prettify" helpers ensure tooltips show exact prop shapes
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
15
|
+
- 🎯 **Type-Safe Permissions**: Typescript "Prettify" helpers ensure tooltips show exact prop shapes.
|
|
16
|
+
- 👑 **First-Class Role Support**: Check for **Roles**, **Permissions**, or **Both**.
|
|
17
|
+
- 🚀 **High Performance**: Optimized with React Context and memoization.
|
|
18
|
+
- 🏢 **Multi-Tenant Native**: Switch between organizations/roles instantly.
|
|
19
|
+
- ⚡ **Zero Loading States**: Instant hydration via server-side data injection.
|
|
20
|
+
- 🛡️ **Route Protection**: Declarative guards with auto-redirects.
|
|
21
|
+
- 🌍 **Universal**: Works in Client Components, Server Components, and Middleware.
|
|
44
22
|
|
|
45
23
|
---
|
|
46
24
|
|
|
47
|
-
##
|
|
25
|
+
## 📦 Installation
|
|
48
26
|
|
|
49
|
-
|
|
27
|
+
### Option 1: Interactive CLI (Recommended)
|
|
28
|
+
|
|
29
|
+
This will set up your types and configuration file automatically.
|
|
50
30
|
|
|
51
31
|
```bash
|
|
52
32
|
npx rbac-shield init
|
|
53
33
|
```
|
|
54
34
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
1. Detect your project type (Next.js/React, TS/JS)
|
|
58
|
-
2. Help you define your resources (e.g., `projects`) and actions (e.g., `create`)
|
|
59
|
-
3. Generate a clean, type-safe `lib/rbac.ts` file configured for your app
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## 📦 Manual Installation
|
|
64
|
-
|
|
65
|
-
If you prefer to set things up yourself:
|
|
35
|
+
### Option 2: Manual Install
|
|
66
36
|
|
|
67
37
|
```bash
|
|
68
38
|
npm install rbac-shield
|
|
69
39
|
# or
|
|
70
40
|
yarn add rbac-shield
|
|
71
|
-
# or
|
|
72
|
-
pnpm add rbac-shield
|
|
73
|
-
# or
|
|
74
|
-
bun add rbac-shield
|
|
75
41
|
```
|
|
76
42
|
|
|
77
|
-
**Peer Dependencies:**
|
|
78
|
-
Ensure you have peer dependencies installed (standard in Next.js apps):
|
|
79
|
-
`react >= 18.0.0`, `react-dom >= 18.0.0`, `next >= 13.0.0`
|
|
80
|
-
|
|
81
43
|
---
|
|
82
44
|
|
|
83
45
|
## 🚀 Quick Start
|
|
84
46
|
|
|
85
|
-
### 1. Define
|
|
47
|
+
### 1. Define Schema
|
|
86
48
|
|
|
87
|
-
Create
|
|
49
|
+
Create `lib/rbac.ts` to define your types and export your instances.
|
|
88
50
|
|
|
89
51
|
```typescript
|
|
90
52
|
// lib/rbac.ts
|
|
91
53
|
"use client";
|
|
92
54
|
import { createRBAC } from "rbac-shield";
|
|
93
55
|
|
|
94
|
-
|
|
95
|
-
export type
|
|
56
|
+
export type Resources = "projects" | "billing" | "users";
|
|
57
|
+
export type Actions = "view" | "create" | "edit" | "delete";
|
|
96
58
|
|
|
97
|
-
// 2. Define actions (what users can do)
|
|
98
|
-
export type Actions = "view" | "create" | "edit" | "delete" | "export";
|
|
99
|
-
|
|
100
|
-
// 3. Create your instances
|
|
101
59
|
export const {
|
|
102
60
|
RBACProvider,
|
|
103
|
-
useRBAC,
|
|
61
|
+
useRBAC,
|
|
62
|
+
useHasRole,
|
|
104
63
|
useHasPermission,
|
|
105
|
-
|
|
64
|
+
useAccess, // Advanced checks
|
|
106
65
|
Can,
|
|
107
66
|
ProtectedRoute,
|
|
108
67
|
guard,
|
|
109
68
|
} = createRBAC<Resources, Actions>();
|
|
110
69
|
```
|
|
111
70
|
|
|
112
|
-
### 2. Wrap
|
|
71
|
+
### 2. Wrap App
|
|
113
72
|
|
|
114
|
-
|
|
73
|
+
Wrap your root layout with the provider.
|
|
115
74
|
|
|
116
75
|
```tsx
|
|
117
76
|
// app/layout.tsx
|
|
118
77
|
import { RBACProvider } from "@/lib/rbac";
|
|
119
78
|
|
|
120
|
-
export default function RootLayout({
|
|
121
|
-
children,
|
|
122
|
-
}: {
|
|
123
|
-
children: React.ReactNode;
|
|
124
|
-
}) {
|
|
79
|
+
export default function RootLayout({ children }) {
|
|
125
80
|
return (
|
|
126
81
|
<html lang="en">
|
|
127
82
|
<body>
|
|
@@ -134,63 +89,63 @@ export default function RootLayout({
|
|
|
134
89
|
|
|
135
90
|
### 3. Load Permissions
|
|
136
91
|
|
|
137
|
-
Initialize
|
|
92
|
+
Initialize permissions. For async user data, **wait for the user to load** before setting auth.
|
|
138
93
|
|
|
139
94
|
```tsx
|
|
140
|
-
// components/
|
|
95
|
+
// components/PermissionLoader.tsx
|
|
141
96
|
"use client";
|
|
142
97
|
import { useEffect } from "react";
|
|
143
|
-
import { useRBAC
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
children,
|
|
148
|
-
}: {
|
|
149
|
-
user: any;
|
|
150
|
-
children: React.ReactNode;
|
|
151
|
-
}) {
|
|
98
|
+
import { useRBAC } from "@/lib/rbac";
|
|
99
|
+
import { useUser } from "@/hooks/useUser";
|
|
100
|
+
|
|
101
|
+
export function PermissionLoader({ children }) {
|
|
152
102
|
const { setAuth, switchTenant } = useRBAC();
|
|
103
|
+
const { user, isLoading } = useUser();
|
|
153
104
|
|
|
154
105
|
useEffect(() => {
|
|
155
|
-
if (user)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
}, [user, setAuth, switchTenant]);
|
|
106
|
+
if (isLoading || !user) return;
|
|
107
|
+
|
|
108
|
+
// Load Roles + Permissions
|
|
109
|
+
setAuth([
|
|
110
|
+
{
|
|
111
|
+
tenantId: user.id || "default",
|
|
112
|
+
roles: [user.role], // e.g. ["admin"]
|
|
113
|
+
permissions: user.permissions, // e.g. ["projects:view"]
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
167
116
|
|
|
117
|
+
switchTenant(user.id || "default");
|
|
118
|
+
}, [user, isLoading]);
|
|
119
|
+
|
|
120
|
+
if (isLoading || !user) return null; // Prevent render until authed
|
|
168
121
|
return <>{children}</>;
|
|
169
122
|
}
|
|
170
123
|
```
|
|
171
124
|
|
|
172
|
-
### 4.
|
|
125
|
+
### 4. Protect Routes
|
|
173
126
|
|
|
174
|
-
Use the
|
|
127
|
+
Use the components to guard access.
|
|
175
128
|
|
|
176
129
|
```tsx
|
|
177
|
-
import { ProtectedRoute, Can,
|
|
130
|
+
import { ProtectedRoute, Can, useHasRole, useAccess } from "@/lib/rbac";
|
|
131
|
+
|
|
132
|
+
export default function AdminDashboard() {
|
|
133
|
+
const isSuperAdmin = useHasRole("super_admin");
|
|
178
134
|
|
|
179
|
-
|
|
180
|
-
const
|
|
135
|
+
// Advanced: Check if user is Admin OR has special permission
|
|
136
|
+
const canManage = useAccess({
|
|
137
|
+
roles: ["admin"],
|
|
138
|
+
permissions: ["system:manage"],
|
|
139
|
+
});
|
|
181
140
|
|
|
182
141
|
return (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<h1>Project Settings</h1>
|
|
142
|
+
<ProtectedRoute role={["admin", "super_admin"]} fallbackPath="/login">
|
|
143
|
+
<h1>Admin Dashboard</h1>
|
|
186
144
|
|
|
187
|
-
{/*
|
|
188
|
-
<Can permission="billing:view"
|
|
145
|
+
{/* Conditional Rendering */}
|
|
146
|
+
<Can permission="billing:view">
|
|
189
147
|
<BillingWidget />
|
|
190
148
|
</Can>
|
|
191
|
-
|
|
192
|
-
{/* 3. Hook Logic Example */}
|
|
193
|
-
<button disabled={!canDelete}>Delete Project</button>
|
|
194
149
|
</ProtectedRoute>
|
|
195
150
|
);
|
|
196
151
|
}
|
|
@@ -198,284 +153,84 @@ export default function ProjectSettings() {
|
|
|
198
153
|
|
|
199
154
|
---
|
|
200
155
|
|
|
201
|
-
##
|
|
202
|
-
|
|
203
|
-
### Roles as Permissions
|
|
156
|
+
## 👑 Role Management & Logic
|
|
204
157
|
|
|
205
|
-
|
|
206
|
-
Assign a "identity permission" to your roles, e.g., `role:admin`, `role:manager`.
|
|
158
|
+
RBAC Shield uses a **Unified Access Logic** across all components.
|
|
207
159
|
|
|
208
|
-
|
|
209
|
-
// ❌ Bad: Brittle
|
|
210
|
-
if (user.role === 'admin') <AdminText />
|
|
211
|
-
|
|
212
|
-
// ✅ Good: Flexible
|
|
213
|
-
// User permissions: ['post:read', 'role:admin']
|
|
214
|
-
<Can permission="role:admin" fallback="Welcome User">
|
|
215
|
-
Welcome Admin
|
|
216
|
-
</Can>
|
|
217
|
-
```
|
|
160
|
+
### Logic Matrix
|
|
218
161
|
|
|
219
|
-
|
|
162
|
+
| Props Provided | Logic Applied | Example |
|
|
163
|
+
| :------------------ | :--------------------------------------------------- | :------------------------------------- |
|
|
164
|
+
| **Role Only** | User has `role` | `<Can role="admin">` |
|
|
165
|
+
| **Permission Only** | User has `permission` | `<Can permission="edit">` |
|
|
166
|
+
| **Both** | **STRICT AND**: User has `role` **AND** `permission` | `<Can role="admin" permission="edit">` |
|
|
220
167
|
|
|
221
|
-
|
|
222
|
-
Use the array format with `requireAll`.
|
|
168
|
+
### Wildcards (`*`)
|
|
223
169
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<Can permission={["role:admin", "post:delete"]} requireAll>
|
|
227
|
-
<DeleteEverythingButton />
|
|
228
|
-
</Can>
|
|
229
|
-
```
|
|
170
|
+
- **Roles**: If user has role `*`, they pass ALL role checks.
|
|
171
|
+
- **Permissions**: If user has permission `*`, they pass ALL permission checks.
|
|
230
172
|
|
|
231
|
-
###
|
|
173
|
+
### Arrays (OR Logic)
|
|
232
174
|
|
|
233
|
-
|
|
175
|
+
Providing an array means "User must match **ANY** of these".
|
|
234
176
|
|
|
235
177
|
```tsx
|
|
236
|
-
//
|
|
237
|
-
<ProtectedRoute
|
|
238
|
-
<Dashboard />
|
|
239
|
-
</ProtectedRoute>
|
|
240
|
-
|
|
241
|
-
// 2. Instant Redirect (No "Access Denied" screen)
|
|
242
|
-
<ProtectedRoute permission="admin:view" fallback={null} fallbackPath="/login" >
|
|
243
|
-
<Dashboard />
|
|
244
|
-
</ProtectedRoute>
|
|
245
|
-
|
|
246
|
-
// 3. No Redirect (Show Custom 403 Page)
|
|
247
|
-
<ProtectedRoute permission="admin:view" redirect={false} fallback={<AccessDeniedPage />} >
|
|
248
|
-
<Dashboard />
|
|
249
|
-
</ProtectedRoute>
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### SSR & Hydration (Eliminate Loading States)
|
|
253
|
-
|
|
254
|
-
Prevent the "flicker" of loading states by passing server-side permissions directly to the provider.
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
// app/layout.tsx (Server Component)
|
|
258
|
-
import { RBACProvider } from "@/lib/rbac";
|
|
259
|
-
import { getSession } from "@/lib/auth"; // Your auth logic
|
|
260
|
-
|
|
261
|
-
export default async function RootLayout({ children }) {
|
|
262
|
-
const session = await getSession();
|
|
263
|
-
|
|
264
|
-
// Prepare valid initial data matches TenantAuthInput[]
|
|
265
|
-
const initialData = session
|
|
266
|
-
? [
|
|
267
|
-
{
|
|
268
|
-
tenantId: session.orgId,
|
|
269
|
-
permissions: session.permissions, // string[] from DB is fine!
|
|
270
|
-
},
|
|
271
|
-
]
|
|
272
|
-
: [];
|
|
273
|
-
|
|
274
|
-
return (
|
|
275
|
-
<html>
|
|
276
|
-
<body>
|
|
277
|
-
<RBACProvider initialData={initialData}>{children}</RBACProvider>
|
|
278
|
-
</body>
|
|
279
|
-
</html>
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Logic Switching (Dynamic APIs)
|
|
285
|
-
|
|
286
|
-
Need to call different APIs or execute different logic based on permissions? You have two options:
|
|
287
|
-
|
|
288
|
-
#### Option 1: Top-Level Hook (Recommended)
|
|
289
|
-
|
|
290
|
-
Use this when you want to resolve the handler _during render_ but call it later (e.g., on click).
|
|
291
|
-
|
|
292
|
-
```tsx
|
|
293
|
-
import { useRBAC, useMatch } from "@/lib/rbac";
|
|
294
|
-
|
|
295
|
-
export default function Dashboard() {
|
|
296
|
-
// 1. Resolve the handler at the top level
|
|
297
|
-
// Note: We return a FUNCTION () => ... so it doesn't run immediately!
|
|
298
|
-
const getData = useMatch({
|
|
299
|
-
"admin:view": () => () => api.getAdminStats(),
|
|
300
|
-
"manager:view": () => () => api.getManagerStats(),
|
|
301
|
-
default: () => () => api.getUserStats(),
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const handleRefresh = () => {
|
|
305
|
-
// 2. Call the resolved function
|
|
306
|
-
if (getData) getData();
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
return <button onClick={handleRefresh}>Refresh Data</button>;
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### Option 2: Event Handler Utility
|
|
314
|
-
|
|
315
|
-
Use the `match` utility (not the hook) if you want to keep all logic inside the event handler.
|
|
316
|
-
|
|
317
|
-
```tsx
|
|
318
|
-
import { usePermissions, match } from "@/lib/rbac";
|
|
319
|
-
|
|
320
|
-
export default function Dashboard() {
|
|
321
|
-
const permissions = usePermissions(); // Hooks must be top-level
|
|
322
|
-
|
|
323
|
-
const handleRefresh = () => {
|
|
324
|
-
// 'match' is a plain function, safe to use here!
|
|
325
|
-
match(permissions, {
|
|
326
|
-
"admin:view": () => api.getAdminStats(),
|
|
327
|
-
"manager:view": () => api.getManagerStats(),
|
|
328
|
-
default: () => api.getUserStats(),
|
|
329
|
-
});
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Server Action Guards
|
|
335
|
-
|
|
336
|
-
Protect arbitrary functions (like Server Actions) using the `guard` wrapper.
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
// actions/project.ts (Server Action)
|
|
340
|
-
import { guard } from "rbac-shield";
|
|
341
|
-
import { getSession } from "@/lib/auth";
|
|
342
|
-
|
|
343
|
-
export async function deleteProject(id: string) {
|
|
344
|
-
const session = await getSession();
|
|
345
|
-
|
|
346
|
-
// Wrap your delicate logic
|
|
347
|
-
const safeAction = guard(
|
|
348
|
-
session.permissions, // User's permissions
|
|
349
|
-
"project:delete", // Required permission
|
|
350
|
-
async () => {
|
|
351
|
-
await db.project.delete(id);
|
|
352
|
-
return "Deleted!";
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
return safeAction(); // Throws Error if unauthorized
|
|
357
|
-
}
|
|
178
|
+
// Allow if user is 'admin' OR 'manager'
|
|
179
|
+
<ProtectedRoute role={["admin", "manager"]}>
|
|
358
180
|
```
|
|
359
181
|
|
|
360
182
|
---
|
|
361
183
|
|
|
362
|
-
##
|
|
184
|
+
## � API Reference
|
|
363
185
|
|
|
364
186
|
### Components
|
|
365
187
|
|
|
366
|
-
#### `<RBACProvider>`
|
|
367
|
-
|
|
368
|
-
Top-level provider component.
|
|
369
|
-
|
|
370
|
-
- **initialData**: (Optional) `TenantAuthInput[]`. Hydrate state immediately.
|
|
371
|
-
- **debug**: (Optional) `boolean`. Log permission checks to console.
|
|
372
|
-
|
|
373
188
|
#### `<ProtectedRoute>`
|
|
374
189
|
|
|
375
|
-
|
|
190
|
+
Guards an entire route. Redirects if access denied.
|
|
376
191
|
|
|
377
|
-
- **
|
|
378
|
-
- **
|
|
379
|
-
- **
|
|
380
|
-
- **
|
|
381
|
-
- **
|
|
382
|
-
- **
|
|
192
|
+
- **role**: `string | string[]`
|
|
193
|
+
- **permission**: `string | string[]`
|
|
194
|
+
- **requireAll**: `boolean` (Default: `false` - generally used for checking multiple permissions)
|
|
195
|
+
- **redirect**: `boolean` (Default: `true`)
|
|
196
|
+
- **fallbackPath**: `string` (Default: `/`)
|
|
197
|
+
- **fallback**: `ReactNode` (Shown while redirecting)
|
|
383
198
|
|
|
384
199
|
#### `<Can>`
|
|
385
200
|
|
|
386
|
-
|
|
201
|
+
Conditionally renders children.
|
|
387
202
|
|
|
388
|
-
- **
|
|
389
|
-
- **
|
|
390
|
-
- **fallback**:
|
|
203
|
+
- **role**: `string | string[]`
|
|
204
|
+
- **permission**: `string | string[]`
|
|
205
|
+
- **fallback**: `ReactNode` (Shown if denied)
|
|
391
206
|
|
|
392
207
|
### Hooks
|
|
393
208
|
|
|
394
|
-
#### `
|
|
395
|
-
|
|
396
|
-
Access raw state and actions.
|
|
209
|
+
#### `useAccess({ roles?, permissions? })`
|
|
397
210
|
|
|
398
|
-
|
|
399
|
-
- `switchTenant(id)`: Change active context.
|
|
400
|
-
- `isLoading`: `boolean`.
|
|
401
|
-
- `activeTenantId`: `string | null`.
|
|
211
|
+
Returns `boolean`. Checks if user matches ANY of the roles OR ANY of the permissions.
|
|
402
212
|
|
|
403
|
-
#### `
|
|
213
|
+
#### `useHasRole(role)`
|
|
404
214
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
- **handlers**: Object `{ [permission]: () => T }`.
|
|
408
|
-
- **default**: Fallback function.
|
|
215
|
+
Returns `boolean`. Checks for specific role (or wildcard).
|
|
409
216
|
|
|
410
217
|
#### `useHasPermission(permission)`
|
|
411
218
|
|
|
412
|
-
Returns `boolean`. Checks for
|
|
413
|
-
|
|
414
|
-
#### `useHasAnyPermission(permissions[])`
|
|
415
|
-
|
|
416
|
-
Returns `boolean`. True if user has **at least one** permission.
|
|
219
|
+
Returns `boolean`. Checks for specific permission (or wildcard).
|
|
417
220
|
|
|
418
|
-
#### `
|
|
419
|
-
|
|
420
|
-
Returns `boolean`. True only if user has **every single** permission.
|
|
421
|
-
|
|
422
|
-
### Utilities
|
|
423
|
-
|
|
424
|
-
#### `guard(userPerms, requiredPerm, function)`
|
|
425
|
-
|
|
426
|
-
Higher-order function that wraps and protects a function execution.
|
|
427
|
-
|
|
428
|
-
#### `checkPermission(userPerms, requiredPerm)`
|
|
429
|
-
|
|
430
|
-
Universal helper for checking permissions (works on Server/Client/Edge).
|
|
431
|
-
|
|
432
|
-
---
|
|
433
|
-
|
|
434
|
-
## 🛡️ Security & Best Practices
|
|
435
|
-
|
|
436
|
-
> [!IMPORTANT] > **Client-side checks are for User Experience (UX) only.**
|
|
437
|
-
|
|
438
|
-
RBAC Shield controls what the user _sees_ in the browser, but it cannot stop a determined attacker from crafting API requests manually.
|
|
439
|
-
|
|
440
|
-
### 1. Server-Side Verification (Universal)
|
|
441
|
-
|
|
442
|
-
Use the universal `checkPermission` helper in Middleware, Server Actions, or API Routes:
|
|
443
|
-
|
|
444
|
-
```tsx
|
|
445
|
-
import { checkPermission } from "rbac-shield";
|
|
446
|
-
// 1. Middleware Example
|
|
447
|
-
export function middleware(req) {
|
|
448
|
-
const permissions = getPermissionsFromCookie(req);
|
|
449
|
-
if (!checkPermission(permissions, "admin:access")) {
|
|
450
|
-
return NextResponse.redirect(new URL("/403", req.url));
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
### 2. Debug Mode
|
|
456
|
-
|
|
457
|
-
Enable debug mode to see permission logs in the console:
|
|
221
|
+
#### `useRBAC()`
|
|
458
222
|
|
|
459
|
-
|
|
460
|
-
<RBACProvider debug={true}>{children}</RBACProvider>
|
|
461
|
-
```
|
|
223
|
+
Access raw state (`isLoading`, `activeTenantId`, etc.) and actions (`setAuth`).
|
|
462
224
|
|
|
463
225
|
---
|
|
464
226
|
|
|
465
|
-
##
|
|
227
|
+
## 🛡️ Best Practices
|
|
466
228
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
| **Infinite Loading** | Ensure `RBACProvider` wraps your app and `setAuth` is called with valid data. |
|
|
470
|
-
| **Type Errors** | Verify your `Resources` and `Actions` types in `lib/rbac.ts` are exported. |
|
|
471
|
-
| **Hydration Mismatch** | `ProtectedRoute` and `Can` are client components; ensure they are used in client contexts or wrapped properly. |
|
|
229
|
+
1. **Server-Side Verification**: Always verify permissions on the server (API Routes, Server Actions) using the `checkPermission` utility or `guard` wrapper. Client-side checks are for UX only.
|
|
230
|
+
2. **Combine Checks**: Use Roles for high-level page access, and Permissions for specific button visibility.
|
|
472
231
|
|
|
473
232
|
---
|
|
474
233
|
|
|
475
|
-
## 🤝 Contributing
|
|
476
|
-
|
|
477
|
-
We welcome contributions! Please open an issue or submit a PR on our [GitHub repository](https://github.com/your-repo/rbac-shield).
|
|
478
|
-
|
|
479
234
|
## 📄 License
|
|
480
235
|
|
|
481
236
|
MIT © [Arif Hossain Roman](https://github.com/devnuckles)
|
package/bin/templates.js
CHANGED
|
@@ -33,7 +33,9 @@ export type Actions = ${actionsType};
|
|
|
33
33
|
export const {
|
|
34
34
|
RBACProvider,
|
|
35
35
|
useRBAC,
|
|
36
|
-
|
|
36
|
+
useHasRole,
|
|
37
|
+
useHasPermission,
|
|
38
|
+
useAccess,
|
|
37
39
|
useHasAnyPermission,
|
|
38
40
|
useHasAllPermissions,
|
|
39
41
|
usePermissions,
|
|
@@ -53,7 +55,9 @@ import { createRBAC } from 'rbac-shield';
|
|
|
53
55
|
export const {
|
|
54
56
|
RBACProvider,
|
|
55
57
|
useRBAC,
|
|
56
|
-
|
|
58
|
+
useHasRole,
|
|
59
|
+
useHasPermission,
|
|
60
|
+
useAccess,
|
|
57
61
|
useHasAnyPermission,
|
|
58
62
|
useHasAllPermissions,
|
|
59
63
|
usePermissions,
|
package/dist/factory.d.mts
CHANGED
|
@@ -5,7 +5,9 @@ type Prettify<T> = {
|
|
|
5
5
|
} & {};
|
|
6
6
|
export interface CanProps<R extends string, A extends string> {
|
|
7
7
|
/** Single permission or array of permissions to check */
|
|
8
|
-
permission
|
|
8
|
+
permission?: PermissionString<R, A> | PermissionString<R, A>[];
|
|
9
|
+
/** Single role or array of roles to check */
|
|
10
|
+
role?: string | string[];
|
|
9
11
|
children: React.ReactNode;
|
|
10
12
|
/** Content to render if permission is denied */
|
|
11
13
|
fallback?: React.ReactNode;
|
|
@@ -15,7 +17,9 @@ export interface CanProps<R extends string, A extends string> {
|
|
|
15
17
|
export interface ProtectedRouteProps<R extends string, A extends string> {
|
|
16
18
|
children: React.ReactNode;
|
|
17
19
|
/** Single permission or array of permissions to check */
|
|
18
|
-
permission
|
|
20
|
+
permission?: PermissionString<R, A> | PermissionString<R, A>[];
|
|
21
|
+
/** Single role or array of roles to check */
|
|
22
|
+
role?: string | string[];
|
|
19
23
|
/** Path to redirect to if access denied (if redirect is true) */
|
|
20
24
|
fallbackPath?: string;
|
|
21
25
|
/** Content to render if access denied (or while redirecting) */
|
|
@@ -40,7 +44,12 @@ export interface RBACFactory<R extends string, A extends string> {
|
|
|
40
44
|
switchTenant: (tenantId: string) => void;
|
|
41
45
|
reset: () => void;
|
|
42
46
|
};
|
|
47
|
+
useHasRole: (role: string) => boolean;
|
|
43
48
|
useHasPermission: (permission: PermissionString<R, A>) => boolean;
|
|
49
|
+
useAccess: (requirements: {
|
|
50
|
+
roles?: string[];
|
|
51
|
+
permissions?: string[];
|
|
52
|
+
}) => boolean;
|
|
44
53
|
useHasAnyPermission: (permissions: PermissionString<R, A>[]) => boolean;
|
|
45
54
|
useHasAllPermissions: (permissions: PermissionString<R, A>[]) => boolean;
|
|
46
55
|
usePermissions: () => PermissionString<R, A>[];
|
package/dist/factory.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ type Prettify<T> = {
|
|
|
5
5
|
} & {};
|
|
6
6
|
export interface CanProps<R extends string, A extends string> {
|
|
7
7
|
/** Single permission or array of permissions to check */
|
|
8
|
-
permission
|
|
8
|
+
permission?: PermissionString<R, A> | PermissionString<R, A>[];
|
|
9
|
+
/** Single role or array of roles to check */
|
|
10
|
+
role?: string | string[];
|
|
9
11
|
children: React.ReactNode;
|
|
10
12
|
/** Content to render if permission is denied */
|
|
11
13
|
fallback?: React.ReactNode;
|
|
@@ -15,7 +17,9 @@ export interface CanProps<R extends string, A extends string> {
|
|
|
15
17
|
export interface ProtectedRouteProps<R extends string, A extends string> {
|
|
16
18
|
children: React.ReactNode;
|
|
17
19
|
/** Single permission or array of permissions to check */
|
|
18
|
-
permission
|
|
20
|
+
permission?: PermissionString<R, A> | PermissionString<R, A>[];
|
|
21
|
+
/** Single role or array of roles to check */
|
|
22
|
+
role?: string | string[];
|
|
19
23
|
/** Path to redirect to if access denied (if redirect is true) */
|
|
20
24
|
fallbackPath?: string;
|
|
21
25
|
/** Content to render if access denied (or while redirecting) */
|
|
@@ -40,7 +44,12 @@ export interface RBACFactory<R extends string, A extends string> {
|
|
|
40
44
|
switchTenant: (tenantId: string) => void;
|
|
41
45
|
reset: () => void;
|
|
42
46
|
};
|
|
47
|
+
useHasRole: (role: string) => boolean;
|
|
43
48
|
useHasPermission: (permission: PermissionString<R, A>) => boolean;
|
|
49
|
+
useAccess: (requirements: {
|
|
50
|
+
roles?: string[];
|
|
51
|
+
permissions?: string[];
|
|
52
|
+
}) => boolean;
|
|
44
53
|
useHasAnyPermission: (permissions: PermissionString<R, A>[]) => boolean;
|
|
45
54
|
useHasAllPermissions: (permissions: PermissionString<R, A>[]) => boolean;
|
|
46
55
|
usePermissions: () => PermissionString<R, A>[];
|