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 +13 -5
- package/cli-src/lib/app_permissions_config.server.ts +94 -0
- package/cli-src/lib/auth/auth_types.ts +2 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +4 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts +1 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/user_management/users/route.js +33 -15
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.js +4 -1
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +53 -6
- package/dist/lib/app_permissions_config.server.d.ts +37 -0
- package/dist/lib/app_permissions_config.server.d.ts.map +1 -0
- package/dist/lib/app_permissions_config.server.js +64 -0
- package/dist/lib/auth/auth_types.d.ts +3 -1
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +3 -1
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +4 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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,
|
|
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 (
|
|
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+
|
|
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 {
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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":"AAGA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAE9C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,eAAe,CAAC;IAEnC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GACD;IACE,aAAa,EAAE,KAAK,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,EAAE,CAAC;IAChB,aAAa,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF
|
|
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;
|
|
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
|
-
|
|
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
|