hazo_auth 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -5
- package/SETUP_CHECKLIST.md +1 -1
- package/cli-src/lib/app_logger.ts +8 -18
- package/cli-src/lib/auth/auth_types.ts +22 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +25 -1
- package/cli-src/lib/auth/session_token_validator.edge.ts +4 -0
- package/cli-src/lib/config/default_config.ts +36 -0
- package/cli-src/lib/navbar_config.server.ts +129 -0
- package/cli-src/lib/scope_hierarchy_config.server.ts +3 -14
- package/cli-src/lib/services/registration_service.ts +12 -0
- package/cli-src/lib/services/scope_labels_service.ts +21 -21
- package/cli-src/lib/services/scope_service.ts +15 -11
- package/cli-src/lib/services/session_token_service.ts +4 -0
- package/cli-src/lib/ui_shell_config.server.ts +15 -0
- package/cli-src/lib/user_types_config.server.ts +178 -0
- package/cli-src/server/types/app_types.ts +5 -7
- package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/me/route.js +17 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts +26 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.js +315 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts +7 -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 +48 -10
- package/dist/components/layouts/login/hooks/use_login_form.js +2 -2
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +8 -14
- package/dist/components/layouts/rbac_test/index.d.ts +1 -3
- package/dist/components/layouts/rbac_test/index.d.ts.map +1 -1
- package/dist/components/layouts/rbac_test/index.js +2 -2
- package/dist/components/layouts/shared/components/auth_navbar.d.ts +26 -0
- package/dist/components/layouts/shared/components/auth_navbar.d.ts.map +1 -0
- package/dist/components/layouts/shared/components/auth_navbar.js +14 -0
- package/dist/components/layouts/shared/components/auth_page_shell.d.ts +3 -1
- package/dist/components/layouts/shared/components/auth_page_shell.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/auth_page_shell.js +17 -2
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts +6 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.js +7 -2
- package/dist/components/layouts/shared/index.d.ts +2 -0
- package/dist/components/layouts/shared/index.d.ts.map +1 -1
- package/dist/components/layouts/shared/index.js +1 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +3 -2
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +45 -18
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +3 -2
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/scope_labels_tab.js +48 -20
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/user_scopes_tab.js +1 -1
- package/dist/components/layouts/user_management/index.d.ts +11 -3
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +52 -5
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/user-type-badge.d.ts +23 -0
- package/dist/components/ui/user-type-badge.d.ts.map +1 -0
- package/dist/components/ui/user-type-badge.js +42 -0
- package/dist/lib/app_logger.d.ts +3 -9
- package/dist/lib/app_logger.d.ts.map +1 -1
- package/dist/lib/app_logger.js +7 -10
- package/dist/lib/auth/auth_types.d.ts +17 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +11 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +21 -1
- package/dist/lib/config/default_config.d.ts +60 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +34 -0
- package/dist/lib/navbar_config.server.d.ts +36 -0
- package/dist/lib/navbar_config.server.d.ts.map +1 -0
- package/dist/lib/navbar_config.server.js +45 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts +3 -7
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -1
- package/dist/lib/scope_hierarchy_config.server.js +1 -10
- package/dist/lib/services/registration_service.d.ts.map +1 -1
- package/dist/lib/services/registration_service.js +8 -0
- package/dist/lib/services/scope_labels_service.d.ts +7 -7
- package/dist/lib/services/scope_labels_service.d.ts.map +1 -1
- package/dist/lib/services/scope_labels_service.js +20 -20
- package/dist/lib/services/scope_service.d.ts +8 -5
- package/dist/lib/services/scope_service.d.ts.map +1 -1
- package/dist/lib/services/scope_service.js +9 -8
- package/dist/lib/ui_shell_config.server.d.ts +5 -0
- package/dist/lib/ui_shell_config.server.d.ts.map +1 -1
- package/dist/lib/ui_shell_config.server.js +5 -0
- package/dist/lib/user_types_config.server.d.ts +56 -0
- package/dist/lib/user_types_config.server.d.ts.map +1 -0
- package/dist/lib/user_types_config.server.js +100 -0
- package/dist/page_components/login.d.ts.map +1 -1
- package/dist/page_components/login.js +3 -7
- package/dist/server/config/config_loader.js +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -3
- package/dist/server/routes/index.d.ts +1 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +2 -0
- package/dist/server/routes/org_management_orgs.d.ts +2 -0
- package/dist/server/routes/org_management_orgs.d.ts.map +1 -0
- package/dist/server/routes/org_management_orgs.js +2 -0
- package/dist/server/types/app_types.d.ts +3 -7
- package/dist/server/types/app_types.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.js +1 -3
- package/hazo_auth_config.example.ini +9 -0
- package/package.json +7 -1
- package/cli-src/server/logging/logger_service.ts +0 -56
- /package/public/profile_pictures/library/Cars/{050 - citroe/314/210n_c3.jpeg" → 050 - citro/303/253n_c3.jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{064 - lamborghini_huraca/314/201n.jpeg" → 064 - lamborghini_hurac/303/241n.jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{099 - citroe/314/210n_2cv_(classic).jpeg" → 099 - citro/303/253n_2cv_(classic).jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{131 - lamborghini_huraca/314/201n_sto.jpeg" → 131 - lamborghini_hurac/303/241n_sto.jpeg"} +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// file_description: API route for organization management (list, create, update, soft delete)
|
|
2
|
+
// section: imports
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
import { get_hazo_connect_instance } from "../../../../../lib/hazo_connect_instance.server";
|
|
5
|
+
import { hazo_get_auth } from "../../../../../lib/auth/hazo_get_auth.server";
|
|
6
|
+
import { create_app_logger } from "../../../../../lib/app_logger";
|
|
7
|
+
import { get_filename, get_line_number } from "../../../../../lib/utils/api_route_helpers";
|
|
8
|
+
import { is_multi_tenancy_enabled } from "../../../../../lib/multi_tenancy_config.server";
|
|
9
|
+
import { get_orgs, create_org, update_org, soft_delete_org, get_org_tree, check_user_org_access, } from "../../../../../lib/services/org_service";
|
|
10
|
+
// section: route_config
|
|
11
|
+
export const dynamic = "force-dynamic";
|
|
12
|
+
// section: constants
|
|
13
|
+
const PERMISSION_ORG_MANAGEMENT = "hazo_perm_org_management";
|
|
14
|
+
const PERMISSION_GLOBAL_ADMIN = "hazo_org_global_admin";
|
|
15
|
+
// section: helpers
|
|
16
|
+
async function check_permission(request) {
|
|
17
|
+
const auth_result = await hazo_get_auth(request, {
|
|
18
|
+
required_permissions: [PERMISSION_ORG_MANAGEMENT],
|
|
19
|
+
strict: false,
|
|
20
|
+
});
|
|
21
|
+
if (!auth_result.authenticated) {
|
|
22
|
+
return {
|
|
23
|
+
authorized: false,
|
|
24
|
+
error: NextResponse.json({ error: "Authentication required" }, { status: 401 }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (!auth_result.permission_ok) {
|
|
28
|
+
return {
|
|
29
|
+
authorized: false,
|
|
30
|
+
error: NextResponse.json({ error: "Permission denied", missing_permissions: auth_result.missing_permissions }, { status: 403 }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Check if user has global admin permission
|
|
34
|
+
const is_global_admin = auth_result.permissions.includes(PERMISSION_GLOBAL_ADMIN);
|
|
35
|
+
return {
|
|
36
|
+
authorized: true,
|
|
37
|
+
user_id: auth_result.user.id,
|
|
38
|
+
user_org_id: auth_result.user.org_id || null,
|
|
39
|
+
user_root_org_id: auth_result.user.root_org_id || null,
|
|
40
|
+
is_global_admin,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// section: api_handler
|
|
44
|
+
/**
|
|
45
|
+
* GET - Fetch organizations
|
|
46
|
+
* Query params:
|
|
47
|
+
* - action: 'list' | 'tree' (default: 'list')
|
|
48
|
+
* - include_inactive: boolean (default: false)
|
|
49
|
+
* - root_org_id: string (optional, filter by root org - required unless global admin)
|
|
50
|
+
*/
|
|
51
|
+
export async function GET(request) {
|
|
52
|
+
var _a;
|
|
53
|
+
const logger = create_app_logger();
|
|
54
|
+
try {
|
|
55
|
+
// Check if multi-tenancy is enabled
|
|
56
|
+
if (!is_multi_tenancy_enabled()) {
|
|
57
|
+
return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
|
|
58
|
+
}
|
|
59
|
+
// Check permission
|
|
60
|
+
const perm_check = await check_permission(request);
|
|
61
|
+
if (!perm_check.authorized) {
|
|
62
|
+
return perm_check.error;
|
|
63
|
+
}
|
|
64
|
+
const { searchParams } = new URL(request.url);
|
|
65
|
+
const action = searchParams.get("action") || "list";
|
|
66
|
+
const include_inactive = searchParams.get("include_inactive") === "true";
|
|
67
|
+
let root_org_id = searchParams.get("root_org_id") || undefined;
|
|
68
|
+
const adapter = get_hazo_connect_instance();
|
|
69
|
+
// If not global admin, restrict to user's org tree
|
|
70
|
+
if (!perm_check.is_global_admin) {
|
|
71
|
+
// User must have an org to view orgs
|
|
72
|
+
if (!perm_check.user_root_org_id && !perm_check.user_org_id) {
|
|
73
|
+
return NextResponse.json({ error: "You are not assigned to any organization" }, { status: 403 });
|
|
74
|
+
}
|
|
75
|
+
// Force filter to user's root org
|
|
76
|
+
root_org_id = perm_check.user_root_org_id || perm_check.user_org_id || undefined;
|
|
77
|
+
}
|
|
78
|
+
if (action === "tree") {
|
|
79
|
+
// Return hierarchical tree structure
|
|
80
|
+
const result = await get_org_tree(adapter, root_org_id, include_inactive);
|
|
81
|
+
if (!result.success) {
|
|
82
|
+
return NextResponse.json({ error: result.error }, { status: 500 });
|
|
83
|
+
}
|
|
84
|
+
return NextResponse.json({
|
|
85
|
+
success: true,
|
|
86
|
+
tree: result.tree,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// List organizations
|
|
90
|
+
const result = await get_orgs(adapter, {
|
|
91
|
+
root_org_id,
|
|
92
|
+
include_inactive,
|
|
93
|
+
});
|
|
94
|
+
if (!result.success) {
|
|
95
|
+
return NextResponse.json({ error: result.error }, { status: 500 });
|
|
96
|
+
}
|
|
97
|
+
logger.info("org_management_orgs_fetched", {
|
|
98
|
+
filename: get_filename(),
|
|
99
|
+
line_number: get_line_number(),
|
|
100
|
+
root_org_id,
|
|
101
|
+
count: ((_a = result.orgs) === null || _a === void 0 ? void 0 : _a.length) || 0,
|
|
102
|
+
is_global_admin: perm_check.is_global_admin,
|
|
103
|
+
});
|
|
104
|
+
return NextResponse.json({
|
|
105
|
+
success: true,
|
|
106
|
+
orgs: result.orgs,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
111
|
+
logger.error("org_management_orgs_fetch_error", {
|
|
112
|
+
filename: get_filename(),
|
|
113
|
+
line_number: get_line_number(),
|
|
114
|
+
error_message,
|
|
115
|
+
});
|
|
116
|
+
return NextResponse.json({ error: "Failed to fetch organizations" }, { status: 500 });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* POST - Create a new organization
|
|
121
|
+
* Body: { name: string, user_limit?: number, parent_org_id?: string }
|
|
122
|
+
*/
|
|
123
|
+
export async function POST(request) {
|
|
124
|
+
var _a;
|
|
125
|
+
const logger = create_app_logger();
|
|
126
|
+
try {
|
|
127
|
+
// Check if multi-tenancy is enabled
|
|
128
|
+
if (!is_multi_tenancy_enabled()) {
|
|
129
|
+
return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
|
|
130
|
+
}
|
|
131
|
+
// Check permission
|
|
132
|
+
const perm_check = await check_permission(request);
|
|
133
|
+
if (!perm_check.authorized) {
|
|
134
|
+
return perm_check.error;
|
|
135
|
+
}
|
|
136
|
+
const body = await request.json();
|
|
137
|
+
const { name, user_limit, parent_org_id } = body;
|
|
138
|
+
// Validate required fields
|
|
139
|
+
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
140
|
+
return NextResponse.json({ error: "name is required and must be a non-empty string" }, { status: 400 });
|
|
141
|
+
}
|
|
142
|
+
const adapter = get_hazo_connect_instance();
|
|
143
|
+
// If not global admin, verify user has access to parent org
|
|
144
|
+
if (!perm_check.is_global_admin && parent_org_id) {
|
|
145
|
+
const access_check = await check_user_org_access(adapter, perm_check.user_org_id || null, perm_check.user_root_org_id || null, parent_org_id);
|
|
146
|
+
if (!access_check.has_access) {
|
|
147
|
+
return NextResponse.json({ error: "You do not have access to the parent organization" }, { status: 403 });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// If not global admin and no parent_org_id, they can't create root orgs
|
|
151
|
+
if (!perm_check.is_global_admin && !parent_org_id) {
|
|
152
|
+
return NextResponse.json({ error: "Only global admins can create root organizations" }, { status: 403 });
|
|
153
|
+
}
|
|
154
|
+
const result = await create_org(adapter, {
|
|
155
|
+
name: name.trim(),
|
|
156
|
+
user_limit: typeof user_limit === "number" ? user_limit : 0,
|
|
157
|
+
parent_org_id,
|
|
158
|
+
created_by: perm_check.user_id,
|
|
159
|
+
});
|
|
160
|
+
if (!result.success) {
|
|
161
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
162
|
+
}
|
|
163
|
+
logger.info("org_management_org_created", {
|
|
164
|
+
filename: get_filename(),
|
|
165
|
+
line_number: get_line_number(),
|
|
166
|
+
org_id: (_a = result.org) === null || _a === void 0 ? void 0 : _a.id,
|
|
167
|
+
name: name.trim(),
|
|
168
|
+
parent_org_id,
|
|
169
|
+
created_by: perm_check.user_id,
|
|
170
|
+
});
|
|
171
|
+
return NextResponse.json({
|
|
172
|
+
success: true,
|
|
173
|
+
org: result.org,
|
|
174
|
+
}, { status: 201 });
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
178
|
+
logger.error("org_management_org_create_error", {
|
|
179
|
+
filename: get_filename(),
|
|
180
|
+
line_number: get_line_number(),
|
|
181
|
+
error_message,
|
|
182
|
+
});
|
|
183
|
+
return NextResponse.json({ error: "Failed to create organization" }, { status: 500 });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* PATCH - Update an existing organization
|
|
188
|
+
* Body: { org_id: string, name?: string, user_limit?: number }
|
|
189
|
+
*/
|
|
190
|
+
export async function PATCH(request) {
|
|
191
|
+
const logger = create_app_logger();
|
|
192
|
+
try {
|
|
193
|
+
// Check if multi-tenancy is enabled
|
|
194
|
+
if (!is_multi_tenancy_enabled()) {
|
|
195
|
+
return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
|
|
196
|
+
}
|
|
197
|
+
// Check permission
|
|
198
|
+
const perm_check = await check_permission(request);
|
|
199
|
+
if (!perm_check.authorized) {
|
|
200
|
+
return perm_check.error;
|
|
201
|
+
}
|
|
202
|
+
const body = await request.json();
|
|
203
|
+
const { org_id, name, user_limit } = body;
|
|
204
|
+
// Validate required fields
|
|
205
|
+
if (!org_id || typeof org_id !== "string") {
|
|
206
|
+
return NextResponse.json({ error: "org_id is required" }, { status: 400 });
|
|
207
|
+
}
|
|
208
|
+
const adapter = get_hazo_connect_instance();
|
|
209
|
+
// If not global admin, verify user has access to this org
|
|
210
|
+
if (!perm_check.is_global_admin) {
|
|
211
|
+
const access_check = await check_user_org_access(adapter, perm_check.user_org_id || null, perm_check.user_root_org_id || null, org_id);
|
|
212
|
+
if (!access_check.has_access) {
|
|
213
|
+
return NextResponse.json({ error: "You do not have access to this organization" }, { status: 403 });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Build update data
|
|
217
|
+
const update_data = {
|
|
218
|
+
changed_by: perm_check.user_id,
|
|
219
|
+
};
|
|
220
|
+
if (name !== undefined) {
|
|
221
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
222
|
+
return NextResponse.json({ error: "name must be a non-empty string" }, { status: 400 });
|
|
223
|
+
}
|
|
224
|
+
update_data.name = name.trim();
|
|
225
|
+
}
|
|
226
|
+
if (user_limit !== undefined) {
|
|
227
|
+
if (typeof user_limit !== "number" || user_limit < 0) {
|
|
228
|
+
return NextResponse.json({ error: "user_limit must be a non-negative number" }, { status: 400 });
|
|
229
|
+
}
|
|
230
|
+
update_data.user_limit = user_limit;
|
|
231
|
+
}
|
|
232
|
+
if (update_data.name === undefined && update_data.user_limit === undefined) {
|
|
233
|
+
return NextResponse.json({ error: "No update data provided" }, { status: 400 });
|
|
234
|
+
}
|
|
235
|
+
const result = await update_org(adapter, org_id, update_data);
|
|
236
|
+
if (!result.success) {
|
|
237
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
238
|
+
}
|
|
239
|
+
logger.info("org_management_org_updated", {
|
|
240
|
+
filename: get_filename(),
|
|
241
|
+
line_number: get_line_number(),
|
|
242
|
+
org_id,
|
|
243
|
+
changed_by: perm_check.user_id,
|
|
244
|
+
});
|
|
245
|
+
return NextResponse.json({
|
|
246
|
+
success: true,
|
|
247
|
+
org: result.org,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
252
|
+
logger.error("org_management_org_update_error", {
|
|
253
|
+
filename: get_filename(),
|
|
254
|
+
line_number: get_line_number(),
|
|
255
|
+
error_message,
|
|
256
|
+
});
|
|
257
|
+
return NextResponse.json({ error: "Failed to update organization" }, { status: 500 });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* DELETE - Soft delete an organization (sets active = false)
|
|
262
|
+
* Query params: org_id
|
|
263
|
+
*/
|
|
264
|
+
export async function DELETE(request) {
|
|
265
|
+
const logger = create_app_logger();
|
|
266
|
+
try {
|
|
267
|
+
// Check if multi-tenancy is enabled
|
|
268
|
+
if (!is_multi_tenancy_enabled()) {
|
|
269
|
+
return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
|
|
270
|
+
}
|
|
271
|
+
// Check permission
|
|
272
|
+
const perm_check = await check_permission(request);
|
|
273
|
+
if (!perm_check.authorized) {
|
|
274
|
+
return perm_check.error;
|
|
275
|
+
}
|
|
276
|
+
const { searchParams } = new URL(request.url);
|
|
277
|
+
const org_id = searchParams.get("org_id");
|
|
278
|
+
if (!org_id) {
|
|
279
|
+
return NextResponse.json({ error: "org_id query parameter is required" }, { status: 400 });
|
|
280
|
+
}
|
|
281
|
+
const adapter = get_hazo_connect_instance();
|
|
282
|
+
// If not global admin, verify user has access to this org
|
|
283
|
+
if (!perm_check.is_global_admin) {
|
|
284
|
+
const access_check = await check_user_org_access(adapter, perm_check.user_org_id || null, perm_check.user_root_org_id || null, org_id);
|
|
285
|
+
if (!access_check.has_access) {
|
|
286
|
+
return NextResponse.json({ error: "You do not have access to this organization" }, { status: 403 });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Soft delete (set active = false)
|
|
290
|
+
const result = await soft_delete_org(adapter, org_id, perm_check.user_id);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
293
|
+
}
|
|
294
|
+
logger.info("org_management_org_deactivated", {
|
|
295
|
+
filename: get_filename(),
|
|
296
|
+
line_number: get_line_number(),
|
|
297
|
+
org_id,
|
|
298
|
+
deactivated_by: perm_check.user_id,
|
|
299
|
+
});
|
|
300
|
+
return NextResponse.json({
|
|
301
|
+
success: true,
|
|
302
|
+
org: result.org,
|
|
303
|
+
message: "Organization deactivated successfully",
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
308
|
+
logger.error("org_management_org_deactivate_error", {
|
|
309
|
+
filename: get_filename(),
|
|
310
|
+
line_number: get_line_number(),
|
|
311
|
+
error_message,
|
|
312
|
+
});
|
|
313
|
+
return NextResponse.json({ error: "Failed to deactivate organization" }, { status: 500 });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -8,6 +8,12 @@ export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
|
8
8
|
error: string;
|
|
9
9
|
}> | NextResponse<{
|
|
10
10
|
success: boolean;
|
|
11
|
+
user_types_enabled: boolean;
|
|
12
|
+
available_user_types: {
|
|
13
|
+
key: string;
|
|
14
|
+
label: string;
|
|
15
|
+
badge_color: string;
|
|
16
|
+
}[];
|
|
11
17
|
users: {
|
|
12
18
|
id: unknown;
|
|
13
19
|
name: {} | null;
|
|
@@ -18,6 +24,7 @@ export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
|
18
24
|
created_at: {} | null;
|
|
19
25
|
profile_picture_url: {} | null;
|
|
20
26
|
profile_source: {} | null;
|
|
27
|
+
user_type: string | null;
|
|
21
28
|
}[];
|
|
22
29
|
}>>;
|
|
23
30
|
/**
|
|
@@ -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;
|
|
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;AAexD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;IAwE7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA2G/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
|
|
@@ -8,6 +8,7 @@ import { get_filename, get_line_number } from "../../../../../lib/utils/api_rout
|
|
|
8
8
|
import { request_password_reset } from "../../../../../lib/services/password_reset_service";
|
|
9
9
|
import { get_auth_cache } from "../../../../../lib/auth/auth_cache";
|
|
10
10
|
import { get_auth_utility_config } from "../../../../../lib/auth_utility_config.server";
|
|
11
|
+
import { is_user_types_enabled, get_all_user_types, get_user_types_config, } from "../../../../../lib/user_types_config.server";
|
|
11
12
|
// section: route_config
|
|
12
13
|
export const dynamic = 'force-dynamic';
|
|
13
14
|
// section: api_handler
|
|
@@ -32,8 +33,19 @@ export async function GET(request) {
|
|
|
32
33
|
line_number: get_line_number(),
|
|
33
34
|
user_count: users.length,
|
|
34
35
|
});
|
|
36
|
+
// Check if user types feature is enabled
|
|
37
|
+
const user_types_enabled = is_user_types_enabled();
|
|
38
|
+
const available_user_types = user_types_enabled
|
|
39
|
+
? get_all_user_types().map((t) => ({
|
|
40
|
+
key: t.key,
|
|
41
|
+
label: t.label,
|
|
42
|
+
badge_color: t.badge_color,
|
|
43
|
+
}))
|
|
44
|
+
: [];
|
|
35
45
|
return NextResponse.json({
|
|
36
46
|
success: true,
|
|
47
|
+
user_types_enabled,
|
|
48
|
+
available_user_types,
|
|
37
49
|
users: users.map((user) => ({
|
|
38
50
|
id: user.id,
|
|
39
51
|
name: user.name || null,
|
|
@@ -44,6 +56,7 @@ export async function GET(request) {
|
|
|
44
56
|
created_at: user.created_at || null,
|
|
45
57
|
profile_picture_url: user.profile_picture_url || null,
|
|
46
58
|
profile_source: user.profile_source || null,
|
|
59
|
+
user_type: user.user_type || null,
|
|
47
60
|
})),
|
|
48
61
|
}, { status: 200 });
|
|
49
62
|
}
|
|
@@ -66,18 +79,43 @@ export async function PATCH(request) {
|
|
|
66
79
|
const logger = create_app_logger();
|
|
67
80
|
try {
|
|
68
81
|
const body = await request.json();
|
|
69
|
-
const { user_id, is_active } = body;
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
const { user_id, is_active, user_type } = body;
|
|
83
|
+
// user_id is always required
|
|
84
|
+
if (!user_id) {
|
|
85
|
+
return NextResponse.json({ error: "user_id is required" }, { status: 400 });
|
|
86
|
+
}
|
|
87
|
+
// Build update object based on what's provided
|
|
88
|
+
const update_data = {
|
|
89
|
+
changed_at: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
// Handle is_active if provided
|
|
92
|
+
if (typeof is_active === "boolean") {
|
|
93
|
+
update_data.is_active = is_active;
|
|
94
|
+
}
|
|
95
|
+
// Handle user_type if provided (only when feature is enabled)
|
|
96
|
+
if (user_type !== undefined) {
|
|
97
|
+
const config = get_user_types_config();
|
|
98
|
+
if (config.enable_user_types) {
|
|
99
|
+
// Allow null to clear the type, or validate type exists
|
|
100
|
+
if (user_type === null || user_type === "") {
|
|
101
|
+
update_data.user_type = null;
|
|
102
|
+
}
|
|
103
|
+
else if (config.user_types.has(user_type)) {
|
|
104
|
+
update_data.user_type = user_type;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return NextResponse.json({ error: "Invalid user type" }, { status: 400 });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Ensure there's something to update besides changed_at
|
|
112
|
+
if (Object.keys(update_data).length === 1) {
|
|
113
|
+
return NextResponse.json({ error: "No valid fields to update" }, { status: 400 });
|
|
72
114
|
}
|
|
73
115
|
const hazoConnect = get_hazo_connect_instance();
|
|
74
116
|
const users_service = createCrudService(hazoConnect, "hazo_users");
|
|
75
|
-
// Update user
|
|
76
|
-
|
|
77
|
-
await users_service.updateById(user_id, {
|
|
78
|
-
is_active,
|
|
79
|
-
changed_at: now,
|
|
80
|
-
});
|
|
117
|
+
// Update user
|
|
118
|
+
await users_service.updateById(user_id, update_data);
|
|
81
119
|
// Invalidate user cache after deactivation
|
|
82
120
|
if (is_active === false) {
|
|
83
121
|
try {
|
|
@@ -100,7 +138,7 @@ export async function PATCH(request) {
|
|
|
100
138
|
filename: get_filename(),
|
|
101
139
|
line_number: get_line_number(),
|
|
102
140
|
user_id,
|
|
103
|
-
|
|
141
|
+
updated_fields: Object.keys(update_data).filter((k) => k !== "changed_at"),
|
|
104
142
|
});
|
|
105
143
|
return NextResponse.json({ success: true }, { status: 200 });
|
|
106
144
|
}
|
|
@@ -97,10 +97,10 @@ export const use_login_form = ({ dataClient, logger, redirectRoute, successMessa
|
|
|
97
97
|
const logData = Object.assign({ filename: get_filename(), line_number: get_line_number(), email: values[LOGIN_FIELD_IDS.EMAIL], ip_address: clientIp, timestamp,
|
|
98
98
|
success }, (errorMessage ? { error_message: errorMessage } : {}));
|
|
99
99
|
if (success) {
|
|
100
|
-
logger.info("login_attempt_successful", logData);
|
|
100
|
+
logger === null || logger === void 0 ? void 0 : logger.info("login_attempt_successful", logData);
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
|
-
logger.error("login_attempt_failed", logData);
|
|
103
|
+
logger === null || logger === void 0 ? void 0 : logger.error("login_attempt_failed", logData);
|
|
104
104
|
}
|
|
105
105
|
}, [logger, values, clientIp]);
|
|
106
106
|
const handleSubmit = useCallback(async (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile_picture_library_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/my_settings/components/profile_picture_library_tab.tsx"],"names":[],"mappings":"AAeA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,QAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,EAAE,6BAA6B,
|
|
1
|
+
{"version":3,"file":"profile_picture_library_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/my_settings/components/profile_picture_library_tab.tsx"],"names":[],"mappings":"AAeA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,QAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,EAAE,6BAA6B,2CAsQ/B"}
|
|
@@ -112,25 +112,19 @@ export function ProfilePictureLibraryTab({ useLibrary, onUseLibraryChange, onPho
|
|
|
112
112
|
const getInitials = () => {
|
|
113
113
|
return "L";
|
|
114
114
|
};
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
4: "grid-cols-4",
|
|
122
|
-
5: "grid-cols-5",
|
|
123
|
-
6: "grid-cols-6",
|
|
124
|
-
7: "grid-cols-7",
|
|
125
|
-
8: "grid-cols-8",
|
|
115
|
+
// Get inline style for grid columns (using inline styles instead of Tailwind classes
|
|
116
|
+
// because dynamic classes like grid-cols-4 may not be included in consuming app's CSS bundle)
|
|
117
|
+
const getGridColumnsStyle = (columns) => {
|
|
118
|
+
const validColumns = Math.min(Math.max(columns, 1), 8);
|
|
119
|
+
return {
|
|
120
|
+
gridTemplateColumns: `repeat(${validColumns}, minmax(0, 1fr))`,
|
|
126
121
|
};
|
|
127
|
-
return columnMap[columns] || "grid-cols-4";
|
|
128
122
|
};
|
|
129
|
-
return (_jsxs("div", { className: "cls_profile_picture_library_tab flex flex-col gap-4", children: [_jsxs("div", { className: "cls_profile_picture_library_tab_switch flex items-center gap-3", children: [_jsx(Switch, { id: "use-library", checked: useLibrary, onCheckedChange: onUseLibraryChange, disabled: disabled, className: "cls_profile_picture_library_tab_switch_input", "aria-label": "Use library photo" }), _jsxs(Label, { htmlFor: "use-library", className: "cls_profile_picture_library_tab_switch_label text-sm font-medium text-[var(--hazo-text-secondary)] cursor-pointer", children: ["Use library photo", _jsx(HazoUITooltip, { message: libraryTooltipMessage, iconSize: tooltipIconSizeSmall, side: "top" })] })] }), _jsxs("div", { className: "cls_profile_picture_library_tab_content grid grid-cols-12 gap-4", children: [_jsxs("div", { className: "cls_profile_picture_library_tab_categories_container flex flex-col gap-2 col-span-3", children: [_jsx(Label, { className: "cls_profile_picture_library_tab_categories_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: "Categories" }), loadingCategories ? (_jsx("div", { className: "cls_profile_picture_library_tab_loading flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx(Loader2, { className: "h-6 w-6 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }) })) : categories.length > 0 ? (_jsx(VerticalTabs, { value: selectedCategory || categories[0], onValueChange: setSelectedCategory, className: "cls_profile_picture_library_tab_vertical_tabs", children: _jsx(VerticalTabsList, { className: "cls_profile_picture_library_tab_vertical_tabs_list w-full", children: categories.map((category) => (_jsx(VerticalTabsTrigger, { value: category, className: "cls_profile_picture_library_tab_vertical_tabs_trigger w-full justify-start", children: category }, category))) }) })) : (_jsx("div", { className: "cls_profile_picture_library_tab_no_categories flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx("p", { className: "cls_profile_picture_library_tab_no_categories_text text-sm text-[var(--hazo-text-muted)]", children: "No categories available" }) }))] }), _jsxs("div", { className: "cls_profile_picture_library_tab_photos_container flex flex-col gap-2 col-span-6", children: [_jsx(Label, { className: "cls_profile_picture_library_tab_photos_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: "Photos" }), loadingPhotos ? (_jsx("div", { className: "cls_profile_picture_library_tab_photos_loading flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx(Loader2, { className: "h-6 w-6 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }) })) : photos.length > 0 ? (_jsx("div", { className:
|
|
123
|
+
return (_jsxs("div", { className: "cls_profile_picture_library_tab flex flex-col gap-4", children: [_jsxs("div", { className: "cls_profile_picture_library_tab_switch flex items-center gap-3", children: [_jsx(Switch, { id: "use-library", checked: useLibrary, onCheckedChange: onUseLibraryChange, disabled: disabled, className: "cls_profile_picture_library_tab_switch_input", "aria-label": "Use library photo" }), _jsxs(Label, { htmlFor: "use-library", className: "cls_profile_picture_library_tab_switch_label text-sm font-medium text-[var(--hazo-text-secondary)] cursor-pointer", children: ["Use library photo", _jsx(HazoUITooltip, { message: libraryTooltipMessage, iconSize: tooltipIconSizeSmall, side: "top" })] })] }), _jsxs("div", { className: "cls_profile_picture_library_tab_content grid grid-cols-12 gap-4", children: [_jsxs("div", { className: "cls_profile_picture_library_tab_categories_container flex flex-col gap-2 col-span-3", children: [_jsx(Label, { className: "cls_profile_picture_library_tab_categories_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: "Categories" }), loadingCategories ? (_jsx("div", { className: "cls_profile_picture_library_tab_loading flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx(Loader2, { className: "h-6 w-6 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }) })) : categories.length > 0 ? (_jsx(VerticalTabs, { value: selectedCategory || categories[0], onValueChange: setSelectedCategory, className: "cls_profile_picture_library_tab_vertical_tabs", children: _jsx(VerticalTabsList, { className: "cls_profile_picture_library_tab_vertical_tabs_list w-full", children: categories.map((category) => (_jsx(VerticalTabsTrigger, { value: category, className: "cls_profile_picture_library_tab_vertical_tabs_trigger w-full justify-start", children: category }, category))) }) })) : (_jsx("div", { className: "cls_profile_picture_library_tab_no_categories flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx("p", { className: "cls_profile_picture_library_tab_no_categories_text text-sm text-[var(--hazo-text-muted)]", children: "No categories available" }) }))] }), _jsxs("div", { className: "cls_profile_picture_library_tab_photos_container flex flex-col gap-2 col-span-6", children: [_jsx(Label, { className: "cls_profile_picture_library_tab_photos_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: "Photos" }), loadingPhotos ? (_jsx("div", { className: "cls_profile_picture_library_tab_photos_loading flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx(Loader2, { className: "h-6 w-6 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }) })) : photos.length > 0 ? (_jsx("div", { className: "cls_profile_picture_library_tab_photos_grid grid gap-3 overflow-y-auto p-4 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px] max-h-[400px]", style: getGridColumnsStyle(libraryPhotoGridColumns), children: photos.map((photoUrl) => (_jsx("button", { type: "button", onClick: () => handlePhotoClick(photoUrl), className: `
|
|
130
124
|
cls_profile_picture_library_tab_photo_thumbnail
|
|
131
125
|
w-full aspect-square rounded-lg overflow-hidden border-2 transition-colors cursor-pointer
|
|
132
126
|
${selectedPhoto === photoUrl ? "border-blue-500 ring-2 ring-blue-200" : "border-[var(--hazo-border)] hover:border-[var(--hazo-border-emphasis)]"}
|
|
133
|
-
`, style: { minHeight: '80px', minWidth: '80px' }, "aria-label": `Select photo ${photoUrl.split('/').pop()}`, children: _jsx("img", { src: photoUrl, alt: `Library photo ${photoUrl.split('/').pop()}`, className: "cls_profile_picture_library_tab_photo_thumbnail_image w-full h-full object-cover", loading: "lazy", onError: (e) => {
|
|
127
|
+
`, style: { minHeight: '80px', minWidth: '80px', aspectRatio: '1/1' }, "aria-label": `Select photo ${photoUrl.split('/').pop()}`, children: _jsx("img", { src: photoUrl, alt: `Library photo ${photoUrl.split('/').pop()}`, className: "cls_profile_picture_library_tab_photo_thumbnail_image w-full h-full object-cover", loading: "lazy", onError: (e) => {
|
|
134
128
|
// Fallback if image fails to load
|
|
135
129
|
const target = e.target;
|
|
136
130
|
target.style.display = 'none';
|
|
@@ -2,8 +2,6 @@ export type RbacTestLayoutProps = {
|
|
|
2
2
|
className?: string;
|
|
3
3
|
/** Whether HRBAC is enabled (passed from server) */
|
|
4
4
|
hrbacEnabled?: boolean;
|
|
5
|
-
/** Default organization for HRBAC scopes */
|
|
6
|
-
defaultOrg?: string;
|
|
7
5
|
};
|
|
8
6
|
/**
|
|
9
7
|
* RBAC/HRBAC Test layout component
|
|
@@ -11,5 +9,5 @@ export type RbacTestLayoutProps = {
|
|
|
11
9
|
* @param props - Component props
|
|
12
10
|
* @returns RBAC test layout component
|
|
13
11
|
*/
|
|
14
|
-
export declare function RbacTestLayout({ className, hrbacEnabled,
|
|
12
|
+
export declare function RbacTestLayout({ className, hrbacEnabled, }: RbacTestLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
15
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/rbac_test/index.tsx"],"names":[],"mappings":"AA2CA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/rbac_test/index.tsx"],"names":[],"mappings":"AA2CA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAgHF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,YAAoB,GACrB,EAAE,mBAAmB,2CAw3BrB"}
|
|
@@ -60,7 +60,7 @@ function getUserInitials(user) {
|
|
|
60
60
|
* @param props - Component props
|
|
61
61
|
* @returns RBAC test layout component
|
|
62
62
|
*/
|
|
63
|
-
export function RbacTestLayout({ className, hrbacEnabled = false,
|
|
63
|
+
export function RbacTestLayout({ className, hrbacEnabled = false, }) {
|
|
64
64
|
var _a;
|
|
65
65
|
const { apiBasePath } = useHazoAuthConfig();
|
|
66
66
|
const authResult = use_hazo_auth();
|
|
@@ -361,7 +361,7 @@ export function RbacTestLayout({ className, hrbacEnabled = false, defaultOrg = "
|
|
|
361
361
|
}
|
|
362
362
|
return (_jsxs("div", { className: `cls_rbac_test_layout flex flex-col gap-6 p-4 w-full max-w-5xl mx-auto ${className || ""}`, children: [_jsxs("div", { className: "cls_rbac_test_header", children: [_jsx("h1", { className: "text-2xl font-bold", children: "RBAC & HRBAC Test" }), _jsx("p", { className: "text-muted-foreground", children: "Test Role-Based Access Control (RBAC) and Hierarchical RBAC (HRBAC) for any user." })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center gap-2", children: [_jsx(User, { className: "h-5 w-5" }), "Select User to Test"] }), _jsx(CardDescription, { children: "Choose a user to test their permissions and scope access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "user_select", children: "User" }), usersLoading ? (_jsxs("div", { className: "flex items-center gap-2 p-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Loading users..." })] })) : (_jsxs(Select, { value: selectedUserId, onValueChange: setSelectedUserId, children: [_jsx(SelectTrigger, { id: "user_select", className: "w-full", children: _jsx(SelectValue, { placeholder: "Select a user" }) }), _jsx(SelectContent, { children: users.map((user) => (_jsx(SelectItem, { value: user.id, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Avatar, { className: "h-6 w-6", children: [_jsx(AvatarImage, { src: user.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600 text-xs", children: getUserInitials(user) })] }), _jsx("span", { children: user.name || user.email_address }), user.name && (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["(", user.email_address, ")"] }))] }) }, user.id))) })] }))] }), selectedUser && (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 p-4 border rounded-lg bg-muted/30", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "h-12 w-12", children: [_jsx(AvatarImage, { src: selectedUser.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600", children: getUserInitials(selectedUser) })] }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: selectedUser.name || selectedUser.email_address }), selectedUser.name && (_jsx("p", { className: "text-sm text-muted-foreground", children: selectedUser.email_address })), _jsxs("p", { className: "text-xs text-muted-foreground font-mono", children: [selectedUser.id.substring(0, 8), "..."] })] })] }), userDataLoading ? (_jsx("div", { className: "flex items-center justify-center", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: userPermissions.length > 0 ? (userPermissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs", children: p }, p)))) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "None" })) })] }), hrbacEnabled && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Assigned Scopes" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: userScopes.length > 0 ? (userScopes.map((s) => (_jsx("span", { className: "px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs", title: `${SCOPE_LEVEL_LABELS[s.scope_type]}: ${s.scope_seq}`, children: s.scope_seq }, `${s.scope_type}-${s.scope_id}`)))) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "None" })) })] }))] }))] }))] })] }), _jsxs(Tabs, { defaultValue: "rbac", className: "w-full", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsxs(TabsTrigger, { value: "rbac", className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-4 w-4" }), "RBAC Test"] }), _jsxs(TabsTrigger, { value: "hrbac", className: "flex items-center gap-2", disabled: !hrbacEnabled, children: [_jsx(FolderTree, { className: "h-4 w-4" }), "HRBAC Test ", !hrbacEnabled && "(Disabled)"] })] }), _jsxs(TabsContent, { value: "rbac", className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-lg", children: "Permission Test" }), _jsx(CardDescription, { children: "Select permissions to test if the selected user has them" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[300px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `rbac_${perm.permission_name}`, checked: selectedPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handlePermissionToggle(perm.permission_name, checked) }), _jsxs("label", { htmlFor: `rbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: [perm.permission_name, userPermissions.includes(perm.permission_name) && (_jsx(CheckCircle, { className: "inline h-3 w-3 text-green-500 ml-1" }))] })] }, perm.permission_name))) })), _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsxs("span", { children: ["Selected: ", selectedPermissions.length, " permission(s)"] }), selectedPermissions.length > 0 && (_jsxs("span", { className: "text-xs", children: ["(", selectedPermissions.join(", "), ")"] }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunRbacTest, disabled: rbacTesting || !selectedUserId, children: rbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Permissions"] })) }), _jsx(Button, { onClick: handleClearRbac, variant: "outline", children: "Clear" })] })] })] }), rbacResult && (_jsxs(Card, { className: rbacResult.permission_ok
|
|
363
363
|
? "border-green-200 bg-green-50/50"
|
|
364
|
-
: "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: rbacResult.permission_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Permission Check Passed" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Permission Check Failed" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [rbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: rbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: rbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: rbacResult.permission_ok ? "Yes" : "No" })] })] }), rbacResult.missing_permissions && rbacResult.missing_permissions.length > 0 && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Missing Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: rbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] }))] })] }))] }), _jsx(TabsContent, { value: "hrbac", className: "flex flex-col gap-4", children: !hrbacEnabled ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-amber-500" }), _jsx("h2", { className: "text-lg font-semibold", children: "HRBAC Not Enabled" }), _jsxs("p", { className: "text-muted-foreground text-center max-w-md", children: ["Enable HRBAC by setting", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "enable_hrbac = true" }), " in the", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "[hazo_auth__scope_hierarchy]" }), " ", "section."] })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center justify-between", children: [_jsx("span", { children: "Scope Access Test" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadScopeTree(), disabled: treeLoading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${treeLoading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx(CardDescription, { children: "Select a scope from the tree and test if the selected user has access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Select Scope" }), treeLoading ? (_jsx("div", { className: "flex items-center justify-center p-8 border rounded-lg", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : scopeTree.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center p-6 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-8 w-8 text-muted-foreground mb-2" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "No scopes available. Create scopes in User Management first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[250px] overflow-auto", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleTreeSelectChange, initialSelectedItemId: selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.id, className: "w-full" }) }))] }), (selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData) && (_jsxs("div", { className: "p-3 border rounded-lg bg-muted/50", children: [_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: "Selected:" }), " ", selectedTreeItem.scopeData.name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [SCOPE_LEVEL_LABELS[selectedTreeItem.scopeData.level], " -", " ", selectedTreeItem.scopeData.seq
|
|
364
|
+
: "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: rbacResult.permission_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Permission Check Passed" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Permission Check Failed" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [rbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: rbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: rbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: rbacResult.permission_ok ? "Yes" : "No" })] })] }), rbacResult.missing_permissions && rbacResult.missing_permissions.length > 0 && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Missing Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: rbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] }))] })] }))] }), _jsx(TabsContent, { value: "hrbac", className: "flex flex-col gap-4", children: !hrbacEnabled ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-amber-500" }), _jsx("h2", { className: "text-lg font-semibold", children: "HRBAC Not Enabled" }), _jsxs("p", { className: "text-muted-foreground text-center max-w-md", children: ["Enable HRBAC by setting", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "enable_hrbac = true" }), " in the", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "[hazo_auth__scope_hierarchy]" }), " ", "section."] })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center justify-between", children: [_jsx("span", { children: "Scope Access Test" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadScopeTree(), disabled: treeLoading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${treeLoading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx(CardDescription, { children: "Select a scope from the tree and test if the selected user has access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Select Scope" }), treeLoading ? (_jsx("div", { className: "flex items-center justify-center p-8 border rounded-lg", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : scopeTree.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center p-6 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-8 w-8 text-muted-foreground mb-2" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "No scopes available. Create scopes in User Management first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[250px] overflow-auto", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleTreeSelectChange, initialSelectedItemId: selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.id, className: "w-full" }) }))] }), (selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData) && (_jsxs("div", { className: "p-3 border rounded-lg bg-muted/50", children: [_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: "Selected:" }), " ", selectedTreeItem.scopeData.name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [SCOPE_LEVEL_LABELS[selectedTreeItem.scopeData.level], " -", " ", selectedTreeItem.scopeData.seq] })] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Additional Permissions (Optional)" }), permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[150px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `hrbac_${perm.permission_name}`, checked: hrbacPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handleHrbacPermissionToggle(perm.permission_name, checked) }), _jsx("label", { htmlFor: `hrbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: perm.permission_name })] }, perm.permission_name))) }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunHrbacTest, disabled: hrbacTesting || !selectedUserId || !(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData), children: hrbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Scope Access"] })) }), _jsx(Button, { onClick: handleClearHrbac, variant: "outline", children: "Clear" })] })] })] }), hrbacResult && (_jsxs(Card, { className: hrbacResult.scope_ok
|
|
365
365
|
? "border-green-200 bg-green-50/50"
|
|
366
366
|
: "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: hrbacResult.scope_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Scope Access Granted" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Scope Access Denied" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [hrbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: hrbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: hrbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: hrbacResult.permission_ok ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Scope OK" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.scope_ok === undefined
|
|
367
367
|
? "text-muted-foreground"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type AuthNavbarProps = {
|
|
2
|
+
/** Logo image path */
|
|
3
|
+
logo_path?: string;
|
|
4
|
+
/** Logo width in pixels */
|
|
5
|
+
logo_width?: number;
|
|
6
|
+
/** Logo height in pixels */
|
|
7
|
+
logo_height?: number;
|
|
8
|
+
/** Company/application name displayed next to logo */
|
|
9
|
+
company_name?: string;
|
|
10
|
+
/** Home link path */
|
|
11
|
+
home_path?: string;
|
|
12
|
+
/** Home link label */
|
|
13
|
+
home_label?: string;
|
|
14
|
+
/** Show home link */
|
|
15
|
+
show_home_link?: boolean;
|
|
16
|
+
/** Navbar background color */
|
|
17
|
+
background_color?: string;
|
|
18
|
+
/** Navbar text color */
|
|
19
|
+
text_color?: string;
|
|
20
|
+
/** Navbar height in pixels */
|
|
21
|
+
height?: number;
|
|
22
|
+
/** Additional CSS class */
|
|
23
|
+
className?: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function AuthNavbar({ logo_path, logo_width, logo_height, company_name, home_path, home_label, show_home_link, background_color, text_color, height, className, }: AuthNavbarProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=auth_navbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth_navbar.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_navbar.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,eAAe,GAAG;IAC5B,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8BAA8B;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAGF,wBAAgB,UAAU,CAAC,EACzB,SAAuB,EACvB,UAAe,EACf,WAAgB,EAChB,YAAiB,EACjB,SAAe,EACf,UAAmB,EACnB,cAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,MAAW,EACX,SAAS,GACV,EAAE,eAAe,2CAiDjB"}
|