react-auth-gate 0.0.1
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/LICENSE +21 -0
- package/README.md +653 -0
- package/dist/core/ruleEngine.d.ts +47 -0
- package/dist/core/ruleEngine.d.ts.map +1 -0
- package/dist/core/ruleEngine.js +138 -0
- package/dist/core/types.d.ts +122 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/devtools/DevPanel.d.ts +13 -0
- package/dist/devtools/DevPanel.d.ts.map +1 -0
- package/dist/devtools/DevPanel.js +308 -0
- package/dist/devtools/DevStore.d.ts +80 -0
- package/dist/devtools/DevStore.d.ts.map +1 -0
- package/dist/devtools/DevStore.js +152 -0
- package/dist/devtools/PermissionsRoot.d.ts +33 -0
- package/dist/devtools/PermissionsRoot.d.ts.map +1 -0
- package/dist/devtools/PermissionsRoot.js +46 -0
- package/dist/devtools/useDevRegister.d.ts +17 -0
- package/dist/devtools/useDevRegister.d.ts.map +1 -0
- package/dist/devtools/useDevRegister.js +29 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/react/Permissioned.d.ts +41 -0
- package/dist/react/Permissioned.d.ts.map +1 -0
- package/dist/react/Permissioned.js +29 -0
- package/dist/react/PermissionsGate.d.ts +79 -0
- package/dist/react/PermissionsGate.d.ts.map +1 -0
- package/dist/react/PermissionsGate.js +101 -0
- package/dist/react/PermissionsProvider.d.ts +39 -0
- package/dist/react/PermissionsProvider.d.ts.map +1 -0
- package/dist/react/PermissionsProvider.js +93 -0
- package/dist/react/ProtectedRoute.d.ts +80 -0
- package/dist/react/ProtectedRoute.d.ts.map +1 -0
- package/dist/react/ProtectedRoute.js +92 -0
- package/dist/react/usePermission.d.ts +50 -0
- package/dist/react/usePermission.d.ts.map +1 -0
- package/dist/react/usePermission.js +71 -0
- package/package.json +56 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionsGate Component
|
|
3
|
+
*
|
|
4
|
+
* Declarative permission boundary for your components.
|
|
5
|
+
* Automatically hides or disables children based on permission checks.
|
|
6
|
+
*/
|
|
7
|
+
import React, { cloneElement } from 'react';
|
|
8
|
+
import { usePermission } from './usePermission';
|
|
9
|
+
/**
|
|
10
|
+
* PermissionsGate Component
|
|
11
|
+
*
|
|
12
|
+
* Wraps components with permission-based access control.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // Hide button if user can't edit
|
|
17
|
+
* <PermissionsGate allow="user.edit" resource={user}>
|
|
18
|
+
* <EditButton />
|
|
19
|
+
* </PermissionsGate>
|
|
20
|
+
*
|
|
21
|
+
* // Disable button instead of hiding
|
|
22
|
+
* <PermissionsGate allow="post.delete" resource={post} mode="disable">
|
|
23
|
+
* <DeleteButton />
|
|
24
|
+
* </PermissionsGate>
|
|
25
|
+
*
|
|
26
|
+
* // Check multiple permissions (any)
|
|
27
|
+
* <PermissionsGate any={["admin", "moderator"]}>
|
|
28
|
+
* <AdminPanel />
|
|
29
|
+
* </PermissionsGate>
|
|
30
|
+
*
|
|
31
|
+
* // Check multiple permissions (all)
|
|
32
|
+
* <PermissionsGate all={["post.edit", "post.publish"]}>
|
|
33
|
+
* <PublishButton />
|
|
34
|
+
* </PermissionsGate>
|
|
35
|
+
*
|
|
36
|
+
* // Show fallback when denied
|
|
37
|
+
* <PermissionsGate allow="premium.feature" fallback={<UpgradePrompt />}>
|
|
38
|
+
* <PremiumFeature />
|
|
39
|
+
* </PermissionsGate>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function PermissionsGate({ allow, any, all, resource, fallback, mode = 'hide', children, }) {
|
|
43
|
+
// Determine the permission check and evaluation mode
|
|
44
|
+
let check;
|
|
45
|
+
let evalMode = 'any';
|
|
46
|
+
if (allow !== undefined) {
|
|
47
|
+
check = allow;
|
|
48
|
+
evalMode = 'any';
|
|
49
|
+
}
|
|
50
|
+
else if (any !== undefined) {
|
|
51
|
+
check = any;
|
|
52
|
+
evalMode = 'any';
|
|
53
|
+
}
|
|
54
|
+
else if (all !== undefined) {
|
|
55
|
+
check = all;
|
|
56
|
+
evalMode = 'all';
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// No permission check specified - deny by default
|
|
60
|
+
console.warn('PermissionsGate: No permission check specified (allow, any, or all). Denying access.');
|
|
61
|
+
return fallback ? React.createElement(React.Fragment, null, fallback) : null;
|
|
62
|
+
}
|
|
63
|
+
const { allowed, loading } = usePermission(check, resource, evalMode);
|
|
64
|
+
// While loading, optionally show loading state
|
|
65
|
+
// For now, we treat loading as "not allowed" for security
|
|
66
|
+
if (loading) {
|
|
67
|
+
return mode === 'hide' ? null : React.createElement(React.Fragment, null, children);
|
|
68
|
+
}
|
|
69
|
+
// Permission denied
|
|
70
|
+
if (!allowed) {
|
|
71
|
+
if (mode === 'hide') {
|
|
72
|
+
return fallback ? React.createElement(React.Fragment, null, fallback) : null;
|
|
73
|
+
}
|
|
74
|
+
// mode === 'disable'
|
|
75
|
+
// Try to add disabled prop to children
|
|
76
|
+
return React.createElement(React.Fragment, null, disableChildren(children));
|
|
77
|
+
}
|
|
78
|
+
// Permission granted
|
|
79
|
+
return React.createElement(React.Fragment, null, children);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Helper function to add disabled prop to React elements
|
|
83
|
+
*/
|
|
84
|
+
function disableChildren(children) {
|
|
85
|
+
return React.Children.map(children, (child) => {
|
|
86
|
+
if (!React.isValidElement(child)) {
|
|
87
|
+
return child;
|
|
88
|
+
}
|
|
89
|
+
// Clone element with disabled prop
|
|
90
|
+
// This works for standard HTML elements and components that accept disabled prop
|
|
91
|
+
return cloneElement(child, {
|
|
92
|
+
disabled: true,
|
|
93
|
+
'aria-disabled': true,
|
|
94
|
+
style: {
|
|
95
|
+
...(child.props.style || {}),
|
|
96
|
+
opacity: 0.5,
|
|
97
|
+
cursor: 'not-allowed',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionsProvider
|
|
3
|
+
*
|
|
4
|
+
* Root provider component that establishes permission context for the entire app.
|
|
5
|
+
* Manages user, roles, permissions, rules, and feature flags.
|
|
6
|
+
*/
|
|
7
|
+
import React, { ReactNode } from 'react';
|
|
8
|
+
import type { PermissionsConfig, PermissionsContextValue, PermissionEvaluation } from '../core/types';
|
|
9
|
+
/**
|
|
10
|
+
* Hook to access the permissions context
|
|
11
|
+
* Throws if used outside of PermissionsProvider
|
|
12
|
+
*/
|
|
13
|
+
export declare function usePermissionsContext<TUser = any>(): PermissionsContextValue<TUser>;
|
|
14
|
+
interface PermissionsProviderProps<TUser = any> extends PermissionsConfig<TUser> {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/** Internal: Dev tools registration callback */
|
|
17
|
+
onEvaluationRegister?: (evaluation: PermissionEvaluation) => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* PermissionsProvider Component
|
|
21
|
+
*
|
|
22
|
+
* Wrap your app with this provider to enable permission checking throughout.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <PermissionsProvider
|
|
27
|
+
* user={currentUser}
|
|
28
|
+
* roles={['admin', 'editor']}
|
|
29
|
+
* permissions={['post.edit', 'post.delete']}
|
|
30
|
+
* rules={customRules}
|
|
31
|
+
* flags={{ newUI: true }}
|
|
32
|
+
* >
|
|
33
|
+
* <App />
|
|
34
|
+
* </PermissionsProvider>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function PermissionsProvider<TUser = any>({ user, roles, permissions, rules, flags, enableDevTools, children, onEvaluationRegister, }: PermissionsProviderProps<TUser>): React.JSX.Element;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=PermissionsProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermissionsProvider.d.ts","sourceRoot":"","sources":["../../src/react/PermissionsProvider.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAAmD,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1F,OAAO,KAAK,EACV,iBAAiB,EACjB,uBAAuB,EAEvB,oBAAoB,EACrB,MAAM,eAAe,CAAC;AASvB;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,GAAG,GAAG,KAAK,uBAAuB,CAAC,KAAK,CAAC,CAUnF;AAED,UAAU,wBAAwB,CAAC,KAAK,GAAG,GAAG,CAAE,SAAQ,iBAAiB,CAAC,KAAK,CAAC;IAC9E,QAAQ,EAAE,SAAS,CAAC;IACpB,gDAAgD;IAChD,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,GAAG,GAAG,EAAE,EAC/C,IAAI,EACJ,KAAU,EACV,WAAgB,EAChB,KAAU,EACV,KAAU,EACV,cAAc,EACd,QAAQ,EACR,oBAAoB,GACrB,EAAE,wBAAwB,CAAC,KAAK,CAAC,qBAiFjC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionsProvider
|
|
3
|
+
*
|
|
4
|
+
* Root provider component that establishes permission context for the entire app.
|
|
5
|
+
* Manages user, roles, permissions, rules, and feature flags.
|
|
6
|
+
*/
|
|
7
|
+
import React, { createContext, useContext, useMemo, useCallback } from 'react';
|
|
8
|
+
import { evaluatePermission as evaluatePermissionCore, createPermissionContext, } from '../core/ruleEngine';
|
|
9
|
+
// Create the context
|
|
10
|
+
const PermissionsContext = createContext(null);
|
|
11
|
+
/**
|
|
12
|
+
* Hook to access the permissions context
|
|
13
|
+
* Throws if used outside of PermissionsProvider
|
|
14
|
+
*/
|
|
15
|
+
export function usePermissionsContext() {
|
|
16
|
+
const context = useContext(PermissionsContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error('usePermissionsContext must be used within a PermissionsProvider');
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* PermissionsProvider Component
|
|
24
|
+
*
|
|
25
|
+
* Wrap your app with this provider to enable permission checking throughout.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <PermissionsProvider
|
|
30
|
+
* user={currentUser}
|
|
31
|
+
* roles={['admin', 'editor']}
|
|
32
|
+
* permissions={['post.edit', 'post.delete']}
|
|
33
|
+
* rules={customRules}
|
|
34
|
+
* flags={{ newUI: true }}
|
|
35
|
+
* >
|
|
36
|
+
* <App />
|
|
37
|
+
* </PermissionsProvider>
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function PermissionsProvider({ user, roles = [], permissions = [], rules = {}, flags = {}, enableDevTools, children, onEvaluationRegister, }) {
|
|
41
|
+
// Auto-enable dev tools in development unless explicitly disabled
|
|
42
|
+
const devToolsEnabled = useMemo(() => {
|
|
43
|
+
if (enableDevTools !== undefined) {
|
|
44
|
+
return enableDevTools;
|
|
45
|
+
}
|
|
46
|
+
// Check if we're in development mode
|
|
47
|
+
return process.env.NODE_ENV !== 'production';
|
|
48
|
+
}, [enableDevTools]);
|
|
49
|
+
/**
|
|
50
|
+
* Core permission evaluation function
|
|
51
|
+
* Used by all permission-checking components and hooks
|
|
52
|
+
*/
|
|
53
|
+
const evaluatePermission = useCallback(async (check, resource, mode = 'any') => {
|
|
54
|
+
const context = createPermissionContext(user, resource, roles, permissions, flags);
|
|
55
|
+
const startTime = performance.now();
|
|
56
|
+
const result = await evaluatePermissionCore(check, context, rules, mode);
|
|
57
|
+
// Register with dev tools if enabled
|
|
58
|
+
if (devToolsEnabled && onEvaluationRegister) {
|
|
59
|
+
const evaluation = {
|
|
60
|
+
id: `eval-${Date.now()}-${Math.random()}`,
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
check: typeof check === 'function' ? 'inline' : check,
|
|
63
|
+
resource,
|
|
64
|
+
allowed: result.allowed,
|
|
65
|
+
ruleResults: result.ruleResults,
|
|
66
|
+
mode,
|
|
67
|
+
};
|
|
68
|
+
onEvaluationRegister(evaluation);
|
|
69
|
+
}
|
|
70
|
+
return result.allowed;
|
|
71
|
+
}, [user, roles, permissions, rules, flags, devToolsEnabled, onEvaluationRegister]);
|
|
72
|
+
// Memoize context value to prevent unnecessary re-renders
|
|
73
|
+
const contextValue = useMemo(() => ({
|
|
74
|
+
user,
|
|
75
|
+
roles,
|
|
76
|
+
permissions,
|
|
77
|
+
rules,
|
|
78
|
+
flags,
|
|
79
|
+
enableDevTools: devToolsEnabled,
|
|
80
|
+
evaluatePermission,
|
|
81
|
+
registerEvaluation: onEvaluationRegister,
|
|
82
|
+
}), [
|
|
83
|
+
user,
|
|
84
|
+
roles,
|
|
85
|
+
permissions,
|
|
86
|
+
rules,
|
|
87
|
+
flags,
|
|
88
|
+
devToolsEnabled,
|
|
89
|
+
evaluatePermission,
|
|
90
|
+
onEvaluationRegister,
|
|
91
|
+
]);
|
|
92
|
+
return (React.createElement(PermissionsContext.Provider, { value: contextValue }, children));
|
|
93
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProtectedRoute Component
|
|
3
|
+
*
|
|
4
|
+
* Wrapper for route protection based on permissions.
|
|
5
|
+
* Works with any routing library (React Router, Next.js, etc.)
|
|
6
|
+
*/
|
|
7
|
+
import React, { ReactNode } from 'react';
|
|
8
|
+
import type { PermissionCheck } from '../core/types';
|
|
9
|
+
export interface ProtectedRouteProps<TUser = any, TResource = any> {
|
|
10
|
+
/**
|
|
11
|
+
* Permission check required to access this route
|
|
12
|
+
*/
|
|
13
|
+
allow: PermissionCheck<TUser, TResource>;
|
|
14
|
+
/**
|
|
15
|
+
* Resource to check permission against (optional)
|
|
16
|
+
*/
|
|
17
|
+
resource?: TResource;
|
|
18
|
+
/**
|
|
19
|
+
* Content to render when permission is granted
|
|
20
|
+
*/
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Content to render when permission is denied
|
|
24
|
+
* Typically a redirect or unauthorized message
|
|
25
|
+
*/
|
|
26
|
+
fallback?: ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Optional callback when access is denied
|
|
29
|
+
* Useful for analytics, logging, or custom redirects
|
|
30
|
+
*/
|
|
31
|
+
onAccessDenied?: () => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* ProtectedRoute Component
|
|
35
|
+
*
|
|
36
|
+
* Protects routes based on permissions.
|
|
37
|
+
* Framework-agnostic - works with any routing solution.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* // With React Router
|
|
42
|
+
* <Route
|
|
43
|
+
* path="/admin"
|
|
44
|
+
* element={
|
|
45
|
+
* <ProtectedRoute
|
|
46
|
+
* allow="admin"
|
|
47
|
+
* fallback={<Navigate to="/login" />}
|
|
48
|
+
* >
|
|
49
|
+
* <AdminDashboard />
|
|
50
|
+
* </ProtectedRoute>
|
|
51
|
+
* }
|
|
52
|
+
* />
|
|
53
|
+
*
|
|
54
|
+
* // With Next.js
|
|
55
|
+
* function AdminPage() {
|
|
56
|
+
* const router = useRouter();
|
|
57
|
+
*
|
|
58
|
+
* return (
|
|
59
|
+
* <ProtectedRoute
|
|
60
|
+
* allow="admin"
|
|
61
|
+
* onAccessDenied={() => router.push('/login')}
|
|
62
|
+
* fallback={<div>Redirecting...</div>}
|
|
63
|
+
* >
|
|
64
|
+
* <AdminPanel />
|
|
65
|
+
* </ProtectedRoute>
|
|
66
|
+
* );
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
* // Resource-based protection
|
|
70
|
+
* <ProtectedRoute
|
|
71
|
+
* allow="post.edit"
|
|
72
|
+
* resource={post}
|
|
73
|
+
* fallback={<Unauthorized />}
|
|
74
|
+
* >
|
|
75
|
+
* <EditPost post={post} />
|
|
76
|
+
* </ProtectedRoute>
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function ProtectedRoute<TUser = any, TResource = any>({ allow, resource, children, fallback, onAccessDenied, }: ProtectedRouteProps<TUser, TResource>): React.JSX.Element;
|
|
80
|
+
//# sourceMappingURL=ProtectedRoute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProtectedRoute.d.ts","sourceRoot":"","sources":["../../src/react/ProtectedRoute.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,WAAW,mBAAmB,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG;IAC/D;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,cAAc,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,EAC3D,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAkC,EAClC,cAAc,GACf,EAAE,mBAAmB,CAAC,KAAK,EAAE,SAAS,CAAC,qBAsBvC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProtectedRoute Component
|
|
3
|
+
*
|
|
4
|
+
* Wrapper for route protection based on permissions.
|
|
5
|
+
* Works with any routing library (React Router, Next.js, etc.)
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { usePermission } from './usePermission';
|
|
9
|
+
/**
|
|
10
|
+
* ProtectedRoute Component
|
|
11
|
+
*
|
|
12
|
+
* Protects routes based on permissions.
|
|
13
|
+
* Framework-agnostic - works with any routing solution.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* // With React Router
|
|
18
|
+
* <Route
|
|
19
|
+
* path="/admin"
|
|
20
|
+
* element={
|
|
21
|
+
* <ProtectedRoute
|
|
22
|
+
* allow="admin"
|
|
23
|
+
* fallback={<Navigate to="/login" />}
|
|
24
|
+
* >
|
|
25
|
+
* <AdminDashboard />
|
|
26
|
+
* </ProtectedRoute>
|
|
27
|
+
* }
|
|
28
|
+
* />
|
|
29
|
+
*
|
|
30
|
+
* // With Next.js
|
|
31
|
+
* function AdminPage() {
|
|
32
|
+
* const router = useRouter();
|
|
33
|
+
*
|
|
34
|
+
* return (
|
|
35
|
+
* <ProtectedRoute
|
|
36
|
+
* allow="admin"
|
|
37
|
+
* onAccessDenied={() => router.push('/login')}
|
|
38
|
+
* fallback={<div>Redirecting...</div>}
|
|
39
|
+
* >
|
|
40
|
+
* <AdminPanel />
|
|
41
|
+
* </ProtectedRoute>
|
|
42
|
+
* );
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* // Resource-based protection
|
|
46
|
+
* <ProtectedRoute
|
|
47
|
+
* allow="post.edit"
|
|
48
|
+
* resource={post}
|
|
49
|
+
* fallback={<Unauthorized />}
|
|
50
|
+
* >
|
|
51
|
+
* <EditPost post={post} />
|
|
52
|
+
* </ProtectedRoute>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function ProtectedRoute({ allow, resource, children, fallback = React.createElement(DefaultUnauthorized, null), onAccessDenied, }) {
|
|
56
|
+
const { allowed, loading } = usePermission(allow, resource);
|
|
57
|
+
// Call onAccessDenied when permission is denied (only once)
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
if (!loading && !allowed && onAccessDenied) {
|
|
60
|
+
onAccessDenied();
|
|
61
|
+
}
|
|
62
|
+
}, [loading, allowed, onAccessDenied]);
|
|
63
|
+
// While loading, show nothing or a loading indicator
|
|
64
|
+
if (loading) {
|
|
65
|
+
return React.createElement(DefaultLoading, null);
|
|
66
|
+
}
|
|
67
|
+
// Permission denied
|
|
68
|
+
if (!allowed) {
|
|
69
|
+
return React.createElement(React.Fragment, null, fallback);
|
|
70
|
+
}
|
|
71
|
+
// Permission granted
|
|
72
|
+
return React.createElement(React.Fragment, null, children);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Default loading component
|
|
76
|
+
*/
|
|
77
|
+
function DefaultLoading() {
|
|
78
|
+
return (React.createElement("div", { style: { padding: '20px', textAlign: 'center' } }, "Loading..."));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Default unauthorized component
|
|
82
|
+
*/
|
|
83
|
+
function DefaultUnauthorized() {
|
|
84
|
+
return (React.createElement("div", { style: {
|
|
85
|
+
padding: '40px',
|
|
86
|
+
textAlign: 'center',
|
|
87
|
+
maxWidth: '600px',
|
|
88
|
+
margin: '0 auto',
|
|
89
|
+
} },
|
|
90
|
+
React.createElement("h1", null, "Access Denied"),
|
|
91
|
+
React.createElement("p", null, "You do not have permission to access this resource.")));
|
|
92
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePermission Hook
|
|
3
|
+
*
|
|
4
|
+
* Check permissions programmatically in your components.
|
|
5
|
+
* Supports async rules and automatically re-evaluates when dependencies change.
|
|
6
|
+
*/
|
|
7
|
+
import type { PermissionCheck } from '../core/types';
|
|
8
|
+
/**
|
|
9
|
+
* Hook to check if a permission is allowed
|
|
10
|
+
*
|
|
11
|
+
* @param check - Permission check (string, array, or function)
|
|
12
|
+
* @param resource - Optional resource to check against
|
|
13
|
+
* @param mode - Evaluation mode for arrays: 'any' (OR) or 'all' (AND)
|
|
14
|
+
* @returns Object with loading state and permission result
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function EditButton({ user }) {
|
|
19
|
+
* const { allowed, loading } = usePermission('user.edit', user);
|
|
20
|
+
*
|
|
21
|
+
* if (loading) return <Spinner />;
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* <button disabled={!allowed}>
|
|
25
|
+
* Edit User
|
|
26
|
+
* </button>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function usePermission<TUser = any, TResource = any>(check: PermissionCheck<TUser, TResource>, resource?: TResource, mode?: 'any' | 'all'): {
|
|
32
|
+
allowed: boolean;
|
|
33
|
+
loading: boolean;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Simpler hook that only returns the boolean result
|
|
37
|
+
* Use when you don't need loading state
|
|
38
|
+
*
|
|
39
|
+
* @param check - Permission check
|
|
40
|
+
* @param resource - Optional resource
|
|
41
|
+
* @param mode - Evaluation mode
|
|
42
|
+
* @returns Boolean indicating if permission is allowed
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* const canEdit = usePermissionValue('user.edit', user);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function usePermissionValue<TUser = any, TResource = any>(check: PermissionCheck<TUser, TResource>, resource?: TResource, mode?: 'any' | 'all'): boolean;
|
|
50
|
+
//# sourceMappingURL=usePermission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePermission.d.ts","sourceRoot":"","sources":["../../src/react/usePermission.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EACxD,KAAK,EAAE,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,EACxC,QAAQ,CAAC,EAAE,SAAS,EACpB,IAAI,GAAE,KAAK,GAAG,KAAa,GAC1B;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB,CA0BA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAC7D,KAAK,EAAE,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,EACxC,QAAQ,CAAC,EAAE,SAAS,EACpB,IAAI,GAAE,KAAK,GAAG,KAAa,GAC1B,OAAO,CAGT"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePermission Hook
|
|
3
|
+
*
|
|
4
|
+
* Check permissions programmatically in your components.
|
|
5
|
+
* Supports async rules and automatically re-evaluates when dependencies change.
|
|
6
|
+
*/
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import { usePermissionsContext } from './PermissionsProvider';
|
|
9
|
+
/**
|
|
10
|
+
* Hook to check if a permission is allowed
|
|
11
|
+
*
|
|
12
|
+
* @param check - Permission check (string, array, or function)
|
|
13
|
+
* @param resource - Optional resource to check against
|
|
14
|
+
* @param mode - Evaluation mode for arrays: 'any' (OR) or 'all' (AND)
|
|
15
|
+
* @returns Object with loading state and permission result
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* function EditButton({ user }) {
|
|
20
|
+
* const { allowed, loading } = usePermission('user.edit', user);
|
|
21
|
+
*
|
|
22
|
+
* if (loading) return <Spinner />;
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* <button disabled={!allowed}>
|
|
26
|
+
* Edit User
|
|
27
|
+
* </button>
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function usePermission(check, resource, mode = 'any') {
|
|
33
|
+
const context = usePermissionsContext();
|
|
34
|
+
const [state, setState] = useState({
|
|
35
|
+
allowed: false,
|
|
36
|
+
loading: true,
|
|
37
|
+
});
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
let cancelled = false;
|
|
40
|
+
// Start evaluation
|
|
41
|
+
setState({ allowed: false, loading: true });
|
|
42
|
+
context.evaluatePermission(check, resource, mode).then((allowed) => {
|
|
43
|
+
if (!cancelled) {
|
|
44
|
+
setState({ allowed, loading: false });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// Cleanup function to prevent state updates after unmount
|
|
48
|
+
return () => {
|
|
49
|
+
cancelled = true;
|
|
50
|
+
};
|
|
51
|
+
}, [context.evaluatePermission, context.roles, context.permissions, context.flags, check, resource, mode]);
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Simpler hook that only returns the boolean result
|
|
56
|
+
* Use when you don't need loading state
|
|
57
|
+
*
|
|
58
|
+
* @param check - Permission check
|
|
59
|
+
* @param resource - Optional resource
|
|
60
|
+
* @param mode - Evaluation mode
|
|
61
|
+
* @returns Boolean indicating if permission is allowed
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* const canEdit = usePermissionValue('user.edit', user);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function usePermissionValue(check, resource, mode = 'any') {
|
|
69
|
+
const { allowed } = usePermission(check, resource, mode);
|
|
70
|
+
return allowed;
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-auth-gate",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A production-grade React authorization framework for RBAC, PBAC, ABAC, feature flags, and async permission checks",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react",
|
|
20
|
+
"permissions",
|
|
21
|
+
"authorization",
|
|
22
|
+
"rbac",
|
|
23
|
+
"pbac",
|
|
24
|
+
"abac",
|
|
25
|
+
"feature-flags",
|
|
26
|
+
"access-control"
|
|
27
|
+
],
|
|
28
|
+
"author": "Klejdi 2K",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/klejdi94/react-auth-gate"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": ">=16.8.0",
|
|
36
|
+
"react-dom": ">=16.8.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/jest": "^29.5.0",
|
|
40
|
+
"@types/react": "^18.2.0",
|
|
41
|
+
"@types/react-dom": "^18.2.0",
|
|
42
|
+
"jest": "^29.5.0",
|
|
43
|
+
"jest-environment-jsdom": "^29.5.0",
|
|
44
|
+
"ts-jest": "^29.1.0",
|
|
45
|
+
"typescript": "^5.3.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc",
|
|
49
|
+
"dev": "tsc --watch",
|
|
50
|
+
"test": "jest",
|
|
51
|
+
"test:watch": "jest --watch",
|
|
52
|
+
"test:coverage": "jest --coverage",
|
|
53
|
+
"prepublishOnly": "npm run build"
|
|
54
|
+
},
|
|
55
|
+
"sideEffects": false
|
|
56
|
+
}
|