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 @@
|
|
|
1
|
+
{"version":3,"file":"ruleEngine.d.ts","sourceRoot":"","sources":["../../src/core/ruleEngine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAEjB;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAC7D,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,EACtC,OAAO,EAAE,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,GAC3C,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAC5D,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,EAC9C,OAAO,EAAE,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,GAC3C,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAUlC;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EACnE,KAAK,EAAE,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,EACxC,OAAO,EAAE,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,EAC5C,QAAQ,EAAE,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,EAC9C,IAAI,GAAE,KAAK,GAAG,KAAa,GAC1B,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC,CAgFD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAClE,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,SAAS,GAAG,SAAS,EAC/B,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,CAQrC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Engine
|
|
3
|
+
*
|
|
4
|
+
* Core logic for evaluating permission rules against a context.
|
|
5
|
+
* Supports sync/async rules, string-based rules, and inline functions.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Evaluates a single permission rule
|
|
9
|
+
*
|
|
10
|
+
* @param rule - The rule function to evaluate
|
|
11
|
+
* @param context - The permission context
|
|
12
|
+
* @returns Promise resolving to rule result and evaluation metadata
|
|
13
|
+
*/
|
|
14
|
+
export async function evaluateRule(rule, context) {
|
|
15
|
+
const startTime = performance.now();
|
|
16
|
+
try {
|
|
17
|
+
const result = await Promise.resolve(rule(context));
|
|
18
|
+
const duration = performance.now() - startTime;
|
|
19
|
+
return {
|
|
20
|
+
result: Boolean(result),
|
|
21
|
+
duration,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const duration = performance.now() - startTime;
|
|
26
|
+
return {
|
|
27
|
+
result: false,
|
|
28
|
+
duration,
|
|
29
|
+
error: error instanceof Error ? error.message : String(error),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolves a string-based permission key to a rule function
|
|
35
|
+
*
|
|
36
|
+
* Strategy:
|
|
37
|
+
* 1. Check if it exists in the rules map
|
|
38
|
+
* 2. Check if it's in the permissions array (direct permission grant)
|
|
39
|
+
* 3. Check if it's in the roles array (role-based grant)
|
|
40
|
+
* 4. Otherwise deny
|
|
41
|
+
*/
|
|
42
|
+
export function resolveStringRule(permissionKey, rulesMap, context) {
|
|
43
|
+
// If a custom rule exists for this key, use it
|
|
44
|
+
if (rulesMap[permissionKey]) {
|
|
45
|
+
return rulesMap[permissionKey];
|
|
46
|
+
}
|
|
47
|
+
// Otherwise, check if the permission/role is directly granted
|
|
48
|
+
return (ctx) => {
|
|
49
|
+
return ctx.permissions.includes(permissionKey) || ctx.roles.includes(permissionKey);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Evaluates a permission check (string, array, or function)
|
|
54
|
+
*
|
|
55
|
+
* @param check - The permission check to evaluate
|
|
56
|
+
* @param context - The permission context
|
|
57
|
+
* @param rulesMap - Map of named rules
|
|
58
|
+
* @param mode - Evaluation mode: 'any' (OR) or 'all' (AND)
|
|
59
|
+
* @returns Promise resolving to evaluation result and metadata
|
|
60
|
+
*/
|
|
61
|
+
export async function evaluatePermission(check, context, rulesMap, mode = 'any') {
|
|
62
|
+
const ruleResults = [];
|
|
63
|
+
// Case 1: Inline function rule
|
|
64
|
+
if (typeof check === 'function') {
|
|
65
|
+
const evaluation = await evaluateRule(check, context);
|
|
66
|
+
ruleResults.push({
|
|
67
|
+
rule: 'inline',
|
|
68
|
+
result: evaluation.result,
|
|
69
|
+
duration: evaluation.duration,
|
|
70
|
+
error: evaluation.error,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
allowed: evaluation.result,
|
|
74
|
+
ruleResults,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Case 2: Single string permission
|
|
78
|
+
if (typeof check === 'string') {
|
|
79
|
+
const rule = resolveStringRule(check, rulesMap, context);
|
|
80
|
+
const evaluation = await evaluateRule(rule, context);
|
|
81
|
+
ruleResults.push({
|
|
82
|
+
rule: check,
|
|
83
|
+
result: evaluation.result,
|
|
84
|
+
duration: evaluation.duration,
|
|
85
|
+
error: evaluation.error,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
allowed: evaluation.result,
|
|
89
|
+
ruleResults,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Case 3: Array of permission strings
|
|
93
|
+
if (Array.isArray(check)) {
|
|
94
|
+
const evaluations = await Promise.all(check.map(async (key) => {
|
|
95
|
+
const rule = resolveStringRule(key, rulesMap, context);
|
|
96
|
+
const evaluation = await evaluateRule(rule, context);
|
|
97
|
+
return {
|
|
98
|
+
rule: key,
|
|
99
|
+
result: evaluation.result,
|
|
100
|
+
duration: evaluation.duration,
|
|
101
|
+
error: evaluation.error,
|
|
102
|
+
};
|
|
103
|
+
}));
|
|
104
|
+
ruleResults.push(...evaluations);
|
|
105
|
+
// Apply mode logic
|
|
106
|
+
const allowed = mode === 'all'
|
|
107
|
+
? evaluations.every((e) => e.result)
|
|
108
|
+
: evaluations.some((e) => e.result);
|
|
109
|
+
return {
|
|
110
|
+
allowed,
|
|
111
|
+
ruleResults,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Invalid check type
|
|
115
|
+
return {
|
|
116
|
+
allowed: false,
|
|
117
|
+
ruleResults: [
|
|
118
|
+
{
|
|
119
|
+
rule: 'unknown',
|
|
120
|
+
result: false,
|
|
121
|
+
duration: 0,
|
|
122
|
+
error: 'Invalid permission check type',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Creates a permission context from configuration values
|
|
129
|
+
*/
|
|
130
|
+
export function createPermissionContext(user, resource, roles, permissions, flags) {
|
|
131
|
+
return {
|
|
132
|
+
user,
|
|
133
|
+
resource,
|
|
134
|
+
roles,
|
|
135
|
+
permissions,
|
|
136
|
+
flags,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for react-permissions-gate
|
|
3
|
+
*
|
|
4
|
+
* Defines the type system for permission checking, rule evaluation,
|
|
5
|
+
* and context management across the library.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Permission evaluation context passed to rule functions
|
|
9
|
+
*/
|
|
10
|
+
export interface PermissionContext<TUser = any, TResource = any> {
|
|
11
|
+
/** Current authenticated user */
|
|
12
|
+
user: TUser;
|
|
13
|
+
/** Optional resource being accessed */
|
|
14
|
+
resource?: TResource;
|
|
15
|
+
/** Array of role strings assigned to the user */
|
|
16
|
+
roles: string[];
|
|
17
|
+
/** Array of permission strings granted to the user */
|
|
18
|
+
permissions: string[];
|
|
19
|
+
/** Feature flags map */
|
|
20
|
+
flags: Record<string, boolean>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Permission rule function signature
|
|
24
|
+
* Can return boolean synchronously or Promise<boolean> for async checks
|
|
25
|
+
*/
|
|
26
|
+
export type PermissionRule<TUser = any, TResource = any> = (ctx: PermissionContext<TUser, TResource>) => boolean | Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Map of named permission rules
|
|
29
|
+
*/
|
|
30
|
+
export type PermissionRulesMap<TUser = any, TResource = any> = Record<string, PermissionRule<TUser, TResource>>;
|
|
31
|
+
/**
|
|
32
|
+
* Permission check input - can be a rule key, array of keys, or inline function
|
|
33
|
+
*/
|
|
34
|
+
export type PermissionCheck<TUser = any, TResource = any> = string | string[] | PermissionRule<TUser, TResource>;
|
|
35
|
+
/**
|
|
36
|
+
* Mode for rendering behavior when permission is denied
|
|
37
|
+
*/
|
|
38
|
+
export type PermissionMode = 'hide' | 'disable';
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for the PermissionsProvider
|
|
41
|
+
*/
|
|
42
|
+
export interface PermissionsConfig<TUser = any> {
|
|
43
|
+
/** Current authenticated user */
|
|
44
|
+
user: TUser;
|
|
45
|
+
/** Array of role strings */
|
|
46
|
+
roles?: string[];
|
|
47
|
+
/** Array of permission strings */
|
|
48
|
+
permissions?: string[];
|
|
49
|
+
/** Named permission rules */
|
|
50
|
+
rules?: PermissionRulesMap<TUser, any>;
|
|
51
|
+
/** Feature flags */
|
|
52
|
+
flags?: Record<string, boolean>;
|
|
53
|
+
/** Enable dev tools panel (defaults to process.env.NODE_ENV !== 'production') */
|
|
54
|
+
enableDevTools?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Internal context value exposed by PermissionsProvider
|
|
58
|
+
*/
|
|
59
|
+
export interface PermissionsContextValue<TUser = any> {
|
|
60
|
+
user: TUser;
|
|
61
|
+
roles: string[];
|
|
62
|
+
permissions: string[];
|
|
63
|
+
rules: PermissionRulesMap<TUser, any>;
|
|
64
|
+
flags: Record<string, boolean>;
|
|
65
|
+
enableDevTools: boolean;
|
|
66
|
+
/** Internal: Evaluate a permission check */
|
|
67
|
+
evaluatePermission: <TResource = any>(check: PermissionCheck<TUser, TResource>, resource?: TResource, mode?: 'any' | 'all') => Promise<boolean>;
|
|
68
|
+
/** Internal: Register permission evaluation for dev tools */
|
|
69
|
+
registerEvaluation?: (evaluation: PermissionEvaluation) => void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Result of a permission evaluation (used by dev tools)
|
|
73
|
+
*/
|
|
74
|
+
export interface PermissionEvaluation {
|
|
75
|
+
/** Unique ID for this evaluation */
|
|
76
|
+
id: string;
|
|
77
|
+
/** Timestamp of evaluation */
|
|
78
|
+
timestamp: number;
|
|
79
|
+
/** Permission check that was evaluated */
|
|
80
|
+
check: string | string[];
|
|
81
|
+
/** Resource involved (if any) */
|
|
82
|
+
resource?: any;
|
|
83
|
+
/** Result of the check */
|
|
84
|
+
allowed: boolean;
|
|
85
|
+
/** Details about which rules were evaluated */
|
|
86
|
+
ruleResults: RuleEvaluationResult[];
|
|
87
|
+
/** Component that triggered this check (if available) */
|
|
88
|
+
component?: string;
|
|
89
|
+
/** Evaluation mode (any/all) */
|
|
90
|
+
mode?: 'any' | 'all';
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Result of evaluating a single rule
|
|
94
|
+
*/
|
|
95
|
+
export interface RuleEvaluationResult {
|
|
96
|
+
/** Rule key or 'inline' for inline functions */
|
|
97
|
+
rule: string;
|
|
98
|
+
/** Result of the rule evaluation */
|
|
99
|
+
result: boolean;
|
|
100
|
+
/** Time taken to evaluate (ms) */
|
|
101
|
+
duration: number;
|
|
102
|
+
/** Error if rule threw */
|
|
103
|
+
error?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Dev tools state
|
|
107
|
+
*/
|
|
108
|
+
export interface DevToolsState {
|
|
109
|
+
/** All permission evaluations */
|
|
110
|
+
evaluations: PermissionEvaluation[];
|
|
111
|
+
/** Whether dev panel is open */
|
|
112
|
+
isOpen: boolean;
|
|
113
|
+
/** Override user context for testing */
|
|
114
|
+
overrideUser?: any;
|
|
115
|
+
/** Override roles for testing */
|
|
116
|
+
overrideRoles?: string[];
|
|
117
|
+
/** Override permissions for testing */
|
|
118
|
+
overridePermissions?: string[];
|
|
119
|
+
/** Override flags for testing */
|
|
120
|
+
overrideFlags?: Record<string, boolean>;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevPanel Component
|
|
3
|
+
*
|
|
4
|
+
* The killer feature: automatic development panel for permission debugging.
|
|
5
|
+
* Shows all permission checks, allows context overrides, and provides live feedback.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
/**
|
|
9
|
+
* DevPanel Component
|
|
10
|
+
* Automatically shown in development mode
|
|
11
|
+
*/
|
|
12
|
+
export declare function DevPanel(): React.JSX.Element | null;
|
|
13
|
+
//# sourceMappingURL=DevPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DevPanel.d.ts","sourceRoot":"","sources":["../../src/devtools/DevPanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAA4B,MAAM,OAAO,CAAC;AAsJjD;;;GAGG;AACH,wBAAgB,QAAQ,6BA4GvB"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevPanel Component
|
|
3
|
+
*
|
|
4
|
+
* The killer feature: automatic development panel for permission debugging.
|
|
5
|
+
* Shows all permission checks, allows context overrides, and provides live feedback.
|
|
6
|
+
*/
|
|
7
|
+
import React, { useState, useMemo } from 'react';
|
|
8
|
+
import { devStore } from './DevStore';
|
|
9
|
+
import { useDevToolsState } from './useDevRegister';
|
|
10
|
+
import { usePermissionsContext } from '../react/PermissionsProvider';
|
|
11
|
+
const PANEL_STYLES = {
|
|
12
|
+
container: {
|
|
13
|
+
position: 'fixed',
|
|
14
|
+
bottom: 0,
|
|
15
|
+
right: 0,
|
|
16
|
+
width: '450px',
|
|
17
|
+
maxHeight: '600px',
|
|
18
|
+
backgroundColor: '#1e1e1e',
|
|
19
|
+
color: '#d4d4d4',
|
|
20
|
+
fontFamily: 'monospace',
|
|
21
|
+
fontSize: '12px',
|
|
22
|
+
boxShadow: '0 -2px 10px rgba(0,0,0,0.5)',
|
|
23
|
+
zIndex: 999999,
|
|
24
|
+
display: 'flex',
|
|
25
|
+
flexDirection: 'column',
|
|
26
|
+
borderTopLeftRadius: '8px',
|
|
27
|
+
},
|
|
28
|
+
header: {
|
|
29
|
+
padding: '12px 16px',
|
|
30
|
+
backgroundColor: '#2d2d2d',
|
|
31
|
+
borderBottom: '1px solid #3e3e3e',
|
|
32
|
+
display: 'flex',
|
|
33
|
+
justifyContent: 'space-between',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
borderTopLeftRadius: '8px',
|
|
36
|
+
},
|
|
37
|
+
title: {
|
|
38
|
+
margin: 0,
|
|
39
|
+
fontSize: '14px',
|
|
40
|
+
fontWeight: 'bold',
|
|
41
|
+
color: '#4fc3f7',
|
|
42
|
+
},
|
|
43
|
+
button: {
|
|
44
|
+
background: 'none',
|
|
45
|
+
border: '1px solid #4fc3f7',
|
|
46
|
+
color: '#4fc3f7',
|
|
47
|
+
padding: '4px 8px',
|
|
48
|
+
cursor: 'pointer',
|
|
49
|
+
fontSize: '11px',
|
|
50
|
+
borderRadius: '3px',
|
|
51
|
+
marginLeft: '8px',
|
|
52
|
+
},
|
|
53
|
+
content: {
|
|
54
|
+
flex: 1,
|
|
55
|
+
overflowY: 'auto',
|
|
56
|
+
padding: '16px',
|
|
57
|
+
},
|
|
58
|
+
tab: {
|
|
59
|
+
padding: '8px 16px',
|
|
60
|
+
cursor: 'pointer',
|
|
61
|
+
display: 'inline-block',
|
|
62
|
+
},
|
|
63
|
+
activeTab: {
|
|
64
|
+
borderBottom: '2px solid #4fc3f7',
|
|
65
|
+
color: '#4fc3f7',
|
|
66
|
+
},
|
|
67
|
+
tabs: {
|
|
68
|
+
display: 'flex',
|
|
69
|
+
backgroundColor: '#252525',
|
|
70
|
+
borderBottom: '1px solid #3e3e3e',
|
|
71
|
+
},
|
|
72
|
+
evaluation: {
|
|
73
|
+
marginBottom: '12px',
|
|
74
|
+
padding: '8px',
|
|
75
|
+
backgroundColor: '#252525',
|
|
76
|
+
borderRadius: '4px',
|
|
77
|
+
borderLeft: '3px solid',
|
|
78
|
+
},
|
|
79
|
+
allowed: {
|
|
80
|
+
borderLeftColor: '#4caf50',
|
|
81
|
+
},
|
|
82
|
+
denied: {
|
|
83
|
+
borderLeftColor: '#f44336',
|
|
84
|
+
},
|
|
85
|
+
timestamp: {
|
|
86
|
+
fontSize: '10px',
|
|
87
|
+
color: '#888',
|
|
88
|
+
},
|
|
89
|
+
badge: {
|
|
90
|
+
display: 'inline-block',
|
|
91
|
+
padding: '2px 6px',
|
|
92
|
+
borderRadius: '3px',
|
|
93
|
+
fontSize: '10px',
|
|
94
|
+
marginRight: '4px',
|
|
95
|
+
marginTop: '4px',
|
|
96
|
+
},
|
|
97
|
+
successBadge: {
|
|
98
|
+
backgroundColor: '#4caf50',
|
|
99
|
+
color: '#fff',
|
|
100
|
+
},
|
|
101
|
+
errorBadge: {
|
|
102
|
+
backgroundColor: '#f44336',
|
|
103
|
+
color: '#fff',
|
|
104
|
+
},
|
|
105
|
+
toggle: {
|
|
106
|
+
position: 'fixed',
|
|
107
|
+
bottom: '10px',
|
|
108
|
+
right: '10px',
|
|
109
|
+
width: '50px',
|
|
110
|
+
height: '50px',
|
|
111
|
+
borderRadius: '50%',
|
|
112
|
+
backgroundColor: '#4fc3f7',
|
|
113
|
+
color: '#fff',
|
|
114
|
+
border: 'none',
|
|
115
|
+
cursor: 'pointer',
|
|
116
|
+
fontSize: '20px',
|
|
117
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.3)',
|
|
118
|
+
zIndex: 999998,
|
|
119
|
+
display: 'flex',
|
|
120
|
+
alignItems: 'center',
|
|
121
|
+
justifyContent: 'center',
|
|
122
|
+
},
|
|
123
|
+
section: {
|
|
124
|
+
marginBottom: '16px',
|
|
125
|
+
},
|
|
126
|
+
sectionTitle: {
|
|
127
|
+
fontSize: '12px',
|
|
128
|
+
fontWeight: 'bold',
|
|
129
|
+
marginBottom: '8px',
|
|
130
|
+
color: '#4fc3f7',
|
|
131
|
+
},
|
|
132
|
+
checkbox: {
|
|
133
|
+
marginRight: '6px',
|
|
134
|
+
},
|
|
135
|
+
label: {
|
|
136
|
+
display: 'block',
|
|
137
|
+
padding: '4px 0',
|
|
138
|
+
cursor: 'pointer',
|
|
139
|
+
},
|
|
140
|
+
input: {
|
|
141
|
+
width: '100%',
|
|
142
|
+
padding: '6px',
|
|
143
|
+
backgroundColor: '#1e1e1e',
|
|
144
|
+
border: '1px solid #3e3e3e',
|
|
145
|
+
color: '#d4d4d4',
|
|
146
|
+
borderRadius: '3px',
|
|
147
|
+
marginTop: '4px',
|
|
148
|
+
fontFamily: 'monospace',
|
|
149
|
+
fontSize: '11px',
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* DevPanel Component
|
|
154
|
+
* Automatically shown in development mode
|
|
155
|
+
*/
|
|
156
|
+
export function DevPanel() {
|
|
157
|
+
const state = useDevToolsState();
|
|
158
|
+
const context = usePermissionsContext();
|
|
159
|
+
const [activeTab, setActiveTab] = useState('evaluations');
|
|
160
|
+
if (!context.enableDevTools) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// Apply overrides to current context
|
|
164
|
+
const effectiveRoles = state.overrideRoles || context.roles;
|
|
165
|
+
const effectivePermissions = state.overridePermissions || context.permissions;
|
|
166
|
+
const effectiveFlags = state.overrideFlags || context.flags;
|
|
167
|
+
return (React.createElement(React.Fragment, null,
|
|
168
|
+
!state.isOpen && (React.createElement("button", { style: PANEL_STYLES.toggle, onClick: () => devStore.togglePanel(), title: "Open Permissions Dev Panel" }, "\uD83D\uDD10")),
|
|
169
|
+
state.isOpen && (React.createElement("div", { style: PANEL_STYLES.container },
|
|
170
|
+
React.createElement("div", { style: PANEL_STYLES.header },
|
|
171
|
+
React.createElement("h3", { style: PANEL_STYLES.title }, "\uD83D\uDD10 Permissions Dev Panel"),
|
|
172
|
+
React.createElement("div", null,
|
|
173
|
+
React.createElement("button", { style: PANEL_STYLES.button, onClick: () => devStore.clearEvaluations() }, "Clear"),
|
|
174
|
+
React.createElement("button", { style: PANEL_STYLES.button, onClick: () => devStore.setOpen(false) }, "\u2715"))),
|
|
175
|
+
React.createElement("div", { style: PANEL_STYLES.tabs },
|
|
176
|
+
React.createElement("div", { style: {
|
|
177
|
+
...PANEL_STYLES.tab,
|
|
178
|
+
...(activeTab === 'evaluations' ? PANEL_STYLES.activeTab : {}),
|
|
179
|
+
}, onClick: () => setActiveTab('evaluations') },
|
|
180
|
+
"Evaluations (",
|
|
181
|
+
state.evaluations.length,
|
|
182
|
+
")"),
|
|
183
|
+
React.createElement("div", { style: {
|
|
184
|
+
...PANEL_STYLES.tab,
|
|
185
|
+
...(activeTab === 'overrides' ? PANEL_STYLES.activeTab : {}),
|
|
186
|
+
}, onClick: () => setActiveTab('overrides') }, "Overrides"),
|
|
187
|
+
React.createElement("div", { style: {
|
|
188
|
+
...PANEL_STYLES.tab,
|
|
189
|
+
...(activeTab === 'context' ? PANEL_STYLES.activeTab : {}),
|
|
190
|
+
}, onClick: () => setActiveTab('context') }, "Context")),
|
|
191
|
+
React.createElement("div", { style: PANEL_STYLES.content },
|
|
192
|
+
activeTab === 'evaluations' && (React.createElement(EvaluationsTab, { evaluations: state.evaluations })),
|
|
193
|
+
activeTab === 'overrides' && (React.createElement(OverridesTab, { roles: context.roles, permissions: context.permissions, flags: context.flags, effectiveRoles: effectiveRoles, effectivePermissions: effectivePermissions, effectiveFlags: effectiveFlags })),
|
|
194
|
+
activeTab === 'context' && (React.createElement(ContextTab, { user: context.user, roles: effectiveRoles, permissions: effectivePermissions, flags: effectiveFlags })))))));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Evaluations tab content
|
|
198
|
+
*/
|
|
199
|
+
function EvaluationsTab({ evaluations }) {
|
|
200
|
+
if (evaluations.length === 0) {
|
|
201
|
+
return React.createElement("div", { style: { color: '#888' } }, "No permission checks yet...");
|
|
202
|
+
}
|
|
203
|
+
return (React.createElement("div", null, evaluations.map((evaluation) => (React.createElement("div", { key: evaluation.id, style: {
|
|
204
|
+
...PANEL_STYLES.evaluation,
|
|
205
|
+
...(evaluation.allowed ? PANEL_STYLES.allowed : PANEL_STYLES.denied),
|
|
206
|
+
} },
|
|
207
|
+
React.createElement("div", { style: { marginBottom: '4px' } },
|
|
208
|
+
React.createElement("strong", { style: { color: evaluation.allowed ? '#4caf50' : '#f44336' } }, evaluation.allowed ? '✓ ALLOWED' : '✗ DENIED'),
|
|
209
|
+
React.createElement("span", { style: { marginLeft: '8px', color: '#d4d4d4' } }, typeof evaluation.check === 'string'
|
|
210
|
+
? evaluation.check
|
|
211
|
+
: Array.isArray(evaluation.check)
|
|
212
|
+
? evaluation.check.join(', ')
|
|
213
|
+
: 'inline function')),
|
|
214
|
+
React.createElement("div", { style: PANEL_STYLES.timestamp },
|
|
215
|
+
new Date(evaluation.timestamp).toLocaleTimeString(),
|
|
216
|
+
evaluation.mode && ` • mode: ${evaluation.mode}`),
|
|
217
|
+
React.createElement("div", { style: { marginTop: '6px' } }, evaluation.ruleResults.map((result, idx) => (React.createElement("span", { key: idx, style: {
|
|
218
|
+
...PANEL_STYLES.badge,
|
|
219
|
+
...(result.result ? PANEL_STYLES.successBadge : PANEL_STYLES.errorBadge),
|
|
220
|
+
}, title: `${result.duration.toFixed(2)}ms${result.error ? ` - ${result.error}` : ''}` },
|
|
221
|
+
result.rule,
|
|
222
|
+
": ",
|
|
223
|
+
result.result ? '✓' : '✗')))),
|
|
224
|
+
evaluation.resource && (React.createElement("div", { style: { marginTop: '4px', fontSize: '10px', color: '#888' } },
|
|
225
|
+
"Resource: ",
|
|
226
|
+
JSON.stringify(evaluation.resource).slice(0, 50),
|
|
227
|
+
"...")))))));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Overrides tab content
|
|
231
|
+
*/
|
|
232
|
+
function OverridesTab({ roles, permissions, flags, effectiveRoles, effectivePermissions, effectiveFlags, }) {
|
|
233
|
+
const [newRole, setNewRole] = useState('');
|
|
234
|
+
const [newPermission, setNewPermission] = useState('');
|
|
235
|
+
const [newFlag, setNewFlag] = useState('');
|
|
236
|
+
// Collect all unique roles and permissions from evaluations
|
|
237
|
+
const allRoles = useMemo(() => {
|
|
238
|
+
const rolesSet = new Set([...roles, ...effectiveRoles]);
|
|
239
|
+
return Array.from(rolesSet).sort();
|
|
240
|
+
}, [roles, effectiveRoles]);
|
|
241
|
+
const allPermissions = useMemo(() => {
|
|
242
|
+
const permsSet = new Set([...permissions, ...effectivePermissions]);
|
|
243
|
+
return Array.from(permsSet).sort();
|
|
244
|
+
}, [permissions, effectivePermissions]);
|
|
245
|
+
return (React.createElement("div", null,
|
|
246
|
+
React.createElement("button", { style: {
|
|
247
|
+
...PANEL_STYLES.button,
|
|
248
|
+
width: '100%',
|
|
249
|
+
marginBottom: '16px',
|
|
250
|
+
}, onClick: () => devStore.resetOverrides() }, "Reset All Overrides"),
|
|
251
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
252
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle }, "Roles"),
|
|
253
|
+
allRoles.map((role) => (React.createElement("label", { key: role, style: PANEL_STYLES.label },
|
|
254
|
+
React.createElement("input", { type: "checkbox", style: PANEL_STYLES.checkbox, checked: effectiveRoles.includes(role), onChange: () => devStore.toggleRole(role, roles) }),
|
|
255
|
+
role))),
|
|
256
|
+
React.createElement("input", { type: "text", style: PANEL_STYLES.input, placeholder: "Add new role...", value: newRole, onChange: (e) => setNewRole(e.target.value), onKeyPress: (e) => {
|
|
257
|
+
if (e.key === 'Enter' && newRole) {
|
|
258
|
+
devStore.toggleRole(newRole, roles);
|
|
259
|
+
setNewRole('');
|
|
260
|
+
}
|
|
261
|
+
} })),
|
|
262
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
263
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle }, "Permissions"),
|
|
264
|
+
allPermissions.map((permission) => (React.createElement("label", { key: permission, style: PANEL_STYLES.label },
|
|
265
|
+
React.createElement("input", { type: "checkbox", style: PANEL_STYLES.checkbox, checked: effectivePermissions.includes(permission), onChange: () => devStore.togglePermission(permission, permissions) }),
|
|
266
|
+
permission))),
|
|
267
|
+
React.createElement("input", { type: "text", style: PANEL_STYLES.input, placeholder: "Add new permission...", value: newPermission, onChange: (e) => setNewPermission(e.target.value), onKeyPress: (e) => {
|
|
268
|
+
if (e.key === 'Enter' && newPermission) {
|
|
269
|
+
devStore.togglePermission(newPermission, permissions);
|
|
270
|
+
setNewPermission('');
|
|
271
|
+
}
|
|
272
|
+
} })),
|
|
273
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
274
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle }, "Feature Flags"),
|
|
275
|
+
Object.entries(effectiveFlags).map(([flag, value]) => (React.createElement("label", { key: flag, style: PANEL_STYLES.label },
|
|
276
|
+
React.createElement("input", { type: "checkbox", style: PANEL_STYLES.checkbox, checked: value, onChange: () => devStore.toggleFlag(flag, flags) }),
|
|
277
|
+
flag))),
|
|
278
|
+
React.createElement("input", { type: "text", style: PANEL_STYLES.input, placeholder: "Add new flag...", value: newFlag, onChange: (e) => setNewFlag(e.target.value), onKeyPress: (e) => {
|
|
279
|
+
if (e.key === 'Enter' && newFlag) {
|
|
280
|
+
devStore.toggleFlag(newFlag, flags);
|
|
281
|
+
setNewFlag('');
|
|
282
|
+
}
|
|
283
|
+
} }))));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Context tab content
|
|
287
|
+
*/
|
|
288
|
+
function ContextTab({ user, roles, permissions, flags, }) {
|
|
289
|
+
return (React.createElement("div", null,
|
|
290
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
291
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle }, "User"),
|
|
292
|
+
React.createElement("pre", { style: { fontSize: '10px', overflow: 'auto' } }, JSON.stringify(user, null, 2))),
|
|
293
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
294
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle },
|
|
295
|
+
"Roles (",
|
|
296
|
+
roles.length,
|
|
297
|
+
")"),
|
|
298
|
+
React.createElement("div", null, roles.length > 0 ? roles.join(', ') : 'None')),
|
|
299
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
300
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle },
|
|
301
|
+
"Permissions (",
|
|
302
|
+
permissions.length,
|
|
303
|
+
")"),
|
|
304
|
+
React.createElement("div", null, permissions.length > 0 ? permissions.join(', ') : 'None')),
|
|
305
|
+
React.createElement("div", { style: PANEL_STYLES.section },
|
|
306
|
+
React.createElement("div", { style: PANEL_STYLES.sectionTitle }, "Feature Flags"),
|
|
307
|
+
React.createElement("pre", { style: { fontSize: '10px' } }, JSON.stringify(flags, null, 2)))));
|
|
308
|
+
}
|