hazo_auth 4.6.2 → 4.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1102,6 +1102,9 @@ The `UserManagementLayout` component provides a comprehensive admin interface fo
1102
1102
  - `admin_user_management` - Access to Users tab
1103
1103
  - `admin_role_management` - Access to Roles tab
1104
1104
  - `admin_permission_management` - Access to Permissions tab
1105
+ - `admin_scope_hierarchy_management` - Access to Scope Hierarchy tab (HRBAC)
1106
+ - `admin_system` - Access to Scope Labels tab (HRBAC)
1107
+ - `admin_user_scope_assignment` - Access to User Scopes tab (HRBAC)
1105
1108
 
1106
1109
  **Required API Routes:**
1107
1110
  The `UserManagementLayout` component requires the following API routes to be created in your project:
@@ -1735,7 +1738,8 @@ Users assigned to a higher-level scope automatically have access to all descenda
1735
1738
 
1736
1739
  ### Required Permissions for Management
1737
1740
 
1738
- - `admin_scope_hierarchy_management` - Manage scopes and scope labels
1741
+ - `admin_scope_hierarchy_management` - Manage scope hierarchy (create, edit, delete scopes)
1742
+ - `admin_system` - System-level administration (scope labels configuration)
1739
1743
  - `admin_user_scope_assignment` - Assign scopes to users
1740
1744
  - `admin_test_access` - Access the RBAC/HRBAC test tool
1741
1745
 
@@ -1743,15 +1747,19 @@ Add these to your `application_permission_list_defaults` in `hazo_auth_config.in
1743
1747
 
1744
1748
  ```ini
1745
1749
  [hazo_auth__user_management]
