secure-role-guard 1.0.1 → 1.0.2

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.
Files changed (2) hide show
  1. package/README.md +157 -1258
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # secure-role-guard
2
2
 
3
- > Zero-vulnerability, framework-agnostic RBAC authorization library for React and Node.js applications.
3
+ > Zero-dependency RBAC authorization for React & Node.js. Define roles once, use everywhere.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/secure-role-guard.svg)](https://www.npmjs.com/package/secure-role-guard)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
8
7
  [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](https://www.npmjs.com/package/secure-role-guard)
9
8
 
@@ -11,74 +10,18 @@
11
10
 
12
11
  ---
13
12
 
14
- ## Table of Contents
13
+ ## 🚀 Quick Overview
15
14
 
16
- - [What This Package Does](#what-this-package-does)
17
- - [What This Package Does NOT Do](#what-this-package-does-not-do)
18
- - [Security Guarantees](#security-guarantees)
19
- - [Installation](#installation)
20
- - [Quick Start](#quick-start)
21
- - [API Reference](#api-reference)
22
- - [Real-World Examples](#real-world-examples)
23
- - [Framework Compatibility](#framework-compatibility)
24
- - [Fixed Roles vs Dynamic Roles](#fixed-roles-vs-dynamic-roles)
25
- - [Common Mistakes to Avoid](#common-mistakes-to-avoid)
26
- - [License](#license)
15
+ | Custom Code (Without Package) | With secure-role-guard |
16
+ | ----------------------------------- | --------------------------- |
17
+ | Write permission logic everywhere | Define once, use everywhere |
18
+ | Handle null/undefined edge cases | Built-in, tested |
19
+ | Different code for frontend/backend | Same API everywhere |
20
+ | 2-4 hours setup | 10 minutes setup |
27
21
 
28
22
  ---
29
23
 
30
- ## What This Package Does ✅
31
-
32
- | Feature | Description |
33
- | ----------------------------- | ------------------------------------------------------- |
34
- | **Role-Based Access Control** | Define roles with granular permissions |
35
- | **Pure Permission Checking** | Deterministic, side-effect-free authorization |
36
- | **React Integration** | Provider, hooks, and components for any React framework |
37
- | **Backend Adapters** | Optional Express and Next.js middleware |
38
- | **Wildcard Support** | Grant access with `*` (all) or `namespace.*` patterns |
39
- | **TypeScript First** | Full type safety with strict mode |
40
- | **Zero Dependencies** | Core has zero runtime dependencies |
41
- | **Framework Agnostic** | Works with Next.js, Remix, Gatsby, Astro, Vite, CRA |
42
-
43
- ---
44
-
45
- ## What This Package Does NOT Do ❌
46
-
47
- > **CRITICAL:** This package handles **AUTHORIZATION** only, **NOT AUTHENTICATION**.
48
-
49
- | This Package Does NOT | You Must Handle This |
50
- | --------------------- | -------------------------------------- |
51
- | Parse JWT tokens | Use a JWT library (jsonwebtoken, jose) |
52
- | Verify authentication | Use Auth.js, Clerk, NextAuth, Passport |
53
- | Read cookies | Use your framework's cookie API |
54
- | Manage sessions | Use express-session, iron-session |
55
- | Make network requests | Fetch user data yourself |
56
- | Access databases | Query your DB to get user roles |
57
- | Store global state | Pass user context explicitly |
58
-
59
- ### Why?
60
-
61
- Authorization and authentication are **separate concerns**. Mixing them creates security vulnerabilities. This package focuses on **one job** and does it correctly.
62
-
63
- ---
64
-
65
- ## Security Guarantees
66
-
67
- | Guarantee | Implementation |
68
- | ------------------------ | -------------------------------------------------- |
69
- | ✅ **Deny by default** | Undefined permissions return `false` |
70
- | ✅ **Immutable configs** | Role definitions are frozen with `Object.freeze()` |
71
- | ✅ **Pure functions** | No side effects, no state mutations |
72
- | ✅ **No eval/regex** | Only strict string matching |
73
- | ✅ **Zero dependencies** | Core has zero runtime dependencies |
74
- | ✅ **TypeScript strict** | Full strict mode compilation |
75
- | ✅ **No global state** | All state is passed explicitly |
76
- | ✅ **No network calls** | Never makes HTTP requests |
77
- | ✅ **No file system** | Never reads or writes files |
78
-
79
- ---
80
-
81
- ## Installation
24
+ ## 📦 Installation
82
25
 
83
26
  ```bash
84
27
  npm install secure-role-guard
@@ -88,275 +31,122 @@ pnpm add secure-role-guard
88
31
  yarn add secure-role-guard
89
32
  ```
90
33
 
91
- **Peer Dependencies:**
34
+ ---
92
35
 
93
- - React ≥16.8.0 (optional, only needed for React features)
36
+ ## 📖 Usage Guide
37
+
38
+ Choose your setup:
39
+
40
+ | I want to use in... | Jump to |
41
+ | --------------------------- | ------------------------------- |
42
+ | **React/Next.js only** | [Frontend Only](#frontend-only) |
43
+ | **Express/Node.js only** | [Backend Only](#backend-only) |
44
+ | **Both Frontend + Backend** | [Full Stack](#full-stack) |
94
45
 
95
46
  ---
96
47
 
97
- ## Quick Start
48
+ ## Frontend Only
98
49
 
99
- ### 1. Define Your Roles
50
+ ### Step 1: Define Roles
100
51
 
101
52
  ```typescript
102
- // roles.ts
53
+ // lib/roles.ts
103
54
  import { defineRoles } from "secure-role-guard";
104
55
 
105
56
  export const roleRegistry = defineRoles({
106
- superadmin: ["*"], // Full access
107
- admin: ["user.read", "user.update", "user.delete", "report.view"],
108
- manager: ["user.read", "report.*"], // Namespace wildcard
109
- support: ["ticket.read", "ticket.reply"],
57
+ admin: ["user.create", "user.read", "user.update", "user.delete"],
58
+ manager: ["user.read", "user.update"],
110
59
  viewer: ["user.read"],
111
60
  });
112
61
  ```
113
62
 
114
- ### 2. Check Permissions (Core - No React)
115
-
116
- ```typescript
117
- import { canUser } from "secure-role-guard";
118
- import { roleRegistry } from "./roles";
119
-
120
- // Your user context (from your auth system)
121
- const user = {
122
- userId: "user-123",
123
- roles: ["admin"],
124
- permissions: ["custom.feature"], // Direct permissions
125
- };
126
-
127
- // Simple checks
128
- canUser(user, "user.update", roleRegistry); // true
129
- canUser(user, "user.delete", roleRegistry); // true
130
- canUser(user, "billing.access", roleRegistry); // false (deny by default)
131
- ```
132
-
133
- ### 3. React Integration
63
+ ### Step 2: Setup Provider
134
64
 
135
65
  ```tsx
136
- import { PermissionProvider, Can, useCan } from "secure-role-guard";
137
- import { roleRegistry } from "./roles";
66
+ // app/providers.tsx (Next.js App Router)
67
+ // or src/App.tsx (Vite/CRA)
68
+ "use client";
138
69
 
139
- // Wrap your app with the provider
140
- function App() {
141
- const user = useAuth(); // YOUR auth hook (not from this package)
70
+ import { PermissionProvider } from "secure-role-guard/react";
71
+ import { roleRegistry } from "@/lib/roles";
142
72
 
73
+ export function Providers({ children, user }) {
74
+ // user = { roles: ['admin'], permissions: [] } from your auth
143
75
  return (
144
76
  <PermissionProvider user={user} registry={roleRegistry}>
145
- <Dashboard />
77
+ {children}
146
78
  </PermissionProvider>
147
79
  );
148
80
  }
81
+ ```
82
+
83
+ ### Step 3: Use in Components
84
+
85
+ ```tsx
86
+ import { Can, useCan } from "secure-role-guard/react";
149
87
 
150
- // Use the Can component for declarative rendering
151
88
  function Dashboard() {
89
+ const canDelete = useCan("user.delete");
90
+
152
91
  return (
153
92
  <div>
93
+ {/* Method 1: Component */}
94
+ <Can permission="user.create">
95
+ <button>Add User</button>
96
+ </Can>
97
+
154
98
  <Can permission="user.update">
155
- <EditUserButton />
99
+ <button>Edit User</button>
156
100
  </Can>
157
101
 
158
- <Can permission="admin.access" fallback={<UpgradePrompt />}>
102
+ {/* Method 2: Hook */}
103
+ {canDelete && <button>Delete User</button>}
104
+
105
+ {/* With fallback */}
106
+ <Can permission="admin.access" fallback={<p>Access Denied</p>}>
159
107
  <AdminPanel />
160
108
  </Can>
161
109
 
162
- <Can permissions={["report.view", "report.export"]} anyOf>
163
- <ReportSection />
110
+ {/* Multiple permissions (ANY) */}
111
+ <Can permissions={["user.update", "user.delete"]} anyOf>
112
+ <UserActions />
164
113
  </Can>
165
114
  </div>
166
115
  );
167
116
  }
168
-
169
- // Or use hooks for programmatic checks
170
- function UserActions() {
171
- const canEdit = useCan("user.update");
172
- const canDelete = useCan("user.delete");
173
-
174
- return (
175
- <div>
176
- {canEdit && <button>Edit</button>}
177
- {canDelete && <button>Delete</button>}
178
- </div>
179
- );
180
- }
181
117
  ```
182
118
 
183
- ---
184
-
185
- ## API Reference
119
+ **That's it for frontend!** ✅
186
120
 
187
- ### Core Functions
121
+ ---
188
122
 
189
- #### `defineRoles(definitions)`
123
+ ## Backend Only
190
124
 
191
- Creates an immutable role registry.
125
+ ### Step 1: Define Roles
192
126
 
193
127
  ```typescript
194
- const registry = defineRoles({
195
- admin: ["user.read", "user.update"],
128
+ // lib/roles.ts
129
+ import { defineRoles } from "secure-role-guard/core";
130
+
131
+ export const roleRegistry = defineRoles({
132
+ admin: ["user.create", "user.read", "user.update", "user.delete"],
133
+ manager: ["user.read", "user.update"],
196
134
  viewer: ["user.read"],
197
135
  });
198
136
  ```
199
137
 
200
- #### `canUser(user, permission, registry)`
201
-
202
- Checks if a user has a specific permission. Returns `boolean`.
203
-
204
- ```typescript
205
- const allowed = canUser(user, "user.update", registry);
206
- ```
207
-
208
- #### `canUserAll(user, permissions, registry)`
209
-
210
- Checks if a user has ALL specified permissions.
211
-
212
- ```typescript
213
- const allowed = canUserAll(user, ["user.read", "user.update"], registry);
214
- ```
215
-
216
- #### `canUserAny(user, permissions, registry)`
217
-
218
- Checks if a user has ANY of the specified permissions.
219
-
220
- ```typescript
221
- const allowed = canUserAny(
222
- user,
223
- ["admin.access", "moderator.access"],
224
- registry
225
- );
226
- ```
227
-
228
- ### React Components
229
-
230
- #### `<PermissionProvider>`
231
-
232
- Provides permission context to child components.
233
-
234
- ```tsx
235
- <PermissionProvider user={user} registry={registry}>
236
- {children}
237
- </PermissionProvider>
238
- ```
239
-
240
- #### `<Can>`
241
-
242
- Conditionally renders children based on permissions.
243
-
244
- | Prop | Type | Description |
245
- | ------------- | ----------- | ------------------------------------- |
246
- | `permission` | `string` | Single permission to check |
247
- | `permissions` | `string[]` | Multiple permissions to check |
248
- | `anyOf` | `boolean` | If true, ANY permission grants access |
249
- | `fallback` | `ReactNode` | Content to show if denied |
250
- | `children` | `ReactNode` | Content to show if allowed |
251
-
252
- #### `<Cannot>`
253
-
254
- Inverse of `<Can>` - renders when permission is NOT granted.
255
-
256
- ### React Hooks
257
-
258
- | Hook | Returns | Description |
259
- | ------------------------ | ------------------------ | ----------------------- |
260
- | `useCan(permission)` | `boolean` | Check single permission |
261
- | `useCanAll(permissions)` | `boolean` | Check ALL permissions |
262
- | `useCanAny(permissions)` | `boolean` | Check ANY permission |
263
- | `usePermissions()` | `PermissionContextValue` | Full context access |
264
- | `useUser()` | `UserContext \| null` | Current user |
265
-
266
- ### User Context Shape
267
-
268
- ```typescript
269
- type UserContext = {
270
- userId?: string; // Optional user identifier
271
- roles?: string[]; // Array of role names
272
- permissions?: string[]; // Direct permissions (bypass roles)
273
- meta?: Record<string, unknown>; // Custom metadata (tenant, org, etc.)
274
- };
275
- ```
276
-
277
- ### Wildcard Permissions
278
-
279
- | Pattern | Grants Access To |
280
- | ---------------- | ------------------------------------------------ |
281
- | `*` | Everything |
282
- | `user.*` | `user.read`, `user.update`, `user.delete`, etc. |
283
- | `report.admin.*` | `report.admin.view`, `report.admin.export`, etc. |
284
-
285
- ---
286
-
287
- ## Real-World Examples
288
-
289
- ### Example 1: Next.js App with Express Backend
290
-
291
- **Frontend (Next.js App Router):**
292
-
293
- ```tsx
294
- // app/providers.tsx
295
- "use client";
296
-
297
- import { PermissionProvider } from "secure-role-guard/react";
298
- import { roleRegistry } from "@/lib/roles";
299
-
300
- export function Providers({
301
- children,
302
- user,
303
- }: {
304
- children: React.ReactNode;
305
- user: UserContext;
306
- }) {
307
- return (
308
- <PermissionProvider user={user} registry={roleRegistry}>
309
- {children}
310
- </PermissionProvider>
311
- );
312
- }
313
-
314
- // app/layout.tsx
315
- import { Providers } from "./providers";
316
- import { getUser } from "@/lib/auth"; // YOUR auth function
317
-
318
- export default async function RootLayout({ children }) {
319
- const user = await getUser(); // Fetch from session/JWT
320
-
321
- return (
322
- <html>
323
- <body>
324
- <Providers user={user}>{children}</Providers>
325
- </body>
326
- </html>
327
- );
328
- }
329
-
330
- // app/admin/page.tsx
331
- import { Can } from "secure-role-guard/react";
332
-
333
- export default function AdminPage() {
334
- return (
335
- <Can permission="admin.access" fallback={<p>Access Denied</p>}>
336
- <h1>Admin Dashboard</h1>
337
- </Can>
338
- );
339
- }
340
- ```
341
-
342
- **Backend (Express.js):**
138
+ ### Step 2: Use in Express
343
139
 
344
140
  ```typescript
345
141
  // server.ts
346
142
  import express from "express";
347
- import { defineRoles } from "secure-role-guard/core";
348
143
  import { requirePermission } from "secure-role-guard/adapters/express";
144
+ import { roleRegistry } from "./lib/roles";
349
145
 
350
146
  const app = express();
351
147
 
352
- // Define roles (same as frontend)
353
- const roleRegistry = defineRoles({
354
- admin: ["user.read", "user.update", "user.delete"],
355
- viewer: ["user.read"],
356
- });
357
-
358
- // YOUR auth middleware (not from this package)
359
- app.use(authMiddleware); // Sets req.user
148
+ // Your auth middleware (sets req.user)
149
+ app.use(yourAuthMiddleware);
360
150
 
361
151
  // Protected routes
362
152
  app.get(
@@ -367,11 +157,11 @@ app.get(
367
157
  }
368
158
  );
369
159
 
370
- app.put(
371
- "/api/users/:id",
372
- requirePermission("user.update", roleRegistry),
160
+ app.post(
161
+ "/api/users",
162
+ requirePermission("user.create", roleRegistry),
373
163
  (req, res) => {
374
- res.json({ success: true });
164
+ res.json({ created: true });
375
165
  }
376
166
  );
377
167
 
@@ -382,1048 +172,157 @@ app.delete(
382
172
  res.json({ deleted: true });
383
173
  }
384
174
  );
385
-
386
- app.listen(3000);
387
- ```
388
-
389
- ---
390
-
391
- ### Example 2: React-Only App (Vite/CRA)
392
-
393
- ```tsx
394
- // src/roles.ts
395
- import { defineRoles } from "secure-role-guard";
396
-
397
- export const roleRegistry = defineRoles({
398
- admin: ["*"],
399
- editor: ["post.read", "post.create", "post.update"],
400
- viewer: ["post.read"],
401
- });
402
-
403
- // src/App.tsx
404
- import { PermissionProvider } from "secure-role-guard";
405
- import { roleRegistry } from "./roles";
406
- import { useAuth } from "./auth"; // YOUR auth hook
407
-
408
- function App() {
409
- const { user, isLoading } = useAuth();
410
-
411
- if (isLoading) return <div>Loading...</div>;
412
-
413
- return (
414
- <PermissionProvider user={user} registry={roleRegistry}>
415
- <Router>
416
- <Routes>
417
- <Route path="/" element={<Home />} />
418
- <Route path="/posts" element={<PostList />} />
419
- <Route path="/admin" element={<AdminRoute />} />
420
- </Routes>
421
- </Router>
422
- </PermissionProvider>
423
- );
424
- }
425
-
426
- // src/components/AdminRoute.tsx
427
- import { useCan } from "secure-role-guard";
428
- import { Navigate } from "react-router-dom";
429
-
430
- function AdminRoute() {
431
- const canAccess = useCan("admin.access");
432
-
433
- if (!canAccess) {
434
- return <Navigate to="/" replace />;
435
- }
436
-
437
- return <AdminPanel />;
438
- }
439
-
440
- // src/components/PostActions.tsx
441
- import { Can, Cannot } from "secure-role-guard";
442
-
443
- function PostActions({ postId }: { postId: string }) {
444
- return (
445
- <div>
446
- <Can permission="post.update">
447
- <button onClick={() => editPost(postId)}>Edit</button>
448
- </Can>
449
-
450
- <Can permission="post.delete">
451
- <button onClick={() => deletePost(postId)}>Delete</button>
452
- </Can>
453
-
454
- <Cannot permission="post.update">
455
- <span>View Only</span>
456
- </Cannot>
457
- </div>
458
- );
459
- }
460
175
  ```
461
176
 
462
- ---
463
-
464
- ### Example 3: Astro with React
177
+ ### Manual Check (Without Middleware)
465
178
 
466
179
  ```typescript
467
- // src/lib/roles.ts
468
- import { defineRoles } from 'secure-role-guard';
469
-
470
- export const roleRegistry = defineRoles({
471
- admin: ['page.edit', 'page.publish', 'settings.manage'],
472
- editor: ['page.edit'],
473
- viewer: [],
474
- });
475
-
476
- // src/components/AdminPanel.tsx (React component)
477
- import { PermissionProvider, Can, useCan } from 'secure-role-guard';
478
- import { roleRegistry } from '../lib/roles';
479
-
480
- interface Props {
481
- user: { roles: string[] } | null;
482
- }
483
-
484
- export default function AdminPanel({ user }: Props) {
485
- return (
486
- <PermissionProvider user={user} registry={roleRegistry}>
487
- <div className="admin-panel">
488
- <Can permission="page.edit">
489
- <PageEditor />
490
- </Can>
491
-
492
- <Can permission="settings.manage">
493
- <SettingsPanel />
494
- </Can>
495
-
496
- <Can permission="page.publish" fallback={<p>Publishing not available</p>}>
497
- <PublishButton />
498
- </Can>
499
- </div>
500
- </PermissionProvider>
501
- );
502
- }
503
-
504
- // src/pages/admin.astro
505
- ---
506
- import AdminPanel from '../components/AdminPanel';
507
- import { getUser } from '../lib/auth';
508
-
509
- const user = await getUser(Astro.request);
510
- ---
511
-
512
- <AdminPanel client:load user={user} />
513
- ```
514
-
515
- ---
516
-
517
- ### Example 4: Next.js API Routes (App Router)
518
-
519
- ```typescript
520
- // app/api/users/route.ts
521
- import { NextRequest, NextResponse } from "next/server";
522
- import { defineRoles, canUser } from "secure-role-guard/core";
523
- import { withPermission } from "secure-role-guard/adapters/nextjs";
524
- import { getUser } from "@/lib/auth";
525
-
526
- const roleRegistry = defineRoles({
527
- admin: ["user.read", "user.update", "user.delete"],
528
- viewer: ["user.read"],
529
- });
530
-
531
- // Option 1: Manual check
532
- export async function GET(request: NextRequest) {
533
- const user = await getUser(request);
534
-
535
- if (!canUser(user, "user.read", roleRegistry)) {
536
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
537
- }
538
-
539
- const users = await fetchUsers();
540
- return NextResponse.json(users);
541
- }
180
+ import { canUser } from "secure-role-guard/core";
542
181
 
543
- // Option 2: Using wrapper
544
- export const PUT = withPermission(
545
- "user.update",
546
- roleRegistry,
547
- { getUser: async (req) => getUser(req) },
548
- async (request, user) => {
549
- const body = await request.json();
550
- const updated = await updateUser(body);
551
- return NextResponse.json(updated);
182
+ app.put("/api/users/:id", (req, res) => {
183
+ if (!canUser(req.user, "user.update", roleRegistry)) {
184
+ return res.status(403).json({ error: "Forbidden" });
552
185
  }
553
- );
554
- ```
555
-
556
- ---
557
-
558
- ### Example 5: Multi-Tenant SaaS
559
-
560
- ```typescript
561
- import { defineRoles, canUser } from "secure-role-guard";
562
-
563
- // Define roles for your multi-tenant application
564
- const roleRegistry = defineRoles({
565
- org_owner: ["*"],
566
- org_admin: ["user.*", "billing.view", "settings.update"],
567
- org_member: ["user.read", "project.*"],
568
- org_viewer: ["user.read", "project.read"],
186
+ // ... your logic
569
187
  });
570
-
571
- // User context with tenant metadata
572
- const currentUser = {
573
- userId: "usr_abc123",
574
- roles: ["org_admin"],
575
- permissions: ["beta.feature"], // Direct permission for beta access
576
- meta: {
577
- tenantId: "tenant_xyz",
578
- orgId: "org_456",
579
- plan: "enterprise",
580
- },
581
- };
582
-
583
- // Authorization check
584
- if (canUser(currentUser, "billing.view", roleRegistry)) {
585
- // Show billing dashboard
586
- }
587
-
588
- // Access tenant metadata for additional business logic
589
- const tenantId = currentUser.meta?.tenantId;
590
- if (tenantId) {
591
- // Filter data by tenant
592
- }
593
188
  ```
594
189
 
595
- ---
596
-
597
- ## Framework Compatibility
598
-
599
- | Framework | Status | Import |
600
- | ---------------------- | ---------------- | ------------------------------------ |
601
- | Next.js (App Router) | ✅ Full support | `secure-role-guard` |
602
- | Next.js (Pages Router) | ✅ Full support | `secure-role-guard` |
603
- | Remix | ✅ Full support | `secure-role-guard` |
604
- | Gatsby | ✅ Full support | `secure-role-guard` |
605
- | Astro (React) | ✅ Full support | `secure-role-guard` |
606
- | Vite + React | ✅ Full support | `secure-role-guard` |
607
- | Create React App | ✅ Full support | `secure-role-guard` |
608
- | Express.js | ✅ Full support | `secure-role-guard/adapters/express` |
609
- | Fastify | 🔧 Adapter-ready | Use core directly |
610
- | Node HTTP | ✅ Full support | `secure-role-guard/core` |
611
-
612
- ---
613
-
614
- ## Fixed Roles vs Dynamic Roles
615
-
616
- This package supports both **Fixed (Hardcoded) Roles** and **Dynamic (Database-driven) Roles**. Choose the approach that fits your application.
617
-
618
- ### Architecture Overview
619
-
620
- ```
621
- ┌─────────────────────────────────────────────────────────────────────────┐
622
- │ YOUR APPLICATION │
623
- ├─────────────────────────────────────────────────────────────────────────┤
624
- │ │
625
- │ OPTION A: Fixed Roles OPTION B: Dynamic Roles │
626
- │ ┌───────────────────┐ ┌───────────────────┐ │
627
- │ │ roles.ts file │ │ Database │ │
628
- │ │ (hardcoded) │ │ (MongoDB/PG/SQL) │ │
629
- │ └─────────┬─────────┘ └─────────┬─────────┘ │
630
- │ │ │ │
631
- │ ▼ ▼ │
632
- │ ┌───────────────────┐ ┌───────────────────┐ │
633
- │ │ defineRoles() │ │ loadRolesFromDB()│ │
634
- │ │ (at build time) │ │ (at runtime) │ │
635
- │ └─────────┬─────────┘ └─────────┬─────────┘ │
636
- │ │ │ │
637
- │ └──────────────┬───────────────────┘ │
638
- │ ▼ │
639
- │ ┌───────────────────────────────────┐ │
640
- │ │ secure-role-guard │ │
641
- │ │ (same API for both approaches) │ │
642
- │ └───────────────────────────────────┘ │
643
- │ │
644
- └─────────────────────────────────────────────────────────────────────────┘
645
- ```
190
+ **That's it for backend!** ✅
646
191
 
647
192
  ---
648
193
 
649
- ### Approach 1: Fixed Roles (Hardcoded)
650
-
651
- Best for applications with **predefined, unchanging roles**.
194
+ ## Full Stack
652
195
 
653
- #### When to Use Fixed Roles
654
-
655
- - ✅ Small to medium applications
656
- - ✅ Roles rarely change
657
- - ✅ Simple admin/user/viewer hierarchy
658
- - ✅ You want faster startup (no DB query needed)
659
-
660
- #### Frontend Example (React/Next.js)
196
+ Use **same role definitions** for both:
661
197
 
662
198
  ```typescript
663
- // lib/roles.ts - Define roles at build time
199
+ // shared/roles.ts (shared between frontend & backend)
664
200
  import { defineRoles } from "secure-role-guard";
665
201
 
666
202
  export const roleRegistry = defineRoles({
667
- superadmin: ["*"], // Full access
668
- admin: ["user.read", "user.create", "user.update", "user.delete", "report.*"],
669
- manager: ["user.read", "user.update", "report.view"],
670
- support: ["ticket.read", "ticket.reply", "user.read"],
671
- viewer: ["user.read", "report.view"],
672
- });
673
-
674
- // -------------------------------------------------------
675
- // app/providers.tsx - Setup Provider
676
- ("use client");
677
-
678
- import { PermissionProvider } from "secure-role-guard/react";
679
- import { roleRegistry } from "@/lib/roles";
680
-
681
- interface User {
682
- id: string;
683
- roles: string[];
684
- permissions?: string[];
685
- }
686
-
687
- export function AuthProvider({
688
- children,
689
- user,
690
- }: {
691
- children: React.ReactNode;
692
- user: User | null;
693
- }) {
694
- return (
695
- <PermissionProvider user={user} registry={roleRegistry}>
696
- {children}
697
- </PermissionProvider>
698
- );
699
- }
700
-
701
- // -------------------------------------------------------
702
- // components/Dashboard.tsx - Use Permissions
703
- import { Can, useCan } from "secure-role-guard/react";
704
-
705
- export function Dashboard() {
706
- const canManageUsers = useCan("user.update");
707
-
708
- return (
709
- <div>
710
- <h1>Dashboard</h1>
711
-
712
- {/* Declarative approach */}
713
- <Can permission="user.create">
714
- <button>Add New User</button>
715
- </Can>
716
-
717
- <Can permission="report.view">
718
- <ReportsSection />
719
- </Can>
720
-
721
- <Can permissions={["user.delete", "user.update"]} anyOf>
722
- <UserManagement />
723
- </Can>
724
-
725
- {/* Programmatic approach */}
726
- {canManageUsers && <EditUserButton />}
727
- </div>
728
- );
729
- }
730
- ```
731
-
732
- #### Backend Example (Express/Fastify)
733
-
734
- ```typescript
735
- // server.ts - Express with Fixed Roles
736
- import express from "express";
737
- import { defineRoles, canUser } from "secure-role-guard/core";
738
- import { requirePermission } from "secure-role-guard/adapters/express";
739
-
740
- const app = express();
741
-
742
- // Same role definitions as frontend
743
- const roleRegistry = defineRoles({
744
- superadmin: ["*"],
745
- admin: ["user.read", "user.create", "user.update", "user.delete"],
746
- manager: ["user.read", "user.update"],
203
+ admin: ["*"], // Full access
204
+ manager: ["user.read", "user.update", "report.*"],
205
+ support: ["ticket.read", "ticket.reply"],
747
206
  viewer: ["user.read"],
748
207
  });
749
-
750
- // YOUR auth middleware (this package doesn't do auth)
751
- app.use(yourAuthMiddleware); // Sets req.user
752
-
753
- // Protected routes with middleware
754
- app.get(
755
- "/api/users",
756
- requirePermission("user.read", roleRegistry),
757
- async (req, res) => {
758
- const users = await db.users.findAll();
759
- res.json(users);
760
- }
761
- );
762
-
763
- app.post(
764
- "/api/users",
765
- requirePermission("user.create", roleRegistry),
766
- async (req, res) => {
767
- const user = await db.users.create(req.body);
768
- res.json(user);
769
- }
770
- );
771
-
772
- // Manual permission check (for complex logic)
773
- app.put("/api/users/:id", async (req, res) => {
774
- const user = req.user;
775
-
776
- if (!canUser(user, "user.update", roleRegistry)) {
777
- return res.status(403).json({ error: "Forbidden" });
778
- }
779
-
780
- // Additional business logic
781
- const targetUser = await db.users.findById(req.params.id);
782
-
783
- // Example: Managers can only edit non-admin users
784
- if (
785
- targetUser.roles.includes("admin") &&
786
- !canUser(user, "admin.manage", roleRegistry)
787
- ) {
788
- return res.status(403).json({ error: "Cannot edit admin users" });
789
- }
790
-
791
- const updated = await db.users.update(req.params.id, req.body);
792
- res.json(updated);
793
- });
794
-
795
- app.listen(3000);
796
208
  ```
797
209
 
798
- ---
799
-
800
- ### Approach 2: Dynamic Roles (Database-Driven)
801
-
802
- Best for applications where **admin can create/modify roles at runtime**.
803
-
804
- #### When to Use Dynamic Roles
805
-
806
- - ✅ Enterprise SaaS applications
807
- - ✅ Admin should create custom roles (e.g., "HR Manager", "Finance Lead")
808
- - ✅ Roles change frequently
809
- - ✅ Multi-tenant with different roles per tenant
810
-
811
- #### Database Schema Examples
210
+ **Frontend:** Follow [Frontend Only](#frontend-only) steps
211
+ **Backend:** Follow [Backend Only](#backend-only) steps
812
212
 
813
- **MongoDB:**
814
-
815
- ```javascript
816
- // roles collection
817
- {
818
- _id: ObjectId("..."),
819
- name: "hr_manager",
820
- display_name: "HR Manager",
821
- permissions: ["employee.read", "employee.create", "employee.update", "leave.approve"],
822
- is_active: true,
823
- tenant_id: ObjectId("..."), // For multi-tenant
824
- created_at: ISODate("...")
825
- }
826
-
827
- // users collection
828
- {
829
- _id: ObjectId("..."),
830
- email: "john@example.com",
831
- roles: [ObjectId("role1"), ObjectId("role2")],
832
- direct_permissions: ["special.feature"], // User-specific permissions
833
- tenant_id: ObjectId("...")
834
- }
835
- ```
836
-
837
- **PostgreSQL:**
838
-
839
- ```sql
840
- -- roles table
841
- CREATE TABLE roles (
842
- id SERIAL PRIMARY KEY,
843
- name VARCHAR(50) UNIQUE NOT NULL,
844
- display_name VARCHAR(100),
845
- is_active BOOLEAN DEFAULT true,
846
- tenant_id INTEGER REFERENCES tenants(id),
847
- created_at TIMESTAMP DEFAULT NOW()
848
- );
849
-
850
- -- permissions table
851
- CREATE TABLE permissions (
852
- id SERIAL PRIMARY KEY,
853
- code VARCHAR(100) UNIQUE NOT NULL, -- e.g., 'user.read'
854
- description TEXT
855
- );
856
-
857
- -- role_permissions (many-to-many)
858
- CREATE TABLE role_permissions (
859
- role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
860
- permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE,
861
- PRIMARY KEY (role_id, permission_id)
862
- );
863
-
864
- -- user_roles (many-to-many)
865
- CREATE TABLE user_roles (
866
- user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
867
- role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
868
- PRIMARY KEY (user_id, role_id)
869
- );
870
-
871
- -- user_permissions (direct permissions, bypass roles)
872
- CREATE TABLE user_permissions (
873
- user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
874
- permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE,
875
- PRIMARY KEY (user_id, permission_id)
876
- );
877
- ```
878
-
879
- **MySQL:**
880
-
881
- ```sql
882
- -- Similar to PostgreSQL, with MySQL syntax
883
- CREATE TABLE roles (
884
- id INT AUTO_INCREMENT PRIMARY KEY,
885
- name VARCHAR(50) UNIQUE NOT NULL,
886
- display_name VARCHAR(100),
887
- is_active TINYINT(1) DEFAULT 1,
888
- tenant_id INT,
889
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
890
- );
891
-
892
- CREATE TABLE permissions (
893
- id INT AUTO_INCREMENT PRIMARY KEY,
894
- code VARCHAR(100) UNIQUE NOT NULL,
895
- description TEXT
896
- );
897
-
898
- CREATE TABLE role_permissions (
899
- role_id INT,
900
- permission_id INT,
901
- PRIMARY KEY (role_id, permission_id),
902
- FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
903
- FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
904
- );
905
- ```
906
-
907
- #### Backend: Loading Dynamic Roles
908
-
909
- ```typescript
910
- // lib/dynamic-roles.ts
911
- import { defineRoles, RoleRegistry } from "secure-role-guard/core";
912
-
913
- // Interface for database abstraction
914
- interface RoleFromDB {
915
- name: string;
916
- permissions: string[];
917
- }
918
-
919
- interface IRoleRepository {
920
- getAllActiveRoles(): Promise<RoleFromDB[]>;
921
- }
922
-
923
- // ============================================================
924
- // MongoDB Implementation
925
- // ============================================================
926
- class MongoRoleRepository implements IRoleRepository {
927
- async getAllActiveRoles(): Promise<RoleFromDB[]> {
928
- const roles = await RoleModel.find({ is_active: true })
929
- .populate("permissions")
930
- .lean();
931
-
932
- return roles.map((role) => ({
933
- name: role.name,
934
- permissions: role.permissions.map((p: any) => p.code),
935
- }));
936
- }
937
- }
938
-
939
- // ============================================================
940
- // PostgreSQL Implementation (using Prisma)
941
- // ============================================================
942
- class PostgresRoleRepository implements IRoleRepository {
943
- async getAllActiveRoles(): Promise<RoleFromDB[]> {
944
- const roles = await prisma.role.findMany({
945
- where: { is_active: true },
946
- include: {
947
- role_permissions: {
948
- include: { permission: true },
949
- },
950
- },
951
- });
952
-
953
- return roles.map((role) => ({
954
- name: role.name,
955
- permissions: role.role_permissions.map((rp) => rp.permission.code),
956
- }));
957
- }
958
- }
959
-
960
- // ============================================================
961
- // MySQL Implementation (using mysql2)
962
- // ============================================================
963
- class MySQLRoleRepository implements IRoleRepository {
964
- async getAllActiveRoles(): Promise<RoleFromDB[]> {
965
- const [rows] = await pool.query(`
966
- SELECT r.name, GROUP_CONCAT(p.code) as permissions
967
- FROM roles r
968
- LEFT JOIN role_permissions rp ON r.id = rp.role_id
969
- LEFT JOIN permissions p ON rp.permission_id = p.id
970
- WHERE r.is_active = 1
971
- GROUP BY r.id, r.name
972
- `);
973
-
974
- return (rows as any[]).map((row) => ({
975
- name: row.name,
976
- permissions: row.permissions ? row.permissions.split(",") : [],
977
- }));
978
- }
979
- }
980
-
981
- // ============================================================
982
- // Dynamic Role Registry Factory
983
- // ============================================================
984
- let cachedRegistry: RoleRegistry | null = null;
985
- let cacheExpiry = 0;
986
- const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
987
-
988
- export async function getDynamicRoleRegistry(
989
- repository: IRoleRepository
990
- ): Promise<RoleRegistry> {
991
- const now = Date.now();
992
-
993
- // Return cached if valid
994
- if (cachedRegistry && now < cacheExpiry) {
995
- return cachedRegistry;
996
- }
997
-
998
- // Fetch from database
999
- const rolesFromDB = await repository.getAllActiveRoles();
1000
-
1001
- // Transform to RoleDefinition format
1002
- const roleDefinition: Record<string, readonly string[]> = {};
1003
- for (const role of rolesFromDB) {
1004
- roleDefinition[role.name] = role.permissions;
1005
- }
1006
-
1007
- // Create registry
1008
- cachedRegistry = defineRoles(roleDefinition);
1009
- cacheExpiry = now + CACHE_TTL;
1010
-
1011
- return cachedRegistry;
1012
- }
1013
-
1014
- // Force refresh (call when admin updates roles)
1015
- export function invalidateRoleCache(): void {
1016
- cachedRegistry = null;
1017
- cacheExpiry = 0;
1018
- }
1019
- ```
1020
-
1021
- #### Backend: Using Dynamic Roles in Express
1022
-
1023
- ```typescript
1024
- // server.ts
1025
- import express from "express";
1026
- import { canUser } from "secure-role-guard/core";
1027
- import {
1028
- getDynamicRoleRegistry,
1029
- invalidateRoleCache,
1030
- } from "./lib/dynamic-roles";
1031
-
1032
- const app = express();
1033
- const roleRepository = new MongoRoleRepository(); // or PostgresRoleRepository
1034
-
1035
- // Middleware to attach registry to request
1036
- app.use(async (req, res, next) => {
1037
- try {
1038
- req.roleRegistry = await getDynamicRoleRegistry(roleRepository);
1039
- next();
1040
- } catch (error) {
1041
- console.error("Failed to load roles:", error);
1042
- res.status(500).json({ error: "Internal server error" });
1043
- }
1044
- });
1045
-
1046
- // Protected routes using dynamic roles
1047
- app.get("/api/employees", async (req, res) => {
1048
- if (!canUser(req.user, "employee.read", req.roleRegistry)) {
1049
- return res.status(403).json({ error: "Forbidden" });
1050
- }
1051
-
1052
- const employees = await db.employees.findAll();
1053
- res.json(employees);
1054
- });
1055
-
1056
- // Admin creates a new role
1057
- app.post("/api/admin/roles", async (req, res) => {
1058
- if (!canUser(req.user, "role.create", req.roleRegistry)) {
1059
- return res.status(403).json({ error: "Forbidden" });
1060
- }
1061
-
1062
- const { name, permissions } = req.body;
1063
-
1064
- // Save to database
1065
- await RoleModel.create({ name, permissions, is_active: true });
1066
-
1067
- // Invalidate cache so new role is available
1068
- invalidateRoleCache();
1069
-
1070
- res.json({ success: true });
1071
- });
1072
-
1073
- app.listen(3000);
1074
- ```
1075
-
1076
- #### Frontend: Using Dynamic Roles
1077
-
1078
- ```typescript
1079
- // lib/auth-context.tsx
1080
- "use client";
1081
-
1082
- import { createContext, useContext, useEffect, useState } from "react";
1083
- import {
1084
- PermissionProvider,
1085
- RoleRegistry,
1086
- defineRoles,
1087
- } from "secure-role-guard/react";
1088
-
1089
- interface User {
1090
- id: string;
1091
- email: string;
1092
- roles: string[];
1093
- permissions: string[];
1094
- }
1095
-
1096
- interface AuthContextValue {
1097
- user: User | null;
1098
- isLoading: boolean;
1099
- }
1100
-
1101
- const AuthContext = createContext<AuthContextValue>({
1102
- user: null,
1103
- isLoading: true,
1104
- });
1105
-
1106
- export function DynamicAuthProvider({
1107
- children,
1108
- }: {
1109
- children: React.ReactNode;
1110
- }) {
1111
- const [user, setUser] = useState<User | null>(null);
1112
- const [roleRegistry, setRoleRegistry] = useState<RoleRegistry | null>(null);
1113
- const [isLoading, setIsLoading] = useState(true);
1114
-
1115
- useEffect(() => {
1116
- async function loadUserAndRoles() {
1117
- try {
1118
- // Fetch current user
1119
- const userRes = await fetch("/api/auth/me");
1120
- const userData = await userRes.json();
1121
-
1122
- if (!userData.user) {
1123
- setIsLoading(false);
1124
- return;
1125
- }
1126
-
1127
- // Fetch dynamic roles from backend
1128
- const rolesRes = await fetch("/api/auth/roles");
1129
- const rolesData = await rolesRes.json();
1130
-
1131
- // Create registry from dynamic roles
1132
- // rolesData format: { admin: ['user.read', ...], manager: [...] }
1133
- const registry = defineRoles(rolesData.roles);
1134
-
1135
- setUser(userData.user);
1136
- setRoleRegistry(registry);
1137
- } catch (error) {
1138
- console.error("Failed to load auth:", error);
1139
- } finally {
1140
- setIsLoading(false);
1141
- }
1142
- }
1143
-
1144
- loadUserAndRoles();
1145
- }, []);
1146
-
1147
- if (isLoading) {
1148
- return <div>Loading...</div>;
1149
- }
1150
-
1151
- if (!roleRegistry) {
1152
- return <div>Failed to load permissions</div>;
1153
- }
1154
-
1155
- return (
1156
- <AuthContext.Provider value={{ user, isLoading }}>
1157
- <PermissionProvider user={user} registry={roleRegistry}>
1158
- {children}
1159
- </PermissionProvider>
1160
- </AuthContext.Provider>
1161
- );
1162
- }
1163
-
1164
- // -------------------------------------------------------
1165
- // API Route: Return roles for frontend
1166
- // app/api/auth/roles/route.ts
1167
-
1168
- import { NextResponse } from "next/server";
1169
-
1170
- export async function GET() {
1171
- // Fetch roles from database
1172
- const roles = await RoleModel.find({ is_active: true }).lean();
1173
-
1174
- // Transform to { roleName: permissions[] } format
1175
- const roleMap: Record<string, string[]> = {};
1176
- for (const role of roles) {
1177
- roleMap[role.name] = role.permissions;
1178
- }
1179
-
1180
- return NextResponse.json({ roles: roleMap });
1181
- }
1182
-
1183
- // -------------------------------------------------------
1184
- // Usage in Components (same as fixed roles!)
1185
-
1186
- import { Can, useCan } from "secure-role-guard/react";
1187
-
1188
- function EmployeeDashboard() {
1189
- const canApproveLeave = useCan("leave.approve");
1190
-
1191
- return (
1192
- <div>
1193
- <Can permission="employee.read">
1194
- <EmployeeList />
1195
- </Can>
1196
-
1197
- <Can permission="employee.create">
1198
- <AddEmployeeButton />
1199
- </Can>
1200
-
1201
- {canApproveLeave && <LeaveApprovalQueue />}
1202
- </div>
1203
- );
1204
- }
1205
- ```
213
+ > 💡 **Pro tip:** Keep roles in a shared package or copy to both projects.
1206
214
 
1207
215
  ---
1208
216
 
1209
- ### Comparison: Fixed vs Dynamic
1210
-
1211
- | Feature | Fixed Roles | Dynamic Roles |
1212
- | ----------------------- | --------------------- | ---------------------------- |
1213
- | **Setup Complexity** | Simple | More complex |
1214
- | **Runtime Performance** | Faster (no DB query) | Slight overhead (cached) |
1215
- | **Flexibility** | Limited | Full flexibility |
1216
- | **Admin Control** | Code changes required | UI-based role management |
1217
- | **Use Case** | Simple apps, MVPs | Enterprise, SaaS |
1218
- | **Role Changes** | Deploy required | Instant (cache invalidation) |
1219
-
1220
- ### Key Points
1221
-
1222
- 1. **Package is database-agnostic** - You fetch data, we check permissions
1223
- 2. **Same API for both approaches** - `canUser()`, `<Can>`, `useCan()` work identically
1224
- 3. **Frontend mirrors backend** - Keep role definitions in sync
1225
- 4. **Always validate on backend** - Frontend is for UX, backend is for security
1226
-
1227
- ---
1228
-
1229
- ## Common Mistakes to Avoid
1230
-
1231
- ### ❌ DON'T: Parse JWT in this package
217
+ ## 📚 API Reference
1232
218
 
1233
- ```typescript
1234
- // WRONG - This package doesn't handle authentication
1235
- import { canUser } from "secure-role-guard";
219
+ ### Core Functions
1236
220
 
1237
- const token = req.headers.authorization;
1238
- const decoded = jwt.verify(token, secret); // NOT our job
1239
- ```
221
+ | Function | Description |
222
+ | ----------------------------------------- | ----------------------- |
223
+ | `defineRoles(roles)` | Create role registry |
224
+ | `canUser(user, permission, registry)` | Check single permission |
225
+ | `canUserAll(user, permissions, registry)` | Check ALL permissions |
226
+ | `canUserAny(user, permissions, registry)` | Check ANY permission |
1240
227
 
1241
- ### DO: Pass already-authenticated user context
228
+ ### React Components
1242
229
 
1243
- ```typescript
1244
- // CORRECT - You handle auth, we handle authorization
1245
- import { canUser } from "secure-role-guard";
230
+ | Component | Description |
231
+ | --------------------------- | ------------------- |
232
+ | `<PermissionProvider>` | Wrap your app |
233
+ | `<Can permission="...">` | Show if allowed |
234
+ | `<Cannot permission="...">` | Show if NOT allowed |
1246
235
 
1247
- // Your auth middleware already verified and decoded the token
1248
- const user = req.user; // Set by YOUR auth middleware
1249
- const allowed = canUser(user, "admin.access", roleRegistry);
1250
- ```
236
+ ### React Hooks
1251
237
 
1252
- ---
238
+ | Hook | Returns |
239
+ | ------------------------ | --------- |
240
+ | `useCan(permission)` | `boolean` |
241
+ | `useCanAll(permissions)` | `boolean` |
242
+ | `useCanAny(permissions)` | `boolean` |
1253
243
 
1254
- ### DON'T: Store user in global state
244
+ ### User Context Shape
1255
245
 
1256
246
  ```typescript
1257
- // WRONG - Global state is a security smell
1258
- let currentUser = null; // Anti-pattern
247
+ const user = {
248
+ userId: "user-123", // Optional
249
+ roles: ["admin", "manager"], // Role names
250
+ permissions: ["custom.perm"], // Direct permissions (bypass roles)
251
+ meta: { tenantId: "..." }, // Optional metadata
252
+ };
1259
253
  ```
1260
254
 
1261
- ### DO: Pass user context explicitly
255
+ ### Wildcard Permissions
1262
256
 
1263
- ```typescript
1264
- // CORRECT - Explicit is better than implicit
1265
- <PermissionProvider user={user} registry={roleRegistry}>
1266
- {children}
1267
- </PermissionProvider>
1268
- ```
257
+ | Pattern | Grants |
258
+ | ---------------- | ------------------------------------------------ |
259
+ | `*` | Everything |
260
+ | `user.*` | `user.read`, `user.update`, etc. |
261
+ | `report.admin.*` | `report.admin.view`, `report.admin.export`, etc. |
1269
262
 
1270
263
  ---
1271
264
 
1272
- ### DON'T: Use for authentication checks
1273
-
1274
- ```typescript
1275
- // WRONG - This is authentication, not authorization
1276
- if (canUser(user, "logged-in", registry)) {
1277
- // ...
1278
- }
1279
- ```
265
+ ## 🔄 Dynamic Roles (From Database)
1280
266
 
1281
- ### DO: Check actual permissions
267
+ If admin creates roles at runtime:
1282
268
 
1283
269
  ```typescript
1284
- // CORRECT - This is authorization
1285
- if (canUser(user, "user.update", registry)) {
1286
- // ...
1287
- }
1288
- ```
270
+ // Fetch roles from your database
271
+ const rolesFromDB = await fetchRolesFromDB();
1289
272
 
1290
- ---
1291
-
1292
- ### DON'T: Assume permissions exist
273
+ // Transform to: { roleName: ['permission1', 'permission2'] }
274
+ const roleDefinition = {};
275
+ rolesFromDB.forEach((role) => {
276
+ roleDefinition[role.name] = role.permissions;
277
+ });
1293
278
 
1294
- ```typescript
1295
- // WRONG - May throw or behave unexpectedly
1296
- if (user.permissions.includes("admin")) {
1297
- // ...
1298
- }
279
+ // Create registry
280
+ const registry = defineRoles(roleDefinition);
1299
281
  ```
1300
282
 
1301
- ### DO: Use the provided functions
1302
-
1303
- ```typescript
1304
- // CORRECT - Handles null/undefined safely (deny by default)
1305
- if (canUser(user, "admin.access", registry)) {
1306
- // ...
1307
- }
1308
- ```
283
+ Works with **any database**: MongoDB, PostgreSQL, MySQL, SQLite, etc.
1309
284
 
1310
285
  ---
1311
286
 
1312
- ## Backend Adapters
1313
-
1314
- ### Express Middleware
1315
-
1316
- ```typescript
1317
- import {
1318
- requirePermission,
1319
- requireAllPermissions,
1320
- requireAnyPermission,
1321
- } from "secure-role-guard/adapters/express";
1322
-
1323
- // Single permission
1324
- app.get("/api/users", requirePermission("user.read", registry), handler);
287
+ ## ⚠️ Important Notes
1325
288
 
1326
- // All permissions required
1327
- app.delete(
1328
- "/api/admin",
1329
- requireAllPermissions(["admin.access", "data.delete"], registry),
1330
- handler
1331
- );
289
+ ### This Package Does NOT:
1332
290
 
1333
- // Any permission
1334
- app.get(
1335
- "/api/reports",
1336
- requireAnyPermission(["report.view", "report.admin"], registry),
1337
- handler
1338
- );
1339
-
1340
- // Custom options
1341
- app.put(
1342
- "/api/settings",
1343
- requirePermission("settings.update", registry, {
1344
- statusCode: 401,
1345
- message: "Unauthorized",
1346
- getUser: (req) => req.session?.user,
1347
- }),
1348
- handler
1349
- );
1350
- ```
1351
-
1352
- ### Next.js Route Handlers
1353
-
1354
- ```typescript
1355
- import {
1356
- withPermission,
1357
- checkNextPermission,
1358
- } from "secure-role-guard/adapters/nextjs";
1359
-
1360
- // Using wrapper
1361
- export const POST = withPermission(
1362
- "post.create",
1363
- registry,
1364
- { getUser: async (req) => getUserFromSession(req) },
1365
- async (request, user) => {
1366
- return Response.json({ created: true });
1367
- }
1368
- );
291
+ - Handle authentication (JWT, sessions, cookies)
292
+ - ❌ Make API/database calls
293
+ - ❌ Store global state
1369
294
 
1370
- // Manual check
1371
- export async function GET(request: NextRequest) {
1372
- const user = await getUser(request);
1373
- const result = checkNextPermission(user, "data.read", registry);
295
+ **You provide:** User with roles → **We check:** Permissions
1374
296
 
1375
- if (!result.allowed) {
1376
- return Response.json({ error: "Forbidden" }, { status: 403 });
1377
- }
297
+ ### Security
1378
298
 
1379
- return Response.json({ data: [] });
1380
- }
1381
- ```
299
+ - ✅ Deny by default (undefined = false)
300
+ - ✅ Zero dependencies in core
301
+ - ✅ Immutable configurations
302
+ - ✅ Pure functions (no side effects)
1382
303
 
1383
304
  ---
1384
305
 
1385
- ## TypeScript Support
306
+ ## 🔒 Backward Compatibility
1386
307
 
1387
- This package is written in TypeScript with strict mode enabled:
308
+ | Version | Meaning |
309
+ | ------------- | ------------------------------------------ |
310
+ | 1.0.x → 1.0.y | Bug fixes, safe to update |
311
+ | 1.x.0 → 1.y.0 | New features, no breaking changes |
312
+ | 1.x.x → 2.0.0 | Breaking changes, migration guide provided |
1388
313
 
1389
- ```typescript
1390
- // tsconfig.json (package configuration)
1391
- {
1392
- "compilerOptions": {
1393
- "strict": true,
1394
- "noImplicitAny": true,
1395
- "strictNullChecks": true,
1396
- "exactOptionalPropertyTypes": true
1397
- }
1398
- }
1399
- ```
1400
-
1401
- All types are exported:
1402
-
1403
- ```typescript
1404
- import type {
1405
- UserContext,
1406
- RoleDefinition,
1407
- RoleRegistry,
1408
- PermissionCheckResult,
1409
- } from "secure-role-guard";
1410
- ```
314
+ **Promise:** v1.x APIs will never break. Update with confidence.
1411
315
 
1412
316
  ---
1413
317
 
1414
- ## License
318
+ ## 📄 License
1415
319
 
1416
320
  MIT © [Sohel Rahaman](https://github.com/sohelrahaman)
1417
321
 
1418
322
  ---
1419
323
 
1420
- ## Security Note
1421
-
1422
- This package is designed to be **boring, predictable, and auditable**. It intentionally avoids:
1423
-
1424
- - Magic behavior
1425
- - Clever hacks
1426
- - Hidden side effects
1427
- - Runtime code generation
324
+ ## 🔗 Links
1428
325
 
1429
- If you find a security issue, please report it via [GitHub Issues](https://github.com/sohelrahaman/secure-role-guard/issues).
326
+ - [GitHub Repository](https://github.com/Sohel-Rahaman-Developer/secure-role-guard)
327
+ - [NPM Package](https://www.npmjs.com/package/secure-role-guard)
328
+ - [Report Issues](https://github.com/Sohel-Rahaman-Developer/secure-role-guard/issues)