secure-role-guard 1.0.0

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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +813 -0
  3. package/dist/adapters/express.d.mts +109 -0
  4. package/dist/adapters/express.d.ts +109 -0
  5. package/dist/adapters/express.js +122 -0
  6. package/dist/adapters/express.js.map +1 -0
  7. package/dist/adapters/express.mjs +118 -0
  8. package/dist/adapters/express.mjs.map +1 -0
  9. package/dist/adapters/index.d.mts +3 -0
  10. package/dist/adapters/index.d.ts +3 -0
  11. package/dist/adapters/index.js +181 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/index.mjs +171 -0
  14. package/dist/adapters/index.mjs.map +1 -0
  15. package/dist/adapters/nextjs.d.mts +140 -0
  16. package/dist/adapters/nextjs.d.ts +140 -0
  17. package/dist/adapters/nextjs.js +138 -0
  18. package/dist/adapters/nextjs.js.map +1 -0
  19. package/dist/adapters/nextjs.mjs +131 -0
  20. package/dist/adapters/nextjs.mjs.map +1 -0
  21. package/dist/core/index.d.mts +100 -0
  22. package/dist/core/index.d.ts +100 -0
  23. package/dist/core/index.js +132 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/index.mjs +125 -0
  26. package/dist/core/index.mjs.map +1 -0
  27. package/dist/index.d.mts +4 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +238 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/index.mjs +222 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/react/index.d.mts +237 -0
  34. package/dist/react/index.d.ts +237 -0
  35. package/dist/react/index.js +177 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/react/index.mjs +167 -0
  38. package/dist/react/index.mjs.map +1 -0
  39. package/dist/types-CSUpaGsY.d.mts +76 -0
  40. package/dist/types-CSUpaGsY.d.ts +76 -0
  41. package/package.json +99 -0
package/README.md ADDED
@@ -0,0 +1,813 @@
1
+ # secure-role-guard
2
+
3
+ > Zero-vulnerability, framework-agnostic RBAC authorization library for React and Node.js applications.
4
+
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
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](https://www.npmjs.com/package/secure-role-guard)
9
+
10
+ **Author:** Sohel Rahaman
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
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
+ - [Common Mistakes to Avoid](#common-mistakes-to-avoid)
25
+ - [License](#license)
26
+
27
+ ---
28
+
29
+ ## What This Package Does ✅
30
+
31
+ | Feature | Description |
32
+ | ----------------------------- | ------------------------------------------------------- |
33
+ | **Role-Based Access Control** | Define roles with granular permissions |
34
+ | **Pure Permission Checking** | Deterministic, side-effect-free authorization |
35
+ | **React Integration** | Provider, hooks, and components for any React framework |
36
+ | **Backend Adapters** | Optional Express and Next.js middleware |
37
+ | **Wildcard Support** | Grant access with `*` (all) or `namespace.*` patterns |
38
+ | **TypeScript First** | Full type safety with strict mode |
39
+ | **Zero Dependencies** | Core has zero runtime dependencies |
40
+ | **Framework Agnostic** | Works with Next.js, Remix, Gatsby, Astro, Vite, CRA |
41
+
42
+ ---
43
+
44
+ ## What This Package Does NOT Do ❌
45
+
46
+ > **CRITICAL:** This package handles **AUTHORIZATION** only, **NOT AUTHENTICATION**.
47
+
48
+ | This Package Does NOT | You Must Handle This |
49
+ | --------------------- | -------------------------------------- |
50
+ | Parse JWT tokens | Use a JWT library (jsonwebtoken, jose) |
51
+ | Verify authentication | Use Auth.js, Clerk, NextAuth, Passport |
52
+ | Read cookies | Use your framework's cookie API |
53
+ | Manage sessions | Use express-session, iron-session |
54
+ | Make network requests | Fetch user data yourself |
55
+ | Access databases | Query your DB to get user roles |
56
+ | Store global state | Pass user context explicitly |
57
+
58
+ ### Why?
59
+
60
+ Authorization and authentication are **separate concerns**. Mixing them creates security vulnerabilities. This package focuses on **one job** and does it correctly.
61
+
62
+ ---
63
+
64
+ ## Security Guarantees
65
+
66
+ | Guarantee | Implementation |
67
+ | ------------------------ | -------------------------------------------------- |
68
+ | ✅ **Deny by default** | Undefined permissions return `false` |
69
+ | ✅ **Immutable configs** | Role definitions are frozen with `Object.freeze()` |
70
+ | ✅ **Pure functions** | No side effects, no state mutations |
71
+ | ✅ **No eval/regex** | Only strict string matching |
72
+ | ✅ **Zero dependencies** | Core has zero runtime dependencies |
73
+ | ✅ **TypeScript strict** | Full strict mode compilation |
74
+ | ✅ **No global state** | All state is passed explicitly |
75
+ | ✅ **No network calls** | Never makes HTTP requests |
76
+ | ✅ **No file system** | Never reads or writes files |
77
+
78
+ ---
79
+
80
+ ## Installation
81
+
82
+ ```bash
83
+ npm install secure-role-guard
84
+ # or
85
+ pnpm add secure-role-guard
86
+ # or
87
+ yarn add secure-role-guard
88
+ ```
89
+
90
+ **Peer Dependencies:**
91
+
92
+ - React ≥16.8.0 (optional, only needed for React features)
93
+
94
+ ---
95
+
96
+ ## Quick Start
97
+
98
+ ### 1. Define Your Roles
99
+
100
+ ```typescript
101
+ // roles.ts
102
+ import { defineRoles } from "secure-role-guard";
103
+
104
+ export const roleRegistry = defineRoles({
105
+ superadmin: ["*"], // Full access
106
+ admin: ["user.read", "user.update", "user.delete", "report.view"],
107
+ manager: ["user.read", "report.*"], // Namespace wildcard
108
+ support: ["ticket.read", "ticket.reply"],
109
+ viewer: ["user.read"],
110
+ });
111
+ ```
112
+
113
+ ### 2. Check Permissions (Core - No React)
114
+
115
+ ```typescript
116
+ import { canUser } from "secure-role-guard";
117
+ import { roleRegistry } from "./roles";
118
+
119
+ // Your user context (from your auth system)
120
+ const user = {
121
+ userId: "user-123",
122
+ roles: ["admin"],
123
+ permissions: ["custom.feature"], // Direct permissions
124
+ };
125
+
126
+ // Simple checks
127
+ canUser(user, "user.update", roleRegistry); // true
128
+ canUser(user, "user.delete", roleRegistry); // true
129
+ canUser(user, "billing.access", roleRegistry); // false (deny by default)
130
+ ```
131
+
132
+ ### 3. React Integration
133
+
134
+ ```tsx
135
+ import { PermissionProvider, Can, useCan } from "secure-role-guard";
136
+ import { roleRegistry } from "./roles";
137
+
138
+ // Wrap your app with the provider
139
+ function App() {
140
+ const user = useAuth(); // YOUR auth hook (not from this package)
141
+
142
+ return (
143
+ <PermissionProvider user={user} registry={roleRegistry}>
144
+ <Dashboard />
145
+ </PermissionProvider>
146
+ );
147
+ }
148
+
149
+ // Use the Can component for declarative rendering
150
+ function Dashboard() {
151
+ return (
152
+ <div>
153
+ <Can permission="user.update">
154
+ <EditUserButton />
155
+ </Can>
156
+
157
+ <Can permission="admin.access" fallback={<UpgradePrompt />}>
158
+ <AdminPanel />
159
+ </Can>
160
+
161
+ <Can permissions={["report.view", "report.export"]} anyOf>
162
+ <ReportSection />
163
+ </Can>
164
+ </div>
165
+ );
166
+ }
167
+
168
+ // Or use hooks for programmatic checks
169
+ function UserActions() {
170
+ const canEdit = useCan("user.update");
171
+ const canDelete = useCan("user.delete");
172
+
173
+ return (
174
+ <div>
175
+ {canEdit && <button>Edit</button>}
176
+ {canDelete && <button>Delete</button>}
177
+ </div>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## API Reference
185
+
186
+ ### Core Functions
187
+
188
+ #### `defineRoles(definitions)`
189
+
190
+ Creates an immutable role registry.
191
+
192
+ ```typescript
193
+ const registry = defineRoles({
194
+ admin: ["user.read", "user.update"],
195
+ viewer: ["user.read"],
196
+ });
197
+ ```
198
+
199
+ #### `canUser(user, permission, registry)`
200
+
201
+ Checks if a user has a specific permission. Returns `boolean`.
202
+
203
+ ```typescript
204
+ const allowed = canUser(user, "user.update", registry);
205
+ ```
206
+
207
+ #### `canUserAll(user, permissions, registry)`
208
+
209
+ Checks if a user has ALL specified permissions.
210
+
211
+ ```typescript
212
+ const allowed = canUserAll(user, ["user.read", "user.update"], registry);
213
+ ```
214
+
215
+ #### `canUserAny(user, permissions, registry)`
216
+
217
+ Checks if a user has ANY of the specified permissions.
218
+
219
+ ```typescript
220
+ const allowed = canUserAny(
221
+ user,
222
+ ["admin.access", "moderator.access"],
223
+ registry
224
+ );
225
+ ```
226
+
227
+ ### React Components
228
+
229
+ #### `<PermissionProvider>`
230
+
231
+ Provides permission context to child components.
232
+
233
+ ```tsx
234
+ <PermissionProvider user={user} registry={registry}>
235
+ {children}
236
+ </PermissionProvider>
237
+ ```
238
+
239
+ #### `<Can>`
240
+
241
+ Conditionally renders children based on permissions.
242
+
243
+ | Prop | Type | Description |
244
+ | ------------- | ----------- | ------------------------------------- |
245
+ | `permission` | `string` | Single permission to check |
246
+ | `permissions` | `string[]` | Multiple permissions to check |
247
+ | `anyOf` | `boolean` | If true, ANY permission grants access |
248
+ | `fallback` | `ReactNode` | Content to show if denied |
249
+ | `children` | `ReactNode` | Content to show if allowed |
250
+
251
+ #### `<Cannot>`
252
+
253
+ Inverse of `<Can>` - renders when permission is NOT granted.
254
+
255
+ ### React Hooks
256
+
257
+ | Hook | Returns | Description |
258
+ | ------------------------ | ------------------------ | ----------------------- |
259
+ | `useCan(permission)` | `boolean` | Check single permission |
260
+ | `useCanAll(permissions)` | `boolean` | Check ALL permissions |
261
+ | `useCanAny(permissions)` | `boolean` | Check ANY permission |
262
+ | `usePermissions()` | `PermissionContextValue` | Full context access |
263
+ | `useUser()` | `UserContext \| null` | Current user |
264
+
265
+ ### User Context Shape
266
+
267
+ ```typescript
268
+ type UserContext = {
269
+ userId?: string; // Optional user identifier
270
+ roles?: string[]; // Array of role names
271
+ permissions?: string[]; // Direct permissions (bypass roles)
272
+ meta?: Record<string, unknown>; // Custom metadata (tenant, org, etc.)
273
+ };
274
+ ```
275
+
276
+ ### Wildcard Permissions
277
+
278
+ | Pattern | Grants Access To |
279
+ | ---------------- | ------------------------------------------------ |
280
+ | `*` | Everything |
281
+ | `user.*` | `user.read`, `user.update`, `user.delete`, etc. |
282
+ | `report.admin.*` | `report.admin.view`, `report.admin.export`, etc. |
283
+
284
+ ---
285
+
286
+ ## Real-World Examples
287
+
288
+ ### Example 1: Next.js App with Express Backend
289
+
290
+ **Frontend (Next.js App Router):**
291
+
292
+ ```tsx
293
+ // app/providers.tsx
294
+ "use client";
295
+
296
+ import { PermissionProvider } from "secure-role-guard/react";
297
+ import { roleRegistry } from "@/lib/roles";
298
+
299
+ export function Providers({
300
+ children,
301
+ user,
302
+ }: {
303
+ children: React.ReactNode;
304
+ user: UserContext;
305
+ }) {
306
+ return (
307
+ <PermissionProvider user={user} registry={roleRegistry}>
308
+ {children}
309
+ </PermissionProvider>
310
+ );
311
+ }
312
+
313
+ // app/layout.tsx
314
+ import { Providers } from "./providers";
315
+ import { getUser } from "@/lib/auth"; // YOUR auth function
316
+
317
+ export default async function RootLayout({ children }) {
318
+ const user = await getUser(); // Fetch from session/JWT
319
+
320
+ return (
321
+ <html>
322
+ <body>
323
+ <Providers user={user}>{children}</Providers>
324
+ </body>
325
+ </html>
326
+ );
327
+ }
328
+
329
+ // app/admin/page.tsx
330
+ import { Can } from "secure-role-guard/react";
331
+
332
+ export default function AdminPage() {
333
+ return (
334
+ <Can permission="admin.access" fallback={<p>Access Denied</p>}>
335
+ <h1>Admin Dashboard</h1>
336
+ </Can>
337
+ );
338
+ }
339
+ ```
340
+
341
+ **Backend (Express.js):**
342
+
343
+ ```typescript
344
+ // server.ts
345
+ import express from "express";
346
+ import { defineRoles } from "secure-role-guard/core";
347
+ import { requirePermission } from "secure-role-guard/adapters/express";
348
+
349
+ const app = express();
350
+
351
+ // Define roles (same as frontend)
352
+ const roleRegistry = defineRoles({
353
+ admin: ["user.read", "user.update", "user.delete"],
354
+ viewer: ["user.read"],
355
+ });
356
+
357
+ // YOUR auth middleware (not from this package)
358
+ app.use(authMiddleware); // Sets req.user
359
+
360
+ // Protected routes
361
+ app.get(
362
+ "/api/users",
363
+ requirePermission("user.read", roleRegistry),
364
+ (req, res) => {
365
+ res.json({ users: [] });
366
+ }
367
+ );
368
+
369
+ app.put(
370
+ "/api/users/:id",
371
+ requirePermission("user.update", roleRegistry),
372
+ (req, res) => {
373
+ res.json({ success: true });
374
+ }
375
+ );
376
+
377
+ app.delete(
378
+ "/api/users/:id",
379
+ requirePermission("user.delete", roleRegistry),
380
+ (req, res) => {
381
+ res.json({ deleted: true });
382
+ }
383
+ );
384
+
385
+ app.listen(3000);
386
+ ```
387
+
388
+ ---
389
+
390
+ ### Example 2: React-Only App (Vite/CRA)
391
+
392
+ ```tsx
393
+ // src/roles.ts
394
+ import { defineRoles } from "secure-role-guard";
395
+
396
+ export const roleRegistry = defineRoles({
397
+ admin: ["*"],
398
+ editor: ["post.read", "post.create", "post.update"],
399
+ viewer: ["post.read"],
400
+ });
401
+
402
+ // src/App.tsx
403
+ import { PermissionProvider } from "secure-role-guard";
404
+ import { roleRegistry } from "./roles";
405
+ import { useAuth } from "./auth"; // YOUR auth hook
406
+
407
+ function App() {
408
+ const { user, isLoading } = useAuth();
409
+
410
+ if (isLoading) return <div>Loading...</div>;
411
+
412
+ return (
413
+ <PermissionProvider user={user} registry={roleRegistry}>
414
+ <Router>
415
+ <Routes>
416
+ <Route path="/" element={<Home />} />
417
+ <Route path="/posts" element={<PostList />} />
418
+ <Route path="/admin" element={<AdminRoute />} />
419
+ </Routes>
420
+ </Router>
421
+ </PermissionProvider>
422
+ );
423
+ }
424
+
425
+ // src/components/AdminRoute.tsx
426
+ import { useCan } from "secure-role-guard";
427
+ import { Navigate } from "react-router-dom";
428
+
429
+ function AdminRoute() {
430
+ const canAccess = useCan("admin.access");
431
+
432
+ if (!canAccess) {
433
+ return <Navigate to="/" replace />;
434
+ }
435
+
436
+ return <AdminPanel />;
437
+ }
438
+
439
+ // src/components/PostActions.tsx
440
+ import { Can, Cannot } from "secure-role-guard";
441
+
442
+ function PostActions({ postId }: { postId: string }) {
443
+ return (
444
+ <div>
445
+ <Can permission="post.update">
446
+ <button onClick={() => editPost(postId)}>Edit</button>
447
+ </Can>
448
+
449
+ <Can permission="post.delete">
450
+ <button onClick={() => deletePost(postId)}>Delete</button>
451
+ </Can>
452
+
453
+ <Cannot permission="post.update">
454
+ <span>View Only</span>
455
+ </Cannot>
456
+ </div>
457
+ );
458
+ }
459
+ ```
460
+
461
+ ---
462
+
463
+ ### Example 3: Astro with React
464
+
465
+ ```typescript
466
+ // src/lib/roles.ts
467
+ import { defineRoles } from 'secure-role-guard';
468
+
469
+ export const roleRegistry = defineRoles({
470
+ admin: ['page.edit', 'page.publish', 'settings.manage'],
471
+ editor: ['page.edit'],
472
+ viewer: [],
473
+ });
474
+
475
+ // src/components/AdminPanel.tsx (React component)
476
+ import { PermissionProvider, Can, useCan } from 'secure-role-guard';
477
+ import { roleRegistry } from '../lib/roles';
478
+
479
+ interface Props {
480
+ user: { roles: string[] } | null;
481
+ }
482
+
483
+ export default function AdminPanel({ user }: Props) {
484
+ return (
485
+ <PermissionProvider user={user} registry={roleRegistry}>
486
+ <div className="admin-panel">
487
+ <Can permission="page.edit">
488
+ <PageEditor />
489
+ </Can>
490
+
491
+ <Can permission="settings.manage">
492
+ <SettingsPanel />
493
+ </Can>
494
+
495
+ <Can permission="page.publish" fallback={<p>Publishing not available</p>}>
496
+ <PublishButton />
497
+ </Can>
498
+ </div>
499
+ </PermissionProvider>
500
+ );
501
+ }
502
+
503
+ // src/pages/admin.astro
504
+ ---
505
+ import AdminPanel from '../components/AdminPanel';
506
+ import { getUser } from '../lib/auth';
507
+
508
+ const user = await getUser(Astro.request);
509
+ ---
510
+
511
+ <AdminPanel client:load user={user} />
512
+ ```
513
+
514
+ ---
515
+
516
+ ### Example 4: Next.js API Routes (App Router)
517
+
518
+ ```typescript
519
+ // app/api/users/route.ts
520
+ import { NextRequest, NextResponse } from "next/server";
521
+ import { defineRoles, canUser } from "secure-role-guard/core";
522
+ import { withPermission } from "secure-role-guard/adapters/nextjs";
523
+ import { getUser } from "@/lib/auth";
524
+
525
+ const roleRegistry = defineRoles({
526
+ admin: ["user.read", "user.update", "user.delete"],
527
+ viewer: ["user.read"],
528
+ });
529
+
530
+ // Option 1: Manual check
531
+ export async function GET(request: NextRequest) {
532
+ const user = await getUser(request);
533
+
534
+ if (!canUser(user, "user.read", roleRegistry)) {
535
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
536
+ }
537
+
538
+ const users = await fetchUsers();
539
+ return NextResponse.json(users);
540
+ }
541
+
542
+ // Option 2: Using wrapper
543
+ export const PUT = withPermission(
544
+ "user.update",
545
+ roleRegistry,
546
+ { getUser: async (req) => getUser(req) },
547
+ async (request, user) => {
548
+ const body = await request.json();
549
+ const updated = await updateUser(body);
550
+ return NextResponse.json(updated);
551
+ }
552
+ );
553
+ ```
554
+
555
+ ---
556
+
557
+ ### Example 5: Multi-Tenant SaaS
558
+
559
+ ```typescript
560
+ import { defineRoles, canUser } from "secure-role-guard";
561
+
562
+ // Define roles for your multi-tenant application
563
+ const roleRegistry = defineRoles({
564
+ org_owner: ["*"],
565
+ org_admin: ["user.*", "billing.view", "settings.update"],
566
+ org_member: ["user.read", "project.*"],
567
+ org_viewer: ["user.read", "project.read"],
568
+ });
569
+
570
+ // User context with tenant metadata
571
+ const currentUser = {
572
+ userId: "usr_abc123",
573
+ roles: ["org_admin"],
574
+ permissions: ["beta.feature"], // Direct permission for beta access
575
+ meta: {
576
+ tenantId: "tenant_xyz",
577
+ orgId: "org_456",
578
+ plan: "enterprise",
579
+ },
580
+ };
581
+
582
+ // Authorization check
583
+ if (canUser(currentUser, "billing.view", roleRegistry)) {
584
+ // Show billing dashboard
585
+ }
586
+
587
+ // Access tenant metadata for additional business logic
588
+ const tenantId = currentUser.meta?.tenantId;
589
+ if (tenantId) {
590
+ // Filter data by tenant
591
+ }
592
+ ```
593
+
594
+ ---
595
+
596
+ ## Framework Compatibility
597
+
598
+ | Framework | Status | Import |
599
+ | ---------------------- | ---------------- | ------------------------------------ |
600
+ | Next.js (App Router) | ✅ Full support | `secure-role-guard` |
601
+ | Next.js (Pages Router) | ✅ Full support | `secure-role-guard` |
602
+ | Remix | ✅ Full support | `secure-role-guard` |
603
+ | Gatsby | ✅ Full support | `secure-role-guard` |
604
+ | Astro (React) | ✅ Full support | `secure-role-guard` |
605
+ | Vite + React | ✅ Full support | `secure-role-guard` |
606
+ | Create React App | ✅ Full support | `secure-role-guard` |
607
+ | Express.js | ✅ Full support | `secure-role-guard/adapters/express` |
608
+ | Fastify | 🔧 Adapter-ready | Use core directly |
609
+ | Node HTTP | ✅ Full support | `secure-role-guard/core` |
610
+
611
+ ---
612
+
613
+ ## Common Mistakes to Avoid
614
+
615
+ ### ❌ DON'T: Parse JWT in this package
616
+
617
+ ```typescript
618
+ // WRONG - This package doesn't handle authentication
619
+ import { canUser } from "secure-role-guard";
620
+
621
+ const token = req.headers.authorization;
622
+ const decoded = jwt.verify(token, secret); // NOT our job
623
+ ```
624
+
625
+ ### ✅ DO: Pass already-authenticated user context
626
+
627
+ ```typescript
628
+ // CORRECT - You handle auth, we handle authorization
629
+ import { canUser } from "secure-role-guard";
630
+
631
+ // Your auth middleware already verified and decoded the token
632
+ const user = req.user; // Set by YOUR auth middleware
633
+ const allowed = canUser(user, "admin.access", roleRegistry);
634
+ ```
635
+
636
+ ---
637
+
638
+ ### ❌ DON'T: Store user in global state
639
+
640
+ ```typescript
641
+ // WRONG - Global state is a security smell
642
+ let currentUser = null; // Anti-pattern
643
+ ```
644
+
645
+ ### ✅ DO: Pass user context explicitly
646
+
647
+ ```typescript
648
+ // CORRECT - Explicit is better than implicit
649
+ <PermissionProvider user={user} registry={roleRegistry}>
650
+ {children}
651
+ </PermissionProvider>
652
+ ```
653
+
654
+ ---
655
+
656
+ ### ❌ DON'T: Use for authentication checks
657
+
658
+ ```typescript
659
+ // WRONG - This is authentication, not authorization
660
+ if (canUser(user, "logged-in", registry)) {
661
+ // ...
662
+ }
663
+ ```
664
+
665
+ ### ✅ DO: Check actual permissions
666
+
667
+ ```typescript
668
+ // CORRECT - This is authorization
669
+ if (canUser(user, "user.update", registry)) {
670
+ // ...
671
+ }
672
+ ```
673
+
674
+ ---
675
+
676
+ ### ❌ DON'T: Assume permissions exist
677
+
678
+ ```typescript
679
+ // WRONG - May throw or behave unexpectedly
680
+ if (user.permissions.includes("admin")) {
681
+ // ...
682
+ }
683
+ ```
684
+
685
+ ### ✅ DO: Use the provided functions
686
+
687
+ ```typescript
688
+ // CORRECT - Handles null/undefined safely (deny by default)
689
+ if (canUser(user, "admin.access", registry)) {
690
+ // ...
691
+ }
692
+ ```
693
+
694
+ ---
695
+
696
+ ## Backend Adapters
697
+
698
+ ### Express Middleware
699
+
700
+ ```typescript
701
+ import {
702
+ requirePermission,
703
+ requireAllPermissions,
704
+ requireAnyPermission,
705
+ } from "secure-role-guard/adapters/express";
706
+
707
+ // Single permission
708
+ app.get("/api/users", requirePermission("user.read", registry), handler);
709
+
710
+ // All permissions required
711
+ app.delete(
712
+ "/api/admin",
713
+ requireAllPermissions(["admin.access", "data.delete"], registry),
714
+ handler
715
+ );
716
+
717
+ // Any permission
718
+ app.get(
719
+ "/api/reports",
720
+ requireAnyPermission(["report.view", "report.admin"], registry),
721
+ handler
722
+ );
723
+
724
+ // Custom options
725
+ app.put(
726
+ "/api/settings",
727
+ requirePermission("settings.update", registry, {
728
+ statusCode: 401,
729
+ message: "Unauthorized",
730
+ getUser: (req) => req.session?.user,
731
+ }),
732
+ handler
733
+ );
734
+ ```
735
+
736
+ ### Next.js Route Handlers
737
+
738
+ ```typescript
739
+ import {
740
+ withPermission,
741
+ checkNextPermission,
742
+ } from "secure-role-guard/adapters/nextjs";
743
+
744
+ // Using wrapper
745
+ export const POST = withPermission(
746
+ "post.create",
747
+ registry,
748
+ { getUser: async (req) => getUserFromSession(req) },
749
+ async (request, user) => {
750
+ return Response.json({ created: true });
751
+ }
752
+ );
753
+
754
+ // Manual check
755
+ export async function GET(request: NextRequest) {
756
+ const user = await getUser(request);
757
+ const result = checkNextPermission(user, "data.read", registry);
758
+
759
+ if (!result.allowed) {
760
+ return Response.json({ error: "Forbidden" }, { status: 403 });
761
+ }
762
+
763
+ return Response.json({ data: [] });
764
+ }
765
+ ```
766
+
767
+ ---
768
+
769
+ ## TypeScript Support
770
+
771
+ This package is written in TypeScript with strict mode enabled:
772
+
773
+ ```typescript
774
+ // tsconfig.json (package configuration)
775
+ {
776
+ "compilerOptions": {
777
+ "strict": true,
778
+ "noImplicitAny": true,
779
+ "strictNullChecks": true,
780
+ "exactOptionalPropertyTypes": true
781
+ }
782
+ }
783
+ ```
784
+
785
+ All types are exported:
786
+
787
+ ```typescript
788
+ import type {
789
+ UserContext,
790
+ RoleDefinition,
791
+ RoleRegistry,
792
+ PermissionCheckResult,
793
+ } from "secure-role-guard";
794
+ ```
795
+
796
+ ---
797
+
798
+ ## License
799
+
800
+ MIT © [Sohel Rahaman](https://github.com/sohelrahaman)
801
+
802
+ ---
803
+
804
+ ## Security Note
805
+
806
+ This package is designed to be **boring, predictable, and auditable**. It intentionally avoids:
807
+
808
+ - Magic behavior
809
+ - Clever hacks
810
+ - Hidden side effects
811
+ - Runtime code generation
812
+
813
+ If you find a security issue, please report it via [GitHub Issues](https://github.com/sohelrahaman/secure-role-guard/issues).