1746
- application_permission_list_defaults = admin_user_management,admin_role_management,admin_permission_management,admin_scope_hierarchy_management,admin_user_scope_assignment,admin_test_access
1750
+ application_permission_list_defaults = admin_user_management,admin_role_management,admin_permission_management,admin_scope_hierarchy_management,admin_system,admin_user_scope_assignment,admin_test_access
1747
1751
  ```
1748
1752
 
1749
1753
  ### User Management UI
1750
1754
 
1751
1755
  When HRBAC is enabled and the user has appropriate permissions, three new tabs appear in the User Management layout:
1752
- - **Scope Hierarchy** - Create, edit, and delete scopes at each level
1753
- - **Scope Labels** - Customize labels for scope levels per organization
1754
- - **User Scopes** - Assign and remove scope assignments for users
1756
+ - **Scope Hierarchy** - Create, edit, and delete scopes at each level (requires `admin_scope_hierarchy_management`)
1757
+ - **Scope Labels** - Customize labels for scope levels per organization (requires `admin_system`)
1758
+ - **User Scopes** - Assign and remove scope assignments for users (requires `admin_user_scope_assignment`)
1759
+
1760
+ **Organization Assignment (when multi-tenancy enabled):**
1761
+ - **Global admins** (`hazo_org_global_admin` permission) can assign users to any organization
1762
+ - **Non-global admins** can only assign users to organizations within their own org tree (filtered by `root_org_id`)
1755
1763
 
1756
1764
  ### RBAC/HRBAC Test Tool
1757
1765
 
@@ -0,0 +1,94 @@
1
+ // file_description: server-only helper to read app permissions configuration from hazo_auth_config.ini
2
+ // This allows consuming apps to declare their required permissions with descriptions for debugging
3
+ // section: imports
4
+ import { read_config_section } from "./config/config_loader.server.js";
5
+
6
+ // section: types
7
+
8
+ /**
9
+ * A single app permission declaration
10
+ */
11
+ export type AppPermission = {
12
+ permission_name: string;
13
+ description: string;
14
+ };
15
+
16
+ /**
17
+ * App permissions configuration
18
+ */
19
+ export type AppPermissionsConfig = {
20
+ permissions: AppPermission[];
21
+ permissions_map: Map<string, string>; // permission_name -> description
22
+ };
23
+
24
+ // section: helpers
25
+
26
+ /**
27
+ * Reads app permissions configuration from hazo_auth_config.ini file
28
+ * Format in INI file:
29
+ * [hazo_auth__app_permissions]
30
+ * app_permission_1 = permission_name:Description for debugging
31
+ * app_permission_2 = another_perm:Another description
32
+ *
33
+ * @returns App permissions configuration with list and map of permissions
34
+ */
35
+ export function get_app_permissions_config(): AppPermissionsConfig {
36
+ const section = read_config_section("hazo_auth__app_permissions");
37
+ const permissions: AppPermission[] = [];
38
+ const permissions_map = new Map<string, string>();
39
+
40
+ if (section) {
41
+ // Iterate through all keys in section that start with "app_permission_"
42
+ for (const [key, value] of Object.entries(section)) {
43
+ if (key.startsWith("app_permission_") && typeof value === "string") {
44
+ const colonIndex = value.indexOf(":");
45
+ if (colonIndex > 0) {
46
+ const permission_name = value.substring(0, colonIndex).trim();
47
+ const description = value.substring(colonIndex + 1).trim();
48
+ if (permission_name && description) {
49
+ permissions.push({ permission_name, description });
50
+ permissions_map.set(permission_name, description);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ return {
58
+ permissions,
59
+ permissions_map,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Gets the description for a specific permission
65
+ * @param permission_name - The permission name to look up
66
+ * @returns Description or undefined if not found in app permissions config
67
+ */
68
+ export function get_app_permission_description(
69
+ permission_name: string,
70
+ ): string | undefined {
71
+ const config = get_app_permissions_config();
72
+ return config.permissions_map.get(permission_name);
73
+ }
74
+
75
+ /**
76
+ * Gets descriptions for multiple permissions
77
+ * @param permission_names - Array of permission names to look up
78
+ * @returns Map of permission names to descriptions (only includes found permissions)
79
+ */
80
+ export function get_app_permission_descriptions(
81
+ permission_names: string[],
82
+ ): Map<string, string> {
83
+ const config = get_app_permissions_config();
84
+ const result = new Map<string, string>();
85
+
86
+ for (const name of permission_names) {
87
+ const description = config.permissions_map.get(name);
88
+ if (description) {
89
+ result.set(name, description);
90
+ }
91
+ }
92
+
93
+ return result;
94
+ }
@@ -100,6 +100,7 @@ export type HazoAuthOptions = {
100
100
  /**
101
101
  * Custom error class for permission denials
102
102
  * Includes technical and user-friendly error messages
103
+ * Optionally includes permission descriptions for debugging
103
104
  */
104
105
  export class PermissionError extends Error {
105
106
  constructor(
@@ -107,6 +108,7 @@ export class PermissionError extends Error {
107
108
  public user_permissions: string[],
108
109
  public required_permissions: string[],
109
110
  public user_friendly_message?: string,
111
+ public permission_descriptions?: Map<string, string>,
110
112
  ) {
111
113
  super(`Missing permissions: ${missing_permissions.join(", ")}`);
112
114
  this.name = "PermissionError";
@@ -18,6 +18,7 @@ import { is_valid_scope_level, type ScopeLevel } from "../services/scope_service
18
18
  import { is_multi_tenancy_enabled, get_multi_tenancy_config } from "../multi_tenancy_config.server.js";
19
19
  import { get_org_cache, type OrgCacheEntry } from "./org_cache.js";
20
20
  import { get_cookie_name, BASE_COOKIE_NAMES } from "../cookies_config.server.js";
21
+ import { get_app_permission_descriptions } from "../app_permissions_config.server.js";
21
22
 
22
23
  // section: helpers
23
24
 
@@ -544,12 +545,15 @@ export async function hazo_get_auth(
544
545
  missing_permissions,
545
546
  config,
546
547
  );
548
+ // Include permission descriptions for debugging
549
+ const permission_descriptions = get_app_permission_descriptions(missing_permissions);
547
550
 
548
551
  throw new PermissionError(
549
552
  missing_permissions,
550
553
  permissions,
551
554
  options.required_permissions,
552
555
  friendly_message,
556
+ permission_descriptions,
553
557
  );
554
558
  }
555
559
  }
@@ -26,6 +26,7 @@ export declare function GET(request: NextRequest): Promise<NextResponse<{
26
26
  profile_picture_url: {} | null;
27
27
  profile_source: {} | null;
28
28
  user_type: string | null;
29
+ app_user_data: Record<string, unknown> | null;
29
30
  org_id: string | null | undefined;
30
31
  root_org_id: string | null | undefined;
31
32
  }[];
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/hazo_auth/user_management/users/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsBxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;IA+E7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA2L/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/hazo_auth/user_management/users/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsBxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;IAgG7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA2L/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
@@ -53,21 +53,39 @@ export async function GET(request) {
53
53
  user_types_enabled,
54
54
  available_user_types,
55
55
  multi_tenancy_enabled,
56
- users: users.map((user) => ({
57
- id: user.id,
58
- name: user.name || null,
59
- email_address: user.email_address,
60
- email_verified: user.email_verified || false,
61
- is_active: user.is_active !== false,
62
- last_logon: user.last_logon || null,
63
- created_at: user.created_at || null,
64
- profile_picture_url: user.profile_picture_url || null,
65
- profile_source: user.profile_source || null,
66
- user_type: user.user_type || null,
67
- // Include org info when multi-tenancy is enabled
68
- org_id: multi_tenancy_enabled ? user.org_id || null : undefined,
69
- root_org_id: multi_tenancy_enabled ? user.root_org_id || null : undefined,
70
- })),
56
+ users: users.map((user) => {
57
+ // Parse app_user_data if it's a string (JSON stored as TEXT in DB)
58
+ let app_user_data = null;
59
+ if (user.app_user_data) {
60
+ if (typeof user.app_user_data === "string") {
61
+ try {
62
+ app_user_data = JSON.parse(user.app_user_data);
63
+ }
64
+ catch (_a) {
65
+ app_user_data = null;
66
+ }
67
+ }
68
+ else {
69
+ app_user_data = user.app_user_data;
70
+ }
71
+ }
72
+ return {
73
+ id: user.id,
74
+ name: user.name || null,
75
+ email_address: user.email_address,
76
+ email_verified: user.email_verified || false,
77
+ is_active: user.is_active !== false,
78
+ last_logon: user.last_logon || null,
79
+ created_at: user.created_at || null,
80
+ profile_picture_url: user.profile_picture_url || null,
81
+ profile_source: user.profile_source || null,
82
+ user_type: user.user_type || null,
83
+ app_user_data,
84
+ // Include org info when multi-tenancy is enabled
85
+ org_id: multi_tenancy_enabled ? user.org_id || null : undefined,
86
+ root_org_id: multi_tenancy_enabled ? user.root_org_id || null : undefined,
87
+ };
88
+ }),
71
89
  }, { status: 200 });
72
90
  }
73
91
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"standalone_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/standalone_layout_wrapper.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,OAAqB,EACrB,WAA8E,EAC9E,gBAAgB,EAChB,gBAAgB,EAChB,WAAkB,EAClB,eAAsB,EACtB,MAAM,EACN,cAAqB,GACtB,EAAE,4BAA4B,2CAmD9B"}
1
+ {"version":3,"file":"standalone_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/standalone_layout_wrapper.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,OAAqB,EACrB,WAA8E,EAC9E,gBAAgB,EAChB,gBAAgB,EAChB,WAAkB,EAClB,eAAsB,EACtB,MAAM,EACN,cAAqB,GACtB,EAAE,4BAA4B,2CAyD9B"}
@@ -11,5 +11,8 @@ export function StandaloneLayoutWrapper({ children, heading = "hazo auth", descr
11
11
  const hasNavbar = navbar !== null && navbar !== undefined;
12
12
  // Calculate navbar height for vertical centering offset
13
13
  const navbarHeight = hasNavbar ? ((_a = navbar === null || navbar === void 0 ? void 0 : navbar.height) !== null && _a !== void 0 ? _a : 64) : 0;
14
- return (_jsxs("div", { className: cn("cls_standalone_layout_wrapper flex min-h-screen w-full flex-col bg-background", wrapperClassName), children: [hasNavbar && _jsx(AuthNavbar, Object.assign({}, navbar)), _jsx("div", { className: cn("cls_standalone_layout_content_area flex-1", verticalCenter && "flex items-center justify-center"), style: verticalCenter ? { minHeight: `calc(100vh - ${navbarHeight}px)` } : undefined, children: _jsxs("div", { className: cn("cls_standalone_layout_content mx-auto flex w-full max-w-5xl flex-col gap-8 p-6", contentClassName), children: [(showHeading || showDescription) && (_jsxs("div", { className: "cls_standalone_layout_header text-center", children: [showHeading && (_jsx("h1", { className: "cls_standalone_layout_title text-2xl font-semibold tracking-tight text-foreground", children: heading })), showDescription && (_jsx("p", { className: "cls_standalone_layout_description mt-2 text-sm text-muted-foreground", children: description }))] })), _jsx("div", { className: "cls_standalone_layout_body", children: children })] }) })] }));
14
+ return (
15
+ // Outer wrapper: Consuming app's wrapperClassName applied here for theming/background
16
+ // This is isolated from the internal layout structure
17
+ _jsx("div", { className: cn("cls_standalone_layout_outer min-h-screen w-full bg-background", wrapperClassName), children: _jsxs("div", { className: "cls_standalone_layout_wrapper flex min-h-screen w-full flex-col", children: [hasNavbar && _jsx(AuthNavbar, Object.assign({}, navbar)), _jsx("div", { className: cn("cls_standalone_layout_content_area flex-1", verticalCenter && "flex items-center justify-center"), style: verticalCenter ? { minHeight: `calc(100vh - ${navbarHeight}px)` } : undefined, children: _jsxs("div", { className: cn("cls_standalone_layout_content mx-auto flex w-full max-w-5xl flex-col gap-8 p-6", contentClassName), children: [(showHeading || showDescription) && (_jsxs("div", { className: "cls_standalone_layout_header text-center", children: [showHeading && (_jsx("h1", { className: "cls_standalone_layout_title text-2xl font-semibold tracking-tight text-foreground", children: heading })), showDescription && (_jsx("p", { className: "cls_standalone_layout_description mt-2 text-sm text-muted-foreground", children: description }))] })), _jsx("div", { className: "cls_standalone_layout_body", children: children })] }) })] }) }));
15
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/user_management/index.tsx"],"names":[],"mappings":"AA2DA,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,cAAc,EAAE,CAAC;CACvC,CAAC;AA+BF;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,YAAoB,EAAE,mBAA2B,EAAE,gBAAwB,EAAE,kBAAuB,EAAE,EAAE,yBAAyB,2CA+nDlL"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/user_management/index.tsx"],"names":[],"mappings":"AA2DA,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,cAAc,EAAE,CAAC;CACvC,CAAC;AA+JF;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,YAAoB,EAAE,mBAA2B,EAAE,gBAAwB,EAAE,kBAAuB,EAAE,EAAE,yBAAyB,2CAwqDlL"}
@@ -1,7 +1,7 @@
1
1
  // file_description: User Management layout component with tabs for managing users, roles, permissions, and HRBAC scopes
2
2
  // section: client_directive
3
3
  "use client";
4
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
4
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
5
5
  // section: imports
6
6
  import { useState, useEffect, useCallback } from "react";
7
7
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
@@ -20,11 +20,42 @@ import { ScopeHierarchyTab } from "./components/scope_hierarchy_tab";
20
20
  import { ScopeLabelsTab } from "./components/scope_labels_tab";
21
21
  import { UserScopesTab } from "./components/user_scopes_tab";
22
22
  import { OrgHierarchyTab } from "./components/org_hierarchy_tab";
23
- import { UserX, KeyRound, Edit, Trash2, Loader2, CircleCheck, CircleX, Plus, UserPlus, Building2 } from "lucide-react";
23
+ import { UserX, KeyRound, Edit, Trash2, Loader2, CircleCheck, CircleX, Plus, UserPlus, Building2, ChevronRight, ChevronDown } from "lucide-react";
24
24
  import { TreeView } from "../../ui/tree-view";
25
25
  import { toast } from "sonner";
26
26
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
27
27
  import { useHazoAuthConfig } from "../../../contexts/hazo_auth_provider";
28
+ // section: helper_components
29
+ /**
30
+ * Recursive JSON tree node component for displaying app_user_data.
31
+ * Renders objects and arrays as expandable/collapsible nodes.
32
+ */
33
+ function JsonTreeNode({ data, keyName, level = 0, defaultExpanded = true, }) {
34
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
35
+ const indent = level * 16;
36
+ // Handle null/undefined
37
+ if (data === null || data === undefined) {
38
+ return (_jsxs("div", { className: "flex items-center py-0.5", style: { paddingLeft: indent }, children: [keyName && _jsxs("span", { className: "text-muted-foreground", children: [keyName, ":"] }), _jsx("span", { className: "ml-1 text-slate-500 italic", children: "null" })] }));
39
+ }
40
+ // Handle arrays
41
+ if (Array.isArray(data)) {
42
+ const hasChildren = data.length > 0;
43
+ return (_jsxs("div", { children: [_jsxs("div", { className: `flex items-center py-0.5 ${hasChildren ? "cursor-pointer hover:bg-muted/50 rounded" : ""}`, style: { paddingLeft: indent }, onClick: () => hasChildren && setIsExpanded(!isExpanded), children: [hasChildren ? (isExpanded ? (_jsx(ChevronDown, { className: "h-3 w-3 mr-1 text-muted-foreground shrink-0" })) : (_jsx(ChevronRight, { className: "h-3 w-3 mr-1 text-muted-foreground shrink-0" }))) : (_jsx("span", { className: "w-4 mr-1" })), keyName && _jsx("span", { className: "text-muted-foreground", children: keyName }), _jsxs("span", { className: "ml-1 text-slate-500", children: ["[", data.length, "]"] })] }), isExpanded && hasChildren && (_jsx("div", { className: "border-l border-slate-200 ml-2", style: { marginLeft: indent + 8 }, children: data.map((item, index) => (_jsx(JsonTreeNode, { data: item, keyName: `[${index}]`, level: 0, defaultExpanded: defaultExpanded }, index))) }))] }));
44
+ }
45
+ // Handle objects
46
+ if (typeof data === "object") {
47
+ const entries = Object.entries(data);
48
+ const hasChildren = entries.length > 0;
49
+ return (_jsxs("div", { children: [_jsxs("div", { className: `flex items-center py-0.5 ${hasChildren ? "cursor-pointer hover:bg-muted/50 rounded" : ""}`, style: { paddingLeft: indent }, onClick: () => hasChildren && setIsExpanded(!isExpanded), children: [hasChildren ? (isExpanded ? (_jsx(ChevronDown, { className: "h-3 w-3 mr-1 text-muted-foreground shrink-0" })) : (_jsx(ChevronRight, { className: "h-3 w-3 mr-1 text-muted-foreground shrink-0" }))) : (_jsx("span", { className: "w-4 mr-1" })), keyName && _jsx("span", { className: "text-muted-foreground", children: keyName }), keyName && _jsx("span", { className: "ml-1 text-slate-500", children: `{${entries.length}}` })] }), isExpanded && hasChildren && (_jsx("div", { className: "border-l border-slate-200 ml-2", style: { marginLeft: indent + 8 }, children: entries.map(([key, value]) => (_jsx(JsonTreeNode, { data: value, keyName: key, level: 0, defaultExpanded: defaultExpanded }, key))) }))] }));
50
+ }
51
+ // Handle primitives (string, number, boolean)
52
+ const valueStr = String(data);
53
+ const displayValue = valueStr.length > 80 ? valueStr.substring(0, 80) + "..." : valueStr;
54
+ const valueColor = typeof data === "number" ? "text-blue-600" :
55
+ typeof data === "boolean" ? "text-orange-600" :
56
+ "text-green-700";
57
+ return (_jsxs("div", { className: "flex items-center py-0.5", style: { paddingLeft: indent }, children: [_jsx("span", { className: "w-4 mr-1" }), keyName && _jsxs("span", { className: "text-muted-foreground", children: [keyName, ":"] }), _jsx("span", { className: `ml-1 ${valueColor}`, children: typeof data === "string" ? `"${displayValue}"` : displayValue })] }));
58
+ }
28
59
  // section: component
29
60
  /**
30
61
  * User Management layout component with tabs for managing users, roles, permissions, and HRBAC scopes
@@ -55,12 +86,14 @@ export function UserManagementLayout({ className, hrbacEnabled = false, multiTen
55
86
  authResult.permissions.includes("hazo_perm_org_management");
56
87
  const hasOrgGlobalAdminPermission = authResult.authenticated &&
57
88
  authResult.permissions.includes("hazo_org_global_admin");
89
+ const hasSystemPermission = authResult.authenticated &&
90
+ authResult.permissions.includes("admin_system");
58
91
  // Determine which tabs to show
59
92
  const showUsersTab = hasUserManagementPermission;
60
93
  const showRolesTab = hasRoleManagementPermission;
61
94
  const showPermissionsTab = hasPermissionManagementPermission;
62
95
  const showScopeHierarchyTab = hrbacEnabled && hasScopeHierarchyPermission;
63
- const showScopeLabelsTab = hrbacEnabled && hasScopeHierarchyPermission;
96
+ const showScopeLabelsTab = hrbacEnabled && hasSystemPermission;
64
97
  const showUserScopesTab = hrbacEnabled && hasUserScopeAssignmentPermission;
65
98
  const showOrgsTab = multiTenancyEnabled && hasOrgManagementPermission;
66
99
  const hasAnyPermission = showUsersTab || showRolesTab || showPermissionsTab || showScopeHierarchyTab || showScopeLabelsTab || showUserScopesTab || showOrgsTab;
@@ -121,13 +154,27 @@ export function UserManagementLayout({ className, hrbacEnabled = false, multiTen
121
154
  void loadUsers();
122
155
  }, [showUsersTab, loadUsers]);
123
156
  // Load organizations (only if multi-tenancy is enabled and user has permission)
157
+ // Non-global admins can only see orgs in their tree
124
158
  useEffect(() => {
125
159
  if (!multiTenancyEnabled || !showUsersTab) {
126
160
  return;
127
161
  }
128
162
  const loadOrgs = async () => {
163
+ var _a, _b;
129
164
  try {
130
- const response = await fetch(`${apiBasePath}/org_management/orgs`);
165
+ const params = new URLSearchParams();
166
+ // Non-global admins can only see orgs in their tree
167
+ if (!hasOrgGlobalAdminPermission && authResult.authenticated) {
168
+ const userRootOrgId = ((_a = authResult.user) === null || _a === void 0 ? void 0 : _a.root_org_id) || ((_b = authResult.user) === null || _b === void 0 ? void 0 : _b.org_id);
169
+ if (userRootOrgId) {
170
+ params.set("root_org_id", userRootOrgId);
171
+ }
172
+ }
173
+ const queryString = params.toString();
174
+ const url = queryString
175
+ ? `${apiBasePath}/org_management/orgs?${queryString}`
176
+ : `${apiBasePath}/org_management/orgs`;
177
+ const response = await fetch(url);
131
178
  const data = await response.json();
132
179
  if (data.success && Array.isArray(data.orgs)) {
133
180
  setAvailableOrgs(data.orgs.map((org) => ({
@@ -143,7 +190,7 @@ export function UserManagementLayout({ className, hrbacEnabled = false, multiTen
143
190
  }
144
191
  };
145
192
  void loadOrgs();
146
- }, [multiTenancyEnabled, showUsersTab, apiBasePath]);
193
+ }, [multiTenancyEnabled, showUsersTab, apiBasePath, hasOrgGlobalAdminPermission, authResult]);
147
194
  // Load permissions (only if user has permission)
148
195
  useEffect(() => {
149
196
  if (!showPermissionsTab) {
@@ -700,7 +747,7 @@ export function UserManagementLayout({ className, hrbacEnabled = false, multiTen
700
747
  minute: "2-digit",
701
748
  second: "2-digit",
702
749
  timeZoneName: "short",
703
- }) })) : (_jsx("span", { className: "text-muted-foreground", children: "-" })) })] }), userTypesEnabled && (_jsxs("div", { className: "cls_user_management_user_detail_field_user_type flex flex-col gap-2", children: [_jsx(Label, { className: "cls_user_management_user_detail_label font-semibold", children: "User Type" }), _jsxs("div", { className: "cls_user_management_user_detail_user_type_value flex items-center gap-2", children: [_jsxs(Select, { value: selectedUser.user_type || "__none__", onValueChange: (value) => handleUserTypeChange(value), disabled: userTypeUpdateLoading, children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, { placeholder: "Select user type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "None" }), availableUserTypes.map((type) => (_jsx(SelectItem, { value: type.key, children: type.label }, type.key)))] })] }), userTypeUpdateLoading && (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }))] })] })), multiTenancyEnabled && (_jsxs("div", { className: "cls_user_management_user_detail_field_org flex flex-col gap-2", children: [_jsx(Label, { className: "cls_user_management_user_detail_label font-semibold", children: "Organization" }), _jsxs("div", { className: "cls_user_management_user_detail_org_value flex items-center gap-2", children: [_jsxs(Select, { value: selectedUser.org_id || "__none__", onValueChange: (value) => handleOrgChange(value), disabled: orgUpdateLoading, children: [_jsx(SelectTrigger, { className: "w-64", children: _jsx(SelectValue, { placeholder: "Select organization" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "None" }), availableOrgs.map((org) => (_jsx(SelectItem, { value: org.id, children: org.name }, org.id)))] })] }), orgUpdateLoading && (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }))] })] }))] })) }), _jsx(DialogFooter, { className: "cls_user_management_user_detail_dialog_footer", children: _jsx(Button, { onClick: () => setUserDetailDialogOpen(false), variant: "outline", className: "cls_user_management_user_detail_dialog_close", children: "Close" }) })] }) }), _jsx(Dialog, { open: assignRolesDialogOpen, onOpenChange: setAssignRolesDialogOpen, children: _jsxs(DialogContent, { className: "cls_user_management_assign_roles_dialog max-w-4xl max-h-[80vh] overflow-y-auto", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Assign Roles to User" }), _jsxs(DialogDescription, { children: ["Select roles to assign to ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), ". Check the roles you want to assign, then click Save."] })] }), _jsx("div", { className: "cls_user_management_assign_roles_dialog_content py-4", children: _jsx(RolesMatrix, { add_button_enabled: false, role_name_selection_enabled: true, permissions_read_only: true, show_save_cancel: true, user_id: selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.id, onSave: (data) => {
750
+ }) })) : (_jsx("span", { className: "text-muted-foreground", children: "-" })) })] }), userTypesEnabled && (_jsxs("div", { className: "cls_user_management_user_detail_field_user_type flex flex-col gap-2", children: [_jsx(Label, { className: "cls_user_management_user_detail_label font-semibold", children: "User Type" }), _jsxs("div", { className: "cls_user_management_user_detail_user_type_value flex items-center gap-2", children: [_jsxs(Select, { value: selectedUser.user_type || "__none__", onValueChange: (value) => handleUserTypeChange(value), disabled: userTypeUpdateLoading, children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, { placeholder: "Select user type" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "None" }), availableUserTypes.map((type) => (_jsx(SelectItem, { value: type.key, children: type.label }, type.key)))] })] }), userTypeUpdateLoading && (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }))] })] })), multiTenancyEnabled && (_jsxs("div", { className: "cls_user_management_user_detail_field_org flex flex-col gap-2", children: [_jsx(Label, { className: "cls_user_management_user_detail_label font-semibold", children: "Organization" }), _jsxs("div", { className: "cls_user_management_user_detail_org_value flex items-center gap-2", children: [_jsxs(Select, { value: selectedUser.org_id || "__none__", onValueChange: (value) => handleOrgChange(value), disabled: orgUpdateLoading, children: [_jsx(SelectTrigger, { className: "w-64", children: _jsx(SelectValue, { placeholder: "Select organization" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "None" }), availableOrgs.map((org) => (_jsx(SelectItem, { value: org.id, children: org.name }, org.id)))] })] }), orgUpdateLoading && (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }))] })] })), _jsxs("div", { className: "cls_user_management_user_detail_field_app_user_data flex flex-col gap-2", children: [_jsx(Label, { className: "cls_user_management_user_detail_label font-semibold", children: "App User Data" }), _jsx("div", { className: "cls_user_management_user_detail_app_user_data_value", children: selectedUser.app_user_data && Object.keys(selectedUser.app_user_data).length > 0 ? (_jsx("div", { className: "border rounded-lg max-h-[200px] overflow-y-auto p-2 bg-slate-50 text-sm font-mono", children: Object.entries(selectedUser.app_user_data).map(([key, value]) => (_jsx(JsonTreeNode, { data: value, keyName: key, level: 0, defaultExpanded: true }, key))) })) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "No app user data" })) })] })] })) }), _jsx(DialogFooter, { className: "cls_user_management_user_detail_dialog_footer", children: _jsx(Button, { onClick: () => setUserDetailDialogOpen(false), variant: "outline", className: "cls_user_management_user_detail_dialog_close", children: "Close" }) })] }) }), _jsx(Dialog, { open: assignRolesDialogOpen, onOpenChange: setAssignRolesDialogOpen, children: _jsxs(DialogContent, { className: "cls_user_management_assign_roles_dialog max-w-4xl max-h-[80vh] overflow-y-auto", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Assign Roles to User" }), _jsxs(DialogDescription, { children: ["Select roles to assign to ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), ". Check the roles you want to assign, then click Save."] })] }), _jsx("div", { className: "cls_user_management_assign_roles_dialog_content py-4", children: _jsx(RolesMatrix, { add_button_enabled: false, role_name_selection_enabled: true, permissions_read_only: true, show_save_cancel: true, user_id: selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.id, onSave: (data) => {
704
751
  // Data is already saved by RolesMatrix component
705
752
  console.log("User roles saved:", data);
706
753
  // Refresh users list to show updated roles
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A single app permission declaration
3
+ */
4
+ export type AppPermission = {
5
+ permission_name: string;
6
+ description: string;
7
+ };
8
+ /**
9
+ * App permissions configuration
10
+ */
11
+ export type AppPermissionsConfig = {
12
+ permissions: AppPermission[];
13
+ permissions_map: Map<string, string>;
14
+ };
15
+ /**
16
+ * Reads app permissions configuration from hazo_auth_config.ini file
17
+ * Format in INI file:
18
+ * [hazo_auth__app_permissions]
19
+ * app_permission_1 = permission_name:Description for debugging
20
+ * app_permission_2 = another_perm:Another description
21
+ *
22
+ * @returns App permissions configuration with list and map of permissions
23
+ */
24
+ export declare function get_app_permissions_config(): AppPermissionsConfig;
25
+ /**
26
+ * Gets the description for a specific permission
27
+ * @param permission_name - The permission name to look up
28
+ * @returns Description or undefined if not found in app permissions config
29
+ */
30
+ export declare function get_app_permission_description(permission_name: string): string | undefined;
31
+ /**
32
+ * Gets descriptions for multiple permissions
33
+ * @param permission_names - Array of permission names to look up
34
+ * @returns Map of permission names to descriptions (only includes found permissions)
35
+ */
36
+ export declare function get_app_permission_descriptions(permission_names: string[]): Map<string, string>;
37
+ //# sourceMappingURL=app_permissions_config.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app_permissions_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/app_permissions_config.server.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAIF;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,oBAAoB,CA0BjE;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,SAAS,CAGpB;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,gBAAgB,EAAE,MAAM,EAAE,GACzB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYrB"}
@@ -0,0 +1,64 @@
1
+ // file_description: server-only helper to read app permissions configuration from hazo_auth_config.ini
2
+ // This allows consuming apps to declare their required permissions with descriptions for debugging
3
+ // section: imports
4
+ import { read_config_section } from "./config/config_loader.server";
5
+ // section: helpers
6
+ /**
7
+ * Reads app permissions configuration from hazo_auth_config.ini file
8
+ * Format in INI file:
9
+ * [hazo_auth__app_permissions]
10
+ * app_permission_1 = permission_name:Description for debugging
11
+ * app_permission_2 = another_perm:Another description
12
+ *
13
+ * @returns App permissions configuration with list and map of permissions
14
+ */
15
+ export function get_app_permissions_config() {
16
+ const section = read_config_section("hazo_auth__app_permissions");
17
+ const permissions = [];
18
+ const permissions_map = new Map();
19
+ if (section) {
20
+ // Iterate through all keys in section that start with "app_permission_"
21
+ for (const [key, value] of Object.entries(section)) {
22
+ if (key.startsWith("app_permission_") && typeof value === "string") {
23
+ const colonIndex = value.indexOf(":");
24
+ if (colonIndex > 0) {
25
+ const permission_name = value.substring(0, colonIndex).trim();
26
+ const description = value.substring(colonIndex + 1).trim();
27
+ if (permission_name && description) {
28
+ permissions.push({ permission_name, description });
29
+ permissions_map.set(permission_name, description);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return {
36
+ permissions,
37
+ permissions_map,
38
+ };
39
+ }
40
+ /**
41
+ * Gets the description for a specific permission
42
+ * @param permission_name - The permission name to look up
43
+ * @returns Description or undefined if not found in app permissions config
44
+ */
45
+ export function get_app_permission_description(permission_name) {
46
+ const config = get_app_permissions_config();
47
+ return config.permissions_map.get(permission_name);
48
+ }
49
+ /**
50
+ * Gets descriptions for multiple permissions
51
+ * @param permission_names - Array of permission names to look up
52
+ * @returns Map of permission names to descriptions (only includes found permissions)
53
+ */
54
+ export function get_app_permission_descriptions(permission_names) {
55
+ const config = get_app_permissions_config();
56
+ const result = new Map();
57
+ for (const name of permission_names) {
58
+ const description = config.permissions_map.get(name);
59
+ if (description) {
60
+ result.set(name, description);
61
+ }
62
+ }
63
+ return result;
64
+ }
@@ -85,13 +85,15 @@ export type HazoAuthOptions = {
85
85
  /**
86
86
  * Custom error class for permission denials
87
87
  * Includes technical and user-friendly error messages
88
+ * Optionally includes permission descriptions for debugging
88
89
  */
89
90
  export declare class PermissionError extends Error {
90
91
  missing_permissions: string[];
91
92
  user_permissions: string[];
92
93
  required_permissions: string[];
93
94
  user_friendly_message?: string | undefined;
94
- constructor(missing_permissions: string[], user_permissions: string[], required_permissions: string[], user_friendly_message?: string | undefined);
95
+ permission_descriptions?: Map<string, string> | undefined;
96
+ constructor(missing_permissions: string[], user_permissions: string[], required_permissions: string[], user_friendly_message?: string | undefined, permission_descriptions?: Map<string, string> | undefined);
95
97
  }
96
98
  /**
97
99
  * Custom error class for scope access denials in HRBAC
@@ -1 +1 @@
1
- {"version":3,"file":"auth_types.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/auth_types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth_types.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/auth_types.ts"],"names":[],"mappings":"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"}
@@ -3,14 +3,16 @@
3
3
  /**
4
4
  * Custom error class for permission denials
5
5
  * Includes technical and user-friendly error messages
6
+ * Optionally includes permission descriptions for debugging
6
7
  */
7
8
  export class PermissionError extends Error {
8
- constructor(missing_permissions, user_permissions, required_permissions, user_friendly_message) {
9
+ constructor(missing_permissions, user_permissions, required_permissions, user_friendly_message, permission_descriptions) {
9
10
  super(`Missing permissions: ${missing_permissions.join(", ")}`);
10
11
  this.missing_permissions = missing_permissions;
11
12
  this.user_permissions = user_permissions;
12
13
  this.required_permissions = required_permissions;
13
14
  this.user_friendly_message = user_friendly_message;
15
+ this.permission_descriptions = permission_descriptions;
14
16
  this.name = "PermissionError";
15
17
  }
16
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hazo_get_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EAAE,cAAc,EAAgB,eAAe,EAAmB,MAAM,cAAc,CAAC;AA4XnG;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAwOzB"}
1
+ {"version":3,"file":"hazo_get_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,KAAK,EAAE,cAAc,EAAgB,eAAe,EAAmB,MAAM,cAAc,CAAC;AA6XnG;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CA2OzB"}
@@ -14,6 +14,7 @@ import { is_valid_scope_level } from "../services/scope_service";
14
14
  import { is_multi_tenancy_enabled, get_multi_tenancy_config } from "../multi_tenancy_config.server";
15
15
  import { get_org_cache } from "./org_cache";
16
16
  import { get_cookie_name, BASE_COOKIE_NAMES } from "../cookies_config.server";
17
+ import { get_app_permission_descriptions } from "../app_permissions_config.server";
17
18
  // section: helpers
18
19
  /**
19
20
  * Parse JSON string to object, returning null on failure
@@ -430,7 +431,9 @@ export async function hazo_get_auth(request, options) {
430
431
  // Throw error if strict mode
431
432
  if (!permission_ok && options.strict) {
432
433
  const friendly_message = get_friendly_error_message(missing_permissions, config);
433
- throw new PermissionError(missing_permissions, permissions, options.required_permissions, friendly_message);
434
+ // Include permission descriptions for debugging
435
+ const permission_descriptions = get_app_permission_descriptions(missing_permissions);
436
+ throw new PermissionError(missing_permissions, permissions, options.required_permissions, friendly_message, permission_descriptions);
434
437
  }
435
438
  }
436
439
  // Check HRBAC scope access if enabled and scope options provided
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "4.6.2",
3
+ "version": "4.6.3",
4
4
  "description": "Zero-config authentication UI components for Next.js with RBAC, OAuth, multi-tenancy, and hierarchical scopes",
5
5
  "keywords": [
6
6
  "authentication",