hazo_auth 4.3.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/cli-src/lib/already_logged_in_config.server.ts +1 -1
  2. package/cli-src/lib/app_logger.ts +8 -18
  3. package/cli-src/lib/auth/auth_types.ts +7 -0
  4. package/cli-src/lib/auth/auth_utils.server.ts +2 -2
  5. package/cli-src/lib/auth/dev_lock_validator.edge.ts +171 -0
  6. package/cli-src/lib/auth/hazo_get_auth.server.ts +84 -13
  7. package/cli-src/lib/auth/index.ts +5 -5
  8. package/cli-src/lib/auth/nextauth_config.ts +4 -4
  9. package/cli-src/lib/auth/org_cache.ts +148 -0
  10. package/cli-src/lib/auth/server_auth.ts +2 -2
  11. package/cli-src/lib/auth/session_token_validator.edge.ts +4 -0
  12. package/cli-src/lib/auth_utility_config.server.ts +1 -1
  13. package/cli-src/lib/config/config_loader.server.ts +1 -1
  14. package/cli-src/lib/config/default_config.ts +44 -0
  15. package/cli-src/lib/dev_lock_config.server.ts +148 -0
  16. package/cli-src/lib/email_verification_config.server.ts +3 -3
  17. package/cli-src/lib/file_types_config.server.ts +1 -1
  18. package/cli-src/lib/forgot_password_config.server.ts +3 -3
  19. package/cli-src/lib/hazo_connect_instance.server.ts +2 -2
  20. package/cli-src/lib/hazo_connect_setup.server.ts +2 -2
  21. package/cli-src/lib/index.ts +24 -24
  22. package/cli-src/lib/login_config.server.ts +4 -4
  23. package/cli-src/lib/messages_config.server.ts +1 -1
  24. package/cli-src/lib/multi_tenancy_config.server.ts +94 -0
  25. package/cli-src/lib/my_settings_config.server.ts +7 -7
  26. package/cli-src/lib/oauth_config.server.ts +2 -2
  27. package/cli-src/lib/password_requirements_config.server.ts +2 -2
  28. package/cli-src/lib/profile_pic_menu_config.server.ts +1 -1
  29. package/cli-src/lib/profile_picture_config.server.ts +2 -2
  30. package/cli-src/lib/register_config.server.ts +5 -5
  31. package/cli-src/lib/reset_password_config.server.ts +4 -4
  32. package/cli-src/lib/scope_hierarchy_config.server.ts +2 -2
  33. package/cli-src/lib/services/email_service.ts +2 -2
  34. package/cli-src/lib/services/email_verification_service.ts +3 -3
  35. package/cli-src/lib/services/login_service.ts +3 -3
  36. package/cli-src/lib/services/oauth_service.ts +4 -4
  37. package/cli-src/lib/services/org_service.ts +965 -0
  38. package/cli-src/lib/services/password_change_service.ts +3 -3
  39. package/cli-src/lib/services/password_reset_service.ts +3 -3
  40. package/cli-src/lib/services/profile_picture_remove_service.ts +3 -3
  41. package/cli-src/lib/services/profile_picture_service.ts +5 -5
  42. package/cli-src/lib/services/registration_service.ts +8 -8
  43. package/cli-src/lib/services/scope_labels_service.ts +3 -3
  44. package/cli-src/lib/services/scope_service.ts +2 -2
  45. package/cli-src/lib/services/session_token_service.ts +6 -2
  46. package/cli-src/lib/services/token_service.ts +2 -2
  47. package/cli-src/lib/services/user_profiles_service.ts +4 -4
  48. package/cli-src/lib/services/user_scope_service.ts +3 -3
  49. package/cli-src/lib/services/user_update_service.ts +4 -4
  50. package/cli-src/lib/ui_shell_config.server.ts +1 -1
  51. package/cli-src/lib/ui_sizes_config.server.ts +1 -1
  52. package/cli-src/lib/user_fields_config.server.ts +1 -1
  53. package/cli-src/lib/user_management_config.server.ts +1 -1
  54. package/cli-src/lib/user_profiles_config.server.ts +1 -1
  55. package/cli-src/lib/utils/error_sanitizer.ts +1 -1
  56. package/cli-src/server/types/app_types.ts +72 -0
  57. package/cli-src/server/types/express.d.ts +16 -0
  58. package/dist/components/layouts/dev_lock/index.d.ts +29 -0
  59. package/dist/components/layouts/dev_lock/index.d.ts.map +1 -0
  60. package/dist/components/layouts/dev_lock/index.js +60 -0
  61. package/dist/components/layouts/index.d.ts +2 -0
  62. package/dist/components/layouts/index.d.ts.map +1 -1
  63. package/dist/components/layouts/index.js +1 -0
  64. package/dist/components/layouts/login/hooks/use_login_form.js +2 -2
  65. package/dist/components/layouts/org_management/index.d.ts +26 -0
  66. package/dist/components/layouts/org_management/index.d.ts.map +1 -0
  67. package/dist/components/layouts/org_management/index.js +75 -0
  68. package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
  69. package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
  70. package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
  71. package/dist/components/layouts/user_management/index.d.ts +3 -1
  72. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  73. package/dist/components/layouts/user_management/index.js +10 -4
  74. package/dist/components/ui/button.d.ts +1 -1
  75. package/dist/lib/app_logger.d.ts +3 -9
  76. package/dist/lib/app_logger.d.ts.map +1 -1
  77. package/dist/lib/app_logger.js +7 -10
  78. package/dist/lib/auth/auth_types.d.ts +6 -0
  79. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  80. package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
  81. package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
  82. package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
  83. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  84. package/dist/lib/auth/hazo_get_auth.server.js +61 -1
  85. package/dist/lib/auth/org_cache.d.ts +65 -0
  86. package/dist/lib/auth/org_cache.d.ts.map +1 -0
  87. package/dist/lib/auth/org_cache.js +103 -0
  88. package/dist/lib/config/default_config.d.ts +76 -0
  89. package/dist/lib/config/default_config.d.ts.map +1 -1
  90. package/dist/lib/config/default_config.js +42 -0
  91. package/dist/lib/dev_lock_config.server.d.ts +41 -0
  92. package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
  93. package/dist/lib/dev_lock_config.server.js +50 -0
  94. package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
  95. package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
  96. package/dist/lib/multi_tenancy_config.server.js +41 -0
  97. package/dist/lib/services/org_service.d.ts +191 -0
  98. package/dist/lib/services/org_service.d.ts.map +1 -0
  99. package/dist/lib/services/org_service.js +746 -0
  100. package/dist/page_components/dev_lock.d.ts +11 -0
  101. package/dist/page_components/dev_lock.d.ts.map +1 -0
  102. package/dist/page_components/dev_lock.js +17 -0
  103. package/dist/page_components/index.d.ts +1 -0
  104. package/dist/page_components/index.d.ts.map +1 -1
  105. package/dist/page_components/index.js +1 -0
  106. package/dist/page_components/login.d.ts.map +1 -1
  107. package/dist/page_components/login.js +3 -7
  108. package/dist/page_components/org_management.d.ts +27 -0
  109. package/dist/page_components/org_management.d.ts.map +1 -0
  110. package/dist/page_components/org_management.js +18 -0
  111. package/dist/server/config/config_loader.js +2 -2
  112. package/dist/server/index.d.ts.map +1 -1
  113. package/dist/server/index.js +2 -3
  114. package/dist/server/types/app_types.d.ts +3 -7
  115. package/dist/server/types/app_types.d.ts.map +1 -1
  116. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  117. package/dist/server_pages/login_client_wrapper.js +1 -3
  118. package/hazo_auth_config.example.ini +30 -0
  119. package/package.json +29 -2
@@ -0,0 +1,276 @@
1
+ // file_description: Organization Hierarchy tab component for managing multi-tenancy organizations using tree view
2
+ // section: client_directive
3
+ "use client";
4
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
5
+ // section: imports
6
+ import { useState, useEffect, useCallback, useMemo } from "react";
7
+ import { TreeView } from "../../../ui/tree-view";
8
+ import { Button } from "../../../ui/button";
9
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../ui/dialog";
10
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "../../../ui/alert-dialog";
11
+ import { Input } from "../../../ui/input";
12
+ import { Label } from "../../../ui/label";
13
+ import { Loader2, Plus, Edit, Trash2, CircleCheck, CircleX, Building2, FolderTree, RefreshCw, AlertCircle, } from "lucide-react";
14
+ import { toast } from "sonner";
15
+ import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
16
+ // section: helpers
17
+ function getUserCountDisplay(org) {
18
+ var _a;
19
+ const count = (_a = org.current_user_count) !== null && _a !== void 0 ? _a : 0;
20
+ if (org.user_limit === 0) {
21
+ return `${count} users`;
22
+ }
23
+ return `${count}/${org.user_limit} users`;
24
+ }
25
+ // Convert OrgTreeNode to TreeDataItem format
26
+ function convertToTreeData(nodes, onEdit, onDelete, onAddChild) {
27
+ return nodes.map((node) => {
28
+ const hasChildren = node.children && node.children.length > 0;
29
+ const isInactive = node.active === false;
30
+ // Build display name with user count and status
31
+ const displayName = isInactive
32
+ ? `${node.name} (${getUserCountDisplay(node)}) [Inactive]`
33
+ : `${node.name} (${getUserCountDisplay(node)})`;
34
+ const item = {
35
+ id: node.id,
36
+ name: displayName,
37
+ icon: Building2,
38
+ orgData: node,
39
+ className: isInactive ? "text-muted-foreground line-through" : undefined,
40
+ actions: (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0", onClick: (e) => {
41
+ e.stopPropagation();
42
+ onAddChild(node);
43
+ }, title: "Add child organization", children: _jsx(Plus, { className: "h-3 w-3" }) }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0", onClick: (e) => {
44
+ e.stopPropagation();
45
+ onEdit(node);
46
+ }, title: "Edit organization", children: _jsx(Edit, { className: "h-3 w-3" }) }), node.active !== false && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0 text-destructive hover:text-destructive", onClick: (e) => {
47
+ e.stopPropagation();
48
+ onDelete(node);
49
+ }, title: "Deactivate organization", children: _jsx(Trash2, { className: "h-3 w-3" }) }))] })),
50
+ };
51
+ if (hasChildren) {
52
+ item.children = convertToTreeData(node.children, onEdit, onDelete, onAddChild);
53
+ }
54
+ return item;
55
+ });
56
+ }
57
+ // section: component
58
+ /**
59
+ * Organization Hierarchy tab component for managing multi-tenancy organizations
60
+ * Displays organizations in a tree view for intuitive hierarchy configuration
61
+ * @param props - Component props
62
+ * @returns Organization Hierarchy tab component
63
+ */
64
+ export function OrgHierarchyTab({ className, isGlobalAdmin = false, }) {
65
+ const { apiBasePath } = useHazoAuthConfig();
66
+ // State
67
+ const [tree, setTree] = useState([]);
68
+ const [loading, setLoading] = useState(true);
69
+ const [actionLoading, setActionLoading] = useState(false);
70
+ const [selectedItem, setSelectedItem] = useState();
71
+ const [showInactive, setShowInactive] = useState(false);
72
+ // Dialog state
73
+ const [addDialogOpen, setAddDialogOpen] = useState(false);
74
+ const [editDialogOpen, setEditDialogOpen] = useState(false);
75
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
76
+ const [selectedOrg, setSelectedOrg] = useState(null);
77
+ const [addParentOrg, setAddParentOrg] = useState(null);
78
+ // Form state
79
+ const [newName, setNewName] = useState("");
80
+ const [newUserLimit, setNewUserLimit] = useState(0);
81
+ const [editName, setEditName] = useState("");
82
+ const [editUserLimit, setEditUserLimit] = useState(0);
83
+ // Load tree data
84
+ const loadTree = useCallback(async () => {
85
+ setLoading(true);
86
+ try {
87
+ const params = new URLSearchParams({
88
+ action: "tree",
89
+ include_inactive: showInactive.toString(),
90
+ });
91
+ const response = await fetch(`${apiBasePath}/org_management/orgs?${params}`);
92
+ const data = await response.json();
93
+ if (data.success) {
94
+ setTree(data.tree || []);
95
+ }
96
+ else {
97
+ if (data.code === "MULTI_TENANCY_DISABLED") {
98
+ toast.error("Multi-tenancy is not enabled");
99
+ }
100
+ else {
101
+ toast.error(data.error || "Failed to load organization hierarchy");
102
+ }
103
+ setTree([]);
104
+ }
105
+ }
106
+ catch (error) {
107
+ toast.error("Failed to load organization hierarchy");
108
+ setTree([]);
109
+ }
110
+ finally {
111
+ setLoading(false);
112
+ }
113
+ }, [apiBasePath, showInactive]);
114
+ // Load data on mount and when showInactive changes
115
+ useEffect(() => {
116
+ void loadTree();
117
+ }, [loadTree]);
118
+ // Handle add org (root level) - only for global admins
119
+ const handleAddRootOrg = () => {
120
+ if (!isGlobalAdmin) {
121
+ toast.error("Only global admins can create root organizations");
122
+ return;
123
+ }
124
+ setAddParentOrg(null);
125
+ setNewName("");
126
+ setNewUserLimit(0);
127
+ setAddDialogOpen(true);
128
+ };
129
+ // Handle add child org
130
+ const handleAddChildOrg = (parent) => {
131
+ setAddParentOrg(parent);
132
+ setNewName("");
133
+ setNewUserLimit(0);
134
+ setAddDialogOpen(true);
135
+ };
136
+ // Handle edit org
137
+ const openEditDialog = (org) => {
138
+ setSelectedOrg(org);
139
+ setEditName(org.name);
140
+ setEditUserLimit(org.user_limit || 0);
141
+ setEditDialogOpen(true);
142
+ };
143
+ // Handle delete org
144
+ const openDeleteDialog = (org) => {
145
+ setSelectedOrg(org);
146
+ setDeleteDialogOpen(true);
147
+ };
148
+ // Create org
149
+ const handleCreateOrg = async () => {
150
+ if (!newName.trim()) {
151
+ toast.error("Name is required");
152
+ return;
153
+ }
154
+ setActionLoading(true);
155
+ try {
156
+ const body = {
157
+ name: newName.trim(),
158
+ user_limit: newUserLimit,
159
+ };
160
+ if (addParentOrg) {
161
+ body.parent_org_id = addParentOrg.id;
162
+ }
163
+ const response = await fetch(`${apiBasePath}/org_management/orgs`, {
164
+ method: "POST",
165
+ headers: { "Content-Type": "application/json" },
166
+ body: JSON.stringify(body),
167
+ });
168
+ const data = await response.json();
169
+ if (data.success) {
170
+ toast.success("Organization created successfully");
171
+ setAddDialogOpen(false);
172
+ setNewName("");
173
+ setNewUserLimit(0);
174
+ setAddParentOrg(null);
175
+ await loadTree();
176
+ }
177
+ else {
178
+ toast.error(data.error || "Failed to create organization");
179
+ }
180
+ }
181
+ catch (error) {
182
+ toast.error("Failed to create organization");
183
+ }
184
+ finally {
185
+ setActionLoading(false);
186
+ }
187
+ };
188
+ // Update org
189
+ const handleUpdateOrg = async () => {
190
+ if (!selectedOrg)
191
+ return;
192
+ if (!editName.trim()) {
193
+ toast.error("Name is required");
194
+ return;
195
+ }
196
+ setActionLoading(true);
197
+ try {
198
+ const body = {
199
+ org_id: selectedOrg.id,
200
+ name: editName.trim(),
201
+ user_limit: editUserLimit,
202
+ };
203
+ const response = await fetch(`${apiBasePath}/org_management/orgs`, {
204
+ method: "PATCH",
205
+ headers: { "Content-Type": "application/json" },
206
+ body: JSON.stringify(body),
207
+ });
208
+ const data = await response.json();
209
+ if (data.success) {
210
+ toast.success("Organization updated successfully");
211
+ setEditDialogOpen(false);
212
+ setSelectedOrg(null);
213
+ setEditName("");
214
+ setEditUserLimit(0);
215
+ await loadTree();
216
+ }
217
+ else {
218
+ toast.error(data.error || "Failed to update organization");
219
+ }
220
+ }
221
+ catch (error) {
222
+ toast.error("Failed to update organization");
223
+ }
224
+ finally {
225
+ setActionLoading(false);
226
+ }
227
+ };
228
+ // Delete (deactivate) org
229
+ const handleDeleteOrg = async () => {
230
+ if (!selectedOrg)
231
+ return;
232
+ setActionLoading(true);
233
+ try {
234
+ const params = new URLSearchParams({
235
+ org_id: selectedOrg.id,
236
+ });
237
+ const response = await fetch(`${apiBasePath}/org_management/orgs?${params}`, {
238
+ method: "DELETE",
239
+ });
240
+ const data = await response.json();
241
+ if (data.success) {
242
+ toast.success("Organization deactivated successfully");
243
+ setDeleteDialogOpen(false);
244
+ setSelectedOrg(null);
245
+ await loadTree();
246
+ }
247
+ else {
248
+ toast.error(data.error || "Failed to deactivate organization");
249
+ }
250
+ }
251
+ catch (error) {
252
+ toast.error("Failed to deactivate organization");
253
+ }
254
+ finally {
255
+ setActionLoading(false);
256
+ }
257
+ };
258
+ // Convert tree to TreeDataItem format
259
+ const treeData = useMemo(() => {
260
+ return convertToTreeData(tree, openEditDialog, openDeleteDialog, handleAddChildOrg);
261
+ }, [tree]);
262
+ // Handle tree item selection
263
+ const handleSelectChange = (item) => {
264
+ setSelectedItem(item);
265
+ };
266
+ return (_jsxs("div", { className: `cls_org_hierarchy_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_org_hierarchy_header flex items-center justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "cls_org_hierarchy_header_left flex items-center gap-4", children: [_jsxs("div", { className: "cls_org_hierarchy_inactive_toggle flex items-center gap-2", children: [_jsx("input", { type: "checkbox", id: "show_inactive", checked: showInactive, onChange: (e) => setShowInactive(e.target.checked), className: "h-4 w-4 rounded border-gray-300" }), _jsx(Label, { htmlFor: "show_inactive", className: "text-sm font-medium cursor-pointer", children: "Show inactive" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadTree(), disabled: loading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx("div", { className: "cls_org_hierarchy_header_right", children: isGlobalAdmin && (_jsxs(Button, { onClick: handleAddRootOrg, variant: "default", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Root Organization"] })) })] }), loading ? (_jsx("div", { className: "cls_org_hierarchy_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : tree.length === 0 ? (_jsxs("div", { className: "cls_org_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center mb-4", children: "No organizations found" }), isGlobalAdmin && (_jsxs(Button, { onClick: handleAddRootOrg, variant: "outline", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Create First Organization"] }))] })) : (_jsx("div", { className: "cls_org_hierarchy_tree_container border rounded-lg overflow-auto w-full min-h-[300px]", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleSelectChange, className: "w-full" }) })), (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.orgData) && (_jsxs("div", { className: "cls_org_hierarchy_selected_info p-4 border rounded-lg bg-muted/50", children: [_jsx("h4", { className: "font-medium mb-2", children: "Selected Organization" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Name:" }), " ", selectedItem.orgData.name] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Users:" }), " ", getUserCountDisplay(selectedItem.orgData)] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Status:" }), " ", selectedItem.orgData.active === false ? (_jsx("span", { className: "text-destructive", children: "Inactive" })) : (_jsx("span", { className: "text-green-600", children: "Active" }))] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "ID:" }), " ", _jsxs("span", { className: "font-mono text-xs", children: [selectedItem.orgData.id.slice(0, 8), "..."] })] })] })] })), _jsx(Dialog, { open: addDialogOpen, onOpenChange: setAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_org_hierarchy_add_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: addParentOrg
267
+ ? `Add Child Organization to "${addParentOrg.name}"`
268
+ : "Add Root Organization" }), _jsxs(DialogDescription, { children: ["Create a new organization", addParentOrg &&
269
+ ` as a child of "${addParentOrg.name}".`] })] }), _jsxs("div", { className: "flex flex-col gap-4 py-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_org_name", children: "Name *" }), _jsx(Input, { id: "new_org_name", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Enter organization name" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_org_user_limit", children: "User Limit (0 = unlimited)" }), _jsx(Input, { id: "new_org_user_limit", type: "number", min: 0, value: newUserLimit, onChange: (e) => setNewUserLimit(parseInt(e.target.value) || 0), placeholder: "0" })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleCreateOrg, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Creating..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Create"] })) }), _jsxs(Button, { onClick: () => setAddDialogOpen(false), variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(Dialog, { open: editDialogOpen, onOpenChange: setEditDialogOpen, children: _jsxs(DialogContent, { className: "cls_org_hierarchy_edit_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Edit Organization" }), _jsxs(DialogDescription, { children: ["Update organization: ", selectedOrg === null || selectedOrg === void 0 ? void 0 : selectedOrg.name] })] }), _jsxs("div", { className: "flex flex-col gap-4 py-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "edit_org_name", children: "Name *" }), _jsx(Input, { id: "edit_org_name", value: editName, onChange: (e) => setEditName(e.target.value), placeholder: "Enter organization name" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "edit_org_user_limit", children: "User Limit (0 = unlimited)" }), _jsx(Input, { id: "edit_org_user_limit", type: "number", min: 0, value: editUserLimit, onChange: (e) => setEditUserLimit(parseInt(e.target.value) || 0), placeholder: "0" })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleUpdateOrg, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Save"] })) }), _jsxs(Button, { onClick: () => {
270
+ setEditDialogOpen(false);
271
+ setSelectedOrg(null);
272
+ }, variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(AlertDialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Deactivate Organization" }), _jsx(AlertDialogDescription, { children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "h-5 w-5 text-amber-500 mt-0.5 flex-shrink-0" }), _jsxs("span", { children: ["Are you sure you want to deactivate \"", selectedOrg === null || selectedOrg === void 0 ? void 0 : selectedOrg.name, "\"? This will mark the organization as inactive but will not delete it. Users in this organization may lose access."] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogAction, { onClick: handleDeleteOrg, disabled: actionLoading, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deactivating..."] })) : ("Deactivate") }), _jsx(AlertDialogCancel, { onClick: () => {
273
+ setDeleteDialogOpen(false);
274
+ setSelectedOrg(null);
275
+ }, children: "Cancel" })] })] }) })] }));
276
+ }
@@ -2,6 +2,8 @@ export type UserManagementLayoutProps = {
2
2
  className?: string;
3
3
  /** Whether HRBAC is enabled (passed from server) */
4
4
  hrbacEnabled?: boolean;
5
+ /** Whether multi-tenancy is enabled (passed from server) */
6
+ multiTenancyEnabled?: boolean;
5
7
  /** Default organization for HRBAC scopes */
6
8
  defaultOrg?: string;
7
9
  };
@@ -16,5 +18,5 @@ export type UserManagementLayoutProps = {
16
18
  * @param props - Component props
17
19
  * @returns User Management layout component
18
20
  */
19
- export declare function UserManagementLayout({ className, hrbacEnabled, defaultOrg }: UserManagementLayoutProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function UserManagementLayout({ className, hrbacEnabled, multiTenancyEnabled, defaultOrg }: UserManagementLayoutProps): import("react/jsx-runtime").JSX.Element;
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/user_management/index.tsx"],"names":[],"mappings":"AAgDA,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAsBF;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,YAAoB,EAAE,UAAe,EAAE,EAAE,yBAAyB,2CAivCnH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/user_management/index.tsx"],"names":[],"mappings":"AAiDA,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,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAsBF;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,YAAoB,EAAE,mBAA2B,EAAE,UAAe,EAAE,EAAE,yBAAyB,2CAkwChJ"}
@@ -17,6 +17,7 @@ import { RolesMatrix } from "./components/roles_matrix";
17
17
  import { ScopeHierarchyTab } from "./components/scope_hierarchy_tab";
18
18
  import { ScopeLabelsTab } from "./components/scope_labels_tab";
19
19
  import { UserScopesTab } from "./components/user_scopes_tab";
20
+ import { OrgHierarchyTab } from "./components/org_hierarchy_tab";
20
21
  import { UserX, KeyRound, Edit, Trash2, Loader2, CircleCheck, CircleX, Plus, UserPlus } from "lucide-react";
21
22
  import { toast } from "sonner";
22
23
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
@@ -33,7 +34,7 @@ import { useHazoAuthConfig } from "../../../contexts/hazo_auth_provider";
33
34
  * @param props - Component props
34
35
  * @returns User Management layout component
35
36
  */
36
- export function UserManagementLayout({ className, hrbacEnabled = false, defaultOrg = "" }) {
37
+ export function UserManagementLayout({ className, hrbacEnabled = false, multiTenancyEnabled = false, defaultOrg = "" }) {
37
38
  const { apiBasePath } = useHazoAuthConfig();
38
39
  // Permission checks
39
40
  const authResult = use_hazo_auth();
@@ -47,6 +48,10 @@ export function UserManagementLayout({ className, hrbacEnabled = false, defaultO
47
48
  authResult.permissions.includes("admin_scope_hierarchy_management");
48
49
  const hasUserScopeAssignmentPermission = authResult.authenticated &&
49
50
  authResult.permissions.includes("admin_user_scope_assignment");
51
+ const hasOrgManagementPermission = authResult.authenticated &&
52
+ authResult.permissions.includes("hazo_perm_org_management");
53
+ const hasOrgGlobalAdminPermission = authResult.authenticated &&
54
+ authResult.permissions.includes("hazo_org_global_admin");
50
55
  // Determine which tabs to show
51
56
  const showUsersTab = hasUserManagementPermission;
52
57
  const showRolesTab = hasRoleManagementPermission;
@@ -54,7 +59,8 @@ export function UserManagementLayout({ className, hrbacEnabled = false, defaultO
54
59
  const showScopeHierarchyTab = hrbacEnabled && hasScopeHierarchyPermission;
55
60
  const showScopeLabelsTab = hrbacEnabled && hasScopeHierarchyPermission;
56
61
  const showUserScopesTab = hrbacEnabled && hasUserScopeAssignmentPermission;
57
- const hasAnyPermission = showUsersTab || showRolesTab || showPermissionsTab || showScopeHierarchyTab || showScopeLabelsTab || showUserScopesTab;
62
+ const showOrgsTab = multiTenancyEnabled && hasOrgManagementPermission;
63
+ const hasAnyPermission = showUsersTab || showRolesTab || showPermissionsTab || showScopeHierarchyTab || showScopeLabelsTab || showUserScopesTab || showOrgsTab;
58
64
  // Tab 1: Users state
59
65
  const [users, setUsers] = useState([]);
60
66
  const [usersLoading, setUsersLoading] = useState(true);
@@ -436,7 +442,7 @@ export function UserManagementLayout({ className, hrbacEnabled = false, defaultO
436
442
  showPermissionsTab ? "permissions" :
437
443
  showScopeLabelsTab ? "scope_labels" :
438
444
  showScopeHierarchyTab ? "scope_hierarchy" :
439
- showUserScopesTab ? "user_scopes" : "users", className: "cls_user_management_tabs w-full", children: [_jsxs(TabsList, { className: "cls_user_management_tabs_list flex w-full flex-wrap", children: [showUsersTab && (_jsx(TabsTrigger, { value: "users", className: "cls_user_management_tabs_trigger flex-1", children: "Manage Users" })), showRolesTab && (_jsx(TabsTrigger, { value: "roles", className: "cls_user_management_tabs_trigger flex-1", children: "Roles" })), showPermissionsTab && (_jsx(TabsTrigger, { value: "permissions", className: "cls_user_management_tabs_trigger flex-1", children: "Permissions" })), showScopeLabelsTab && (_jsx(TabsTrigger, { value: "scope_labels", className: "cls_user_management_tabs_trigger flex-1", children: "Scope Labels" })), showScopeHierarchyTab && (_jsx(TabsTrigger, { value: "scope_hierarchy", className: "cls_user_management_tabs_trigger flex-1", children: "Scope Hierarchy" })), showUserScopesTab && (_jsx(TabsTrigger, { value: "user_scopes", className: "cls_user_management_tabs_trigger flex-1", children: "User Scopes" }))] }), showUsersTab && (_jsx(TabsContent, { value: "users", className: "cls_user_management_tab_users w-full", children: usersLoading ? (_jsx("div", { className: "cls_user_management_users_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "cls_user_management_users_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_user_management_users_table w-full", children: [_jsx(TableHeader, { className: "cls_user_management_users_table_header", children: _jsxs(TableRow, { className: "cls_user_management_users_table_header_row", children: [_jsx(TableHead, { className: "cls_user_management_users_table_header_profile_pic w-16", children: "Photo" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_id", children: "ID" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_name", children: "Name" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_email", children: "Email" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_email_verified", children: "Email Verified" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_is_active", children: "Active" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_last_logon", children: "Last Logon" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_created_at", children: "Created At" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_actions text-right", children: "Actions" })] }) }), _jsx(TableBody, { className: "cls_user_management_users_table_body", children: users.length === 0 ? (_jsx(TableRow, { className: "cls_user_management_users_table_row_empty", children: _jsx(TableCell, { colSpan: 9, className: "text-center text-muted-foreground py-8", children: "No users found." }) })) : (users.map((user) => (_jsxs(TableRow, { className: "cls_user_management_users_table_row cursor-pointer hover:bg-muted/50", onClick: () => {
445
+ showUserScopesTab ? "user_scopes" : "users", className: "cls_user_management_tabs w-full", children: [_jsxs(TabsList, { className: "cls_user_management_tabs_list flex w-full flex-wrap", children: [showUsersTab && (_jsx(TabsTrigger, { value: "users", className: "cls_user_management_tabs_trigger flex-1", children: "Manage Users" })), showRolesTab && (_jsx(TabsTrigger, { value: "roles", className: "cls_user_management_tabs_trigger flex-1", children: "Roles" })), showPermissionsTab && (_jsx(TabsTrigger, { value: "permissions", className: "cls_user_management_tabs_trigger flex-1", children: "Permissions" })), showScopeLabelsTab && (_jsx(TabsTrigger, { value: "scope_labels", className: "cls_user_management_tabs_trigger flex-1", children: "Scope Labels" })), showScopeHierarchyTab && (_jsx(TabsTrigger, { value: "scope_hierarchy", className: "cls_user_management_tabs_trigger flex-1", children: "Scope Hierarchy" })), showUserScopesTab && (_jsx(TabsTrigger, { value: "user_scopes", className: "cls_user_management_tabs_trigger flex-1", children: "User Scopes" })), showOrgsTab && (_jsx(TabsTrigger, { value: "organizations", className: "cls_user_management_tabs_trigger flex-1", children: "Organizations" }))] }), showUsersTab && (_jsx(TabsContent, { value: "users", className: "cls_user_management_tab_users w-full", children: usersLoading ? (_jsx("div", { className: "cls_user_management_users_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "cls_user_management_users_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_user_management_users_table w-full", children: [_jsx(TableHeader, { className: "cls_user_management_users_table_header", children: _jsxs(TableRow, { className: "cls_user_management_users_table_header_row", children: [_jsx(TableHead, { className: "cls_user_management_users_table_header_profile_pic w-16", children: "Photo" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_id", children: "ID" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_name", children: "Name" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_email", children: "Email" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_email_verified", children: "Email Verified" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_is_active", children: "Active" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_last_logon", children: "Last Logon" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_created_at", children: "Created At" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_actions text-right", children: "Actions" })] }) }), _jsx(TableBody, { className: "cls_user_management_users_table_body", children: users.length === 0 ? (_jsx(TableRow, { className: "cls_user_management_users_table_row_empty", children: _jsx(TableCell, { colSpan: 9, className: "text-center text-muted-foreground py-8", children: "No users found." }) })) : (users.map((user) => (_jsxs(TableRow, { className: "cls_user_management_users_table_row cursor-pointer hover:bg-muted/50", onClick: () => {
440
446
  setSelectedUser(user);
441
447
  setUserDetailDialogOpen(true);
442
448
  }, children: [_jsx(TableCell, { className: "cls_user_management_users_table_cell_profile_pic", children: _jsxs(Avatar, { className: "cls_user_management_users_table_avatar h-8 w-8", children: [_jsx(AvatarImage, { src: user.profile_picture_url || undefined, alt: user.name ? `Profile picture of ${user.name}` : "Profile picture", className: "cls_user_management_users_table_avatar_image" }), _jsx(AvatarFallback, { className: "cls_user_management_users_table_avatar_fallback bg-slate-200 text-slate-600 text-xs", children: getUserInitials(user) })] }) }), _jsxs(TableCell, { className: "cls_user_management_users_table_cell_id font-mono text-xs", children: [user.id.substring(0, 8), "..."] }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_name", children: user.name || "-" }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_email", children: user.email_address }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_email_verified", children: user.email_verified ? (_jsx("span", { className: "text-green-600", children: "Yes" })) : (_jsx("span", { className: "text-red-600", children: "No" })) }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_is_active", children: user.is_active ? (_jsx("span", { className: "text-green-600", children: "Active" })) : (_jsx("span", { className: "text-red-600", children: "Inactive" })) }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_last_logon", children: user.last_logon
@@ -461,7 +467,7 @@ export function UserManagementLayout({ className, hrbacEnabled = false, defaultO
461
467
  setEditingPermission(permission);
462
468
  setEditDescription(permission.description);
463
469
  setEditPermissionDialogOpen(true);
464
- }, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_edit", children: [_jsx(Edit, { className: "h-4 w-4 mr-1" }), "Edit"] }), _jsxs(Button, { onClick: () => handleDeletePermission(permission), disabled: permissionsActionLoading, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_delete text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-1" }), "Delete"] })] })) }) })] }, `${permission.source}-${permission.id}-${permission.permission_name}`)))) })] }) }))] }) })), showScopeLabelsTab && (_jsx(TabsContent, { value: "scope_labels", className: "cls_user_management_tab_scope_labels w-full", children: _jsx(ScopeLabelsTab, { defaultOrg: defaultOrg }) })), showScopeHierarchyTab && (_jsx(TabsContent, { value: "scope_hierarchy", className: "cls_user_management_tab_scope_hierarchy w-full", children: _jsx(ScopeHierarchyTab, { defaultOrg: defaultOrg }) })), showUserScopesTab && (_jsx(TabsContent, { value: "user_scopes", className: "cls_user_management_tab_user_scopes w-full", children: _jsx(UserScopesTab, {}) }))] })), _jsx(AlertDialog, { open: deactivateDialogOpen, onOpenChange: setDeactivateDialogOpen, children: _jsxs(AlertDialogContent, { className: "cls_user_management_deactivate_dialog", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Deactivate User" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to deactivate ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), "? They will not be able to log in until reactivated."] })] }), _jsxs(AlertDialogFooter, { className: "cls_user_management_deactivate_dialog_footer", children: [_jsx(AlertDialogAction, { onClick: handleDeactivateUser, disabled: usersActionLoading, className: "cls_user_management_deactivate_dialog_confirm", children: usersActionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deactivating..."] })) : ("Deactivate") }), _jsx(AlertDialogCancel, { onClick: () => {
470
+ }, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_edit", children: [_jsx(Edit, { className: "h-4 w-4 mr-1" }), "Edit"] }), _jsxs(Button, { onClick: () => handleDeletePermission(permission), disabled: permissionsActionLoading, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_delete text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-1" }), "Delete"] })] })) }) })] }, `${permission.source}-${permission.id}-${permission.permission_name}`)))) })] }) }))] }) })), showScopeLabelsTab && (_jsx(TabsContent, { value: "scope_labels", className: "cls_user_management_tab_scope_labels w-full", children: _jsx(ScopeLabelsTab, { defaultOrg: defaultOrg }) })), showScopeHierarchyTab && (_jsx(TabsContent, { value: "scope_hierarchy", className: "cls_user_management_tab_scope_hierarchy w-full", children: _jsx(ScopeHierarchyTab, { defaultOrg: defaultOrg }) })), showUserScopesTab && (_jsx(TabsContent, { value: "user_scopes", className: "cls_user_management_tab_user_scopes w-full", children: _jsx(UserScopesTab, {}) })), showOrgsTab && (_jsx(TabsContent, { value: "organizations", className: "cls_user_management_tab_organizations w-full", children: _jsx(OrgHierarchyTab, { isGlobalAdmin: hasOrgGlobalAdminPermission }) }))] })), _jsx(AlertDialog, { open: deactivateDialogOpen, onOpenChange: setDeactivateDialogOpen, children: _jsxs(AlertDialogContent, { className: "cls_user_management_deactivate_dialog", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Deactivate User" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to deactivate ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), "? They will not be able to log in until reactivated."] })] }), _jsxs(AlertDialogFooter, { className: "cls_user_management_deactivate_dialog_footer", children: [_jsx(AlertDialogAction, { onClick: handleDeactivateUser, disabled: usersActionLoading, className: "cls_user_management_deactivate_dialog_confirm", children: usersActionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deactivating..."] })) : ("Deactivate") }), _jsx(AlertDialogCancel, { onClick: () => {
465
471
  setDeactivateDialogOpen(false);
466
472
  setSelectedUser(null);
467
473
  }, className: "cls_user_management_deactivate_dialog_cancel", children: "Cancel" })] })] }) }), _jsx(AlertDialog, { open: resetPasswordDialogOpen, onOpenChange: setResetPasswordDialogOpen, children: _jsxs(AlertDialogContent, { className: "cls_user_management_reset_password_dialog", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Reset Password" }), _jsxs(AlertDialogDescription, { children: ["Send a password reset email to ", selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address, "? They will receive a link to reset their password."] })] }), _jsxs(AlertDialogFooter, { className: "cls_user_management_reset_password_dialog_footer", children: [_jsx(AlertDialogAction, { onClick: handleResetPassword, disabled: usersActionLoading, className: "cls_user_management_reset_password_dialog_confirm", children: usersActionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Sending..."] })) : ("Send Reset Email") }), _jsx(AlertDialogCancel, { onClick: () => {
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
4
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
5
5
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -1,12 +1,6 @@
1
1
  /**
2
- * Creates a logger service instance for use in UI components
3
- * This uses the main app logging service and can be extended with an external logger
4
- * when provided as part of component setup
2
+ * Returns the hazo_auth logger instance
3
+ * Uses hazo_logs for consistent logging across hazo packages
5
4
  */
6
- export declare const create_app_logger: (external_logger?: {
7
- info?: (message: string, data?: Record<string, unknown>) => void;
8
- error?: (message: string, data?: Record<string, unknown>) => void;
9
- warn?: (message: string, data?: Record<string, unknown>) => void;
10
- debug?: (message: string, data?: Record<string, unknown>) => void;
11
- }) => import("../server/types/app_types").logger_service;
5
+ export declare const create_app_logger: () => import("hazo_logs").Logger;
12
6
  //# sourceMappingURL=app_logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app_logger.d.ts","sourceRoot":"","sources":["../../src/lib/app_logger.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,kBAAkB;IAChB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACjE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACjE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACnE,uDAGF,CAAC"}
1
+ {"version":3,"file":"app_logger.d.ts","sourceRoot":"","sources":["../../src/lib/app_logger.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,kCAAe,CAAC"}
@@ -1,14 +1,11 @@
1
- // file_description: client-accessible wrapper for the main app logging service
1
+ // file_description: client-accessible wrapper for the main app logging service using hazo_logs
2
2
  // section: imports
3
- import { create_logger_service } from "../server/logging/logger_service";
4
- // section: constants
5
- const APP_NAMESPACE = "hazo_auth_ui";
3
+ import { createLogger } from "hazo_logs";
6
4
  // section: logger_instance
5
+ // Create a singleton logger for the hazo_auth package
6
+ const logger = createLogger("hazo_auth");
7
7
  /**
8
- * Creates a logger service instance for use in UI components
9
- * This uses the main app logging service and can be extended with an external logger
10
- * when provided as part of component setup
8
+ * Returns the hazo_auth logger instance
9
+ * Uses hazo_logs for consistent logging across hazo packages
11
10
  */
12
- export const create_app_logger = (external_logger) => {
13
- return create_logger_service(APP_NAMESPACE, external_logger);
14
- };
11
+ export const create_app_logger = () => logger;
@@ -7,6 +7,12 @@ export type HazoAuthUser = {
7
7
  email_address: string;
8
8
  is_active: boolean;
9
9
  profile_picture_url: string | null;
10
+ org_id?: string | null;
11
+ org_name?: string | null;
12
+ parent_org_id?: string | null;
13
+ parent_org_name?: string | null;
14
+ root_org_id?: string | null;
15
+ root_org_name?: string | null;
10
16
  };
11
17
  /**
12
18
  * Scope access information returned when HRBAC scope checking is used
@@ -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;CACpC,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;;;;GAIG;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;CACpC,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;CAClB,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;CACpB,CAAC;AAEF;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAE/B,mBAAmB,EAAE,MAAM,EAAE;IAC7B,gBAAgB,EAAE,MAAM,EAAE;IAC1B,oBAAoB,EAAE,MAAM,EAAE;IAC9B,qBAAqB,CAAC,EAAE,MAAM;gBAH9B,mBAAmB,EAAE,MAAM,EAAE,EAC7B,gBAAgB,EAAE,MAAM,EAAE,EAC1B,oBAAoB,EAAE,MAAM,EAAE,EAC9B,qBAAqB,CAAC,EAAE,MAAM,YAAA;CAKxC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEhC,UAAU,EAAE,MAAM;IAClB,gBAAgB,EAAE,MAAM;IACxB,WAAW,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;gBAF/E,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAKzF"}
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,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;;;;GAIG;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;CACpC,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;CAClB,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;CACpB,CAAC;AAEF;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAE/B,mBAAmB,EAAE,MAAM,EAAE;IAC7B,gBAAgB,EAAE,MAAM,EAAE;IAC1B,oBAAoB,EAAE,MAAM,EAAE;IAC9B,qBAAqB,CAAC,EAAE,MAAM;gBAH9B,mBAAmB,EAAE,MAAM,EAAE,EAC7B,gBAAgB,EAAE,MAAM,EAAE,EAC1B,oBAAoB,EAAE,MAAM,EAAE,EAC9B,qBAAqB,CAAC,EAAE,MAAM,YAAA;CAKxC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEhC,UAAU,EAAE,MAAM;IAClB,gBAAgB,EAAE,MAAM;IACxB,WAAW,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;gBAF/E,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAKzF"}
@@ -0,0 +1,38 @@
1
+ import type { NextRequest } from "next/server";
2
+ export type DevLockValidationResult = {
3
+ valid: boolean;
4
+ expired?: boolean;
5
+ };
6
+ export type DevLockCookieData = {
7
+ value: string;
8
+ max_age: number;
9
+ };
10
+ /**
11
+ * Creates a signed dev lock cookie value
12
+ * Cookie format: timestamp|expiry_timestamp|signature
13
+ * @param password - The dev lock password (used as signing key)
14
+ * @param expiry_days - Number of days until cookie expires (default: 7)
15
+ * @returns Cookie value and max_age in seconds
16
+ */
17
+ export declare function create_dev_lock_cookie(password: string, expiry_days?: number): Promise<DevLockCookieData>;
18
+ /**
19
+ * Validates dev lock cookie from request (Edge-compatible)
20
+ * Checks signature validity and expiration
21
+ * @param request - NextRequest object
22
+ * @returns Validation result with valid flag and optional expired flag
23
+ */
24
+ export declare function validate_dev_lock_cookie(request: NextRequest): Promise<DevLockValidationResult>;
25
+ /**
26
+ * Validates password against environment variable (for unlock endpoint)
27
+ * Uses constant-time comparison to prevent timing attacks
28
+ * @param password - Password to validate
29
+ * @returns true if password matches
30
+ */
31
+ export declare function validate_dev_lock_password(password: string): boolean;
32
+ /**
33
+ * Gets the dev lock cookie name
34
+ * Exported for use in API routes when setting the cookie
35
+ * @returns Cookie name string
36
+ */
37
+ export declare function get_dev_lock_cookie_name(): string;
38
+ //# sourceMappingURL=dev_lock_validator.edge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev_lock_validator.edge.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/dev_lock_validator.edge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAmDF;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,MAAU,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAU5B;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,uBAAuB,CAAC,CA8ClC;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQpE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD"}
@@ -0,0 +1,122 @@
1
+ // section: constants
2
+ const COOKIE_NAME = "hazo_auth_dev_lock";
3
+ const SEPARATOR = "|";
4
+ // section: helpers
5
+ /**
6
+ * Creates HMAC-SHA256 signature using Web Crypto API (Edge compatible)
7
+ * @param data - Data to sign
8
+ * @param secret - Secret key for signing
9
+ * @returns Hex string signature
10
+ */
11
+ async function create_signature(data, secret) {
12
+ const encoder = new TextEncoder();
13
+ const key_data = encoder.encode(secret);
14
+ const message_data = encoder.encode(data);
15
+ const crypto_key = await crypto.subtle.importKey("raw", key_data, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
16
+ const signature = await crypto.subtle.sign("HMAC", crypto_key, message_data);
17
+ // Convert ArrayBuffer to hex string
18
+ return Array.from(new Uint8Array(signature))
19
+ .map((b) => b.toString(16).padStart(2, "0"))
20
+ .join("");
21
+ }
22
+ /**
23
+ * Performs constant-time comparison of two strings
24
+ * Prevents timing attacks
25
+ * @param a - First string
26
+ * @param b - Second string
27
+ * @returns true if strings are equal
28
+ */
29
+ function constant_time_compare(a, b) {
30
+ if (a.length !== b.length) {
31
+ return false;
32
+ }
33
+ let result = 0;
34
+ for (let i = 0; i < a.length; i++) {
35
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
36
+ }
37
+ return result === 0;
38
+ }
39
+ // section: main_functions
40
+ /**
41
+ * Creates a signed dev lock cookie value
42
+ * Cookie format: timestamp|expiry_timestamp|signature
43
+ * @param password - The dev lock password (used as signing key)
44
+ * @param expiry_days - Number of days until cookie expires (default: 7)
45
+ * @returns Cookie value and max_age in seconds
46
+ */
47
+ export async function create_dev_lock_cookie(password, expiry_days = 7) {
48
+ const timestamp = Date.now();
49
+ const expiry_timestamp = timestamp + expiry_days * 24 * 60 * 60 * 1000;
50
+ const data = `${timestamp}${SEPARATOR}${expiry_timestamp}`;
51
+ const signature = await create_signature(data, password);
52
+ return {
53
+ value: `${data}${SEPARATOR}${signature}`,
54
+ max_age: expiry_days * 24 * 60 * 60, // in seconds
55
+ };
56
+ }
57
+ /**
58
+ * Validates dev lock cookie from request (Edge-compatible)
59
+ * Checks signature validity and expiration
60
+ * @param request - NextRequest object
61
+ * @returns Validation result with valid flag and optional expired flag
62
+ */
63
+ export async function validate_dev_lock_cookie(request) {
64
+ var _a;
65
+ const password = process.env.HAZO_AUTH_DEV_LOCK_PASSWORD;
66
+ if (!password) {
67
+ // No password set - cannot validate
68
+ return { valid: false };
69
+ }
70
+ const cookie = (_a = request.cookies.get(COOKIE_NAME)) === null || _a === void 0 ? void 0 : _a.value;
71
+ if (!cookie) {
72
+ return { valid: false };
73
+ }
74
+ try {
75
+ const parts = cookie.split(SEPARATOR);
76
+ if (parts.length !== 3) {
77
+ return { valid: false };
78
+ }
79
+ const [timestamp_str, expiry_str, signature] = parts;
80
+ const timestamp = parseInt(timestamp_str, 10);
81
+ const expiry_timestamp = parseInt(expiry_str, 10);
82
+ if (isNaN(timestamp) || isNaN(expiry_timestamp)) {
83
+ return { valid: false };
84
+ }
85
+ // Check expiry
86
+ if (Date.now() > expiry_timestamp) {
87
+ return { valid: false, expired: true };
88
+ }
89
+ // Verify signature
90
+ const data = `${timestamp}${SEPARATOR}${expiry_timestamp}`;
91
+ const expected_signature = await create_signature(data, password);
92
+ // Constant-time comparison to prevent timing attacks
93
+ if (!constant_time_compare(signature, expected_signature)) {
94
+ return { valid: false };
95
+ }
96
+ return { valid: true };
97
+ }
98
+ catch (_b) {
99
+ return { valid: false };
100
+ }
101
+ }
102
+ /**
103
+ * Validates password against environment variable (for unlock endpoint)
104
+ * Uses constant-time comparison to prevent timing attacks
105
+ * @param password - Password to validate
106
+ * @returns true if password matches
107
+ */
108
+ export function validate_dev_lock_password(password) {
109
+ const expected = process.env.HAZO_AUTH_DEV_LOCK_PASSWORD;
110
+ if (!expected || !password) {
111
+ return false;
112
+ }
113
+ return constant_time_compare(password, expected);
114
+ }
115
+ /**
116
+ * Gets the dev lock cookie name
117
+ * Exported for use in API routes when setting the cookie
118
+ * @returns Cookie name string
119
+ */
120
+ export function get_dev_lock_cookie_name() {
121
+ return COOKIE_NAME;
122
+ }