hazo_auth 4.3.0 → 4.4.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.
Files changed (105) hide show
  1. package/cli-src/lib/already_logged_in_config.server.ts +1 -1
  2. package/cli-src/lib/app_logger.ts +1 -1
  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 +1 -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 +3 -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 +74 -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/org_management/index.d.ts +26 -0
  65. package/dist/components/layouts/org_management/index.d.ts.map +1 -0
  66. package/dist/components/layouts/org_management/index.js +75 -0
  67. package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
  68. package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
  69. package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
  70. package/dist/components/layouts/user_management/index.d.ts +3 -1
  71. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  72. package/dist/components/layouts/user_management/index.js +10 -4
  73. package/dist/lib/auth/auth_types.d.ts +6 -0
  74. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  75. package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
  76. package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
  77. package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
  78. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  79. package/dist/lib/auth/hazo_get_auth.server.js +61 -1
  80. package/dist/lib/auth/org_cache.d.ts +65 -0
  81. package/dist/lib/auth/org_cache.d.ts.map +1 -0
  82. package/dist/lib/auth/org_cache.js +103 -0
  83. package/dist/lib/config/default_config.d.ts +76 -0
  84. package/dist/lib/config/default_config.d.ts.map +1 -1
  85. package/dist/lib/config/default_config.js +42 -0
  86. package/dist/lib/dev_lock_config.server.d.ts +41 -0
  87. package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
  88. package/dist/lib/dev_lock_config.server.js +50 -0
  89. package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
  90. package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
  91. package/dist/lib/multi_tenancy_config.server.js +41 -0
  92. package/dist/lib/services/org_service.d.ts +191 -0
  93. package/dist/lib/services/org_service.d.ts.map +1 -0
  94. package/dist/lib/services/org_service.js +746 -0
  95. package/dist/page_components/dev_lock.d.ts +11 -0
  96. package/dist/page_components/dev_lock.d.ts.map +1 -0
  97. package/dist/page_components/dev_lock.js +17 -0
  98. package/dist/page_components/index.d.ts +1 -0
  99. package/dist/page_components/index.d.ts.map +1 -1
  100. package/dist/page_components/index.js +1 -0
  101. package/dist/page_components/org_management.d.ts +27 -0
  102. package/dist/page_components/org_management.d.ts.map +1 -0
  103. package/dist/page_components/org_management.js +18 -0
  104. package/hazo_auth_config.example.ini +30 -0
  105. package/package.json +23 -2
@@ -0,0 +1,746 @@
1
+ import { createCrudService } from "hazo_connect/server";
2
+ import { create_app_logger } from "../app_logger";
3
+ import { sanitize_error_for_user } from "../utils/error_sanitizer";
4
+ // section: constants
5
+ const TABLE_NAME = "hazo_org";
6
+ const USERS_TABLE_NAME = "hazo_users";
7
+ // section: helpers
8
+ /**
9
+ * Gets the user count for an organization
10
+ * @param adapter - HazoConnect adapter
11
+ * @param org_id - Organization ID
12
+ * @returns User count
13
+ */
14
+ export async function get_org_user_count(adapter, org_id) {
15
+ try {
16
+ const users_service = createCrudService(adapter, USERS_TABLE_NAME);
17
+ const users = await users_service.findBy({ org_id });
18
+ const count = Array.isArray(users) ? users.length : 0;
19
+ return {
20
+ success: true,
21
+ count,
22
+ };
23
+ }
24
+ catch (error) {
25
+ const logger = create_app_logger();
26
+ const error_message = sanitize_error_for_user(error, {
27
+ logToConsole: true,
28
+ logToLogger: true,
29
+ logger,
30
+ context: {
31
+ filename: "org_service.ts",
32
+ line_number: 0,
33
+ operation: "get_org_user_count",
34
+ org_id,
35
+ },
36
+ });
37
+ return {
38
+ success: false,
39
+ error: error_message,
40
+ };
41
+ }
42
+ }
43
+ /**
44
+ * Gets user count for the root organization (includes all child orgs)
45
+ * @param adapter - HazoConnect adapter
46
+ * @param root_org_id - Root organization ID
47
+ * @returns Total user count across org tree
48
+ */
49
+ export async function get_root_org_user_count(adapter, root_org_id) {
50
+ try {
51
+ const users_service = createCrudService(adapter, USERS_TABLE_NAME);
52
+ const users = await users_service.findBy({ root_org_id });
53
+ const count = Array.isArray(users) ? users.length : 0;
54
+ return {
55
+ success: true,
56
+ count,
57
+ };
58
+ }
59
+ catch (error) {
60
+ const logger = create_app_logger();
61
+ const error_message = sanitize_error_for_user(error, {
62
+ logToConsole: true,
63
+ logToLogger: true,
64
+ logger,
65
+ context: {
66
+ filename: "org_service.ts",
67
+ line_number: 0,
68
+ operation: "get_root_org_user_count",
69
+ root_org_id,
70
+ },
71
+ });
72
+ return {
73
+ success: false,
74
+ error: error_message,
75
+ };
76
+ }
77
+ }
78
+ // section: service_functions
79
+ /**
80
+ * Gets all organizations, optionally filtered by root_org_id
81
+ * @param adapter - HazoConnect adapter
82
+ * @param options - Filter options
83
+ * @returns List of organizations
84
+ */
85
+ export async function get_orgs(adapter, options) {
86
+ try {
87
+ const org_service = createCrudService(adapter, TABLE_NAME);
88
+ let orgs;
89
+ if (options === null || options === void 0 ? void 0 : options.root_org_id) {
90
+ // Get orgs in this org tree (by root_org_id)
91
+ orgs = await org_service.findBy({ root_org_id: options.root_org_id });
92
+ // Also include the root org itself
93
+ const root_orgs = await org_service.findBy({ id: options.root_org_id });
94
+ if (Array.isArray(root_orgs) && root_orgs.length > 0) {
95
+ orgs = [...root_orgs, ...(Array.isArray(orgs) ? orgs : [])];
96
+ }
97
+ }
98
+ else {
99
+ // Get all orgs (global admin view)
100
+ orgs = await org_service.findBy({});
101
+ }
102
+ if (!Array.isArray(orgs)) {
103
+ return {
104
+ success: true,
105
+ orgs: [],
106
+ };
107
+ }
108
+ // Filter inactive if not requested
109
+ let filtered_orgs = orgs;
110
+ if (!(options === null || options === void 0 ? void 0 : options.include_inactive)) {
111
+ filtered_orgs = filtered_orgs.filter((org) => org.active !== false);
112
+ }
113
+ return {
114
+ success: true,
115
+ orgs: filtered_orgs,
116
+ };
117
+ }
118
+ catch (error) {
119
+ const logger = create_app_logger();
120
+ const error_message = sanitize_error_for_user(error, {
121
+ logToConsole: true,
122
+ logToLogger: true,
123
+ logger,
124
+ context: {
125
+ filename: "org_service.ts",
126
+ line_number: 0,
127
+ operation: "get_orgs",
128
+ options,
129
+ },
130
+ });
131
+ return {
132
+ success: false,
133
+ error: error_message,
134
+ };
135
+ }
136
+ }
137
+ /**
138
+ * Gets a single organization by ID with computed user count
139
+ * @param adapter - HazoConnect adapter
140
+ * @param org_id - Organization ID
141
+ * @returns Organization with user count
142
+ */
143
+ export async function get_org_by_id(adapter, org_id) {
144
+ try {
145
+ const org_service = createCrudService(adapter, TABLE_NAME);
146
+ const orgs = await org_service.findBy({ id: org_id });
147
+ if (!Array.isArray(orgs) || orgs.length === 0) {
148
+ return {
149
+ success: false,
150
+ error: "Organization not found",
151
+ };
152
+ }
153
+ const org = orgs[0];
154
+ // Get user count
155
+ const count_result = await get_org_user_count(adapter, org_id);
156
+ const current_user_count = count_result.success ? count_result.count : 0;
157
+ return {
158
+ success: true,
159
+ org: Object.assign(Object.assign({}, org), { current_user_count }),
160
+ };
161
+ }
162
+ catch (error) {
163
+ const logger = create_app_logger();
164
+ const error_message = sanitize_error_for_user(error, {
165
+ logToConsole: true,
166
+ logToLogger: true,
167
+ logger,
168
+ context: {
169
+ filename: "org_service.ts",
170
+ line_number: 0,
171
+ operation: "get_org_by_id",
172
+ org_id,
173
+ },
174
+ });
175
+ return {
176
+ success: false,
177
+ error: error_message,
178
+ };
179
+ }
180
+ }
181
+ /**
182
+ * Creates a new organization
183
+ * @param adapter - HazoConnect adapter
184
+ * @param data - Organization data
185
+ * @returns Created organization
186
+ */
187
+ export async function create_org(adapter, data) {
188
+ var _a;
189
+ try {
190
+ const org_service = createCrudService(adapter, TABLE_NAME);
191
+ const now = new Date().toISOString();
192
+ // Determine root_org_id
193
+ let root_org_id = null;
194
+ if (data.parent_org_id) {
195
+ // Validate parent exists
196
+ const parent_result = await get_org_by_id(adapter, data.parent_org_id);
197
+ if (!parent_result.success || !parent_result.org) {
198
+ return {
199
+ success: false,
200
+ error: "Parent organization not found",
201
+ };
202
+ }
203
+ // If parent has a root_org_id, use it; otherwise parent IS the root
204
+ root_org_id = parent_result.org.root_org_id || data.parent_org_id;
205
+ }
206
+ const insert_data = {
207
+ name: data.name,
208
+ user_limit: (_a = data.user_limit) !== null && _a !== void 0 ? _a : 0,
209
+ parent_org_id: data.parent_org_id || null,
210
+ root_org_id: root_org_id,
211
+ active: true,
212
+ created_at: now,
213
+ created_by: data.created_by,
214
+ changed_at: now,
215
+ changed_by: data.created_by,
216
+ };
217
+ const inserted = await org_service.insert(insert_data);
218
+ if (!Array.isArray(inserted) || inserted.length === 0) {
219
+ return {
220
+ success: false,
221
+ error: "Failed to create organization",
222
+ };
223
+ }
224
+ return {
225
+ success: true,
226
+ org: inserted[0],
227
+ };
228
+ }
229
+ catch (error) {
230
+ const logger = create_app_logger();
231
+ const error_message = sanitize_error_for_user(error, {
232
+ logToConsole: true,
233
+ logToLogger: true,
234
+ logger,
235
+ context: {
236
+ filename: "org_service.ts",
237
+ line_number: 0,
238
+ operation: "create_org",
239
+ data,
240
+ },
241
+ });
242
+ return {
243
+ success: false,
244
+ error: error_message,
245
+ };
246
+ }
247
+ }
248
+ /**
249
+ * Updates an existing organization
250
+ * @param adapter - HazoConnect adapter
251
+ * @param org_id - Organization ID
252
+ * @param data - Update data
253
+ * @returns Updated organization
254
+ */
255
+ export async function update_org(adapter, org_id, data) {
256
+ try {
257
+ const org_service = createCrudService(adapter, TABLE_NAME);
258
+ const now = new Date().toISOString();
259
+ // Check org exists
260
+ const existing = await get_org_by_id(adapter, org_id);
261
+ if (!existing.success) {
262
+ return {
263
+ success: false,
264
+ error: existing.error || "Organization not found",
265
+ };
266
+ }
267
+ const update_data = {
268
+ changed_at: now,
269
+ changed_by: data.changed_by,
270
+ };
271
+ if (data.name !== undefined) {
272
+ update_data.name = data.name;
273
+ }
274
+ if (data.user_limit !== undefined) {
275
+ update_data.user_limit = data.user_limit;
276
+ }
277
+ const updated = await org_service.updateById(org_id, update_data);
278
+ if (!Array.isArray(updated) || updated.length === 0) {
279
+ return {
280
+ success: false,
281
+ error: "Failed to update organization",
282
+ };
283
+ }
284
+ return {
285
+ success: true,
286
+ org: updated[0],
287
+ };
288
+ }
289
+ catch (error) {
290
+ const logger = create_app_logger();
291
+ const error_message = sanitize_error_for_user(error, {
292
+ logToConsole: true,
293
+ logToLogger: true,
294
+ logger,
295
+ context: {
296
+ filename: "org_service.ts",
297
+ line_number: 0,
298
+ operation: "update_org",
299
+ org_id,
300
+ data,
301
+ },
302
+ });
303
+ return {
304
+ success: false,
305
+ error: error_message,
306
+ };
307
+ }
308
+ }
309
+ /**
310
+ * Soft deletes an organization (sets active = false)
311
+ * @param adapter - HazoConnect adapter
312
+ * @param org_id - Organization ID
313
+ * @param changed_by - User ID making the change
314
+ * @returns Deactivated organization
315
+ */
316
+ export async function soft_delete_org(adapter, org_id, changed_by) {
317
+ try {
318
+ const org_service = createCrudService(adapter, TABLE_NAME);
319
+ const now = new Date().toISOString();
320
+ // Check org exists
321
+ const existing = await get_org_by_id(adapter, org_id);
322
+ if (!existing.success) {
323
+ return {
324
+ success: false,
325
+ error: existing.error || "Organization not found",
326
+ };
327
+ }
328
+ const update_data = {
329
+ active: false,
330
+ changed_at: now,
331
+ changed_by: changed_by,
332
+ };
333
+ const updated = await org_service.updateById(org_id, update_data);
334
+ if (!Array.isArray(updated) || updated.length === 0) {
335
+ return {
336
+ success: false,
337
+ error: "Failed to deactivate organization",
338
+ };
339
+ }
340
+ return {
341
+ success: true,
342
+ org: updated[0],
343
+ };
344
+ }
345
+ catch (error) {
346
+ const logger = create_app_logger();
347
+ const error_message = sanitize_error_for_user(error, {
348
+ logToConsole: true,
349
+ logToLogger: true,
350
+ logger,
351
+ context: {
352
+ filename: "org_service.ts",
353
+ line_number: 0,
354
+ operation: "soft_delete_org",
355
+ org_id,
356
+ changed_by,
357
+ },
358
+ });
359
+ return {
360
+ success: false,
361
+ error: error_message,
362
+ };
363
+ }
364
+ }
365
+ /**
366
+ * Gets immediate children of an organization
367
+ * @param adapter - HazoConnect adapter
368
+ * @param org_id - Parent organization ID
369
+ * @returns Child organizations
370
+ */
371
+ export async function get_org_children(adapter, org_id, include_inactive) {
372
+ try {
373
+ const org_service = createCrudService(adapter, TABLE_NAME);
374
+ const children = await org_service.findBy({ parent_org_id: org_id });
375
+ if (!Array.isArray(children)) {
376
+ return {
377
+ success: true,
378
+ orgs: [],
379
+ };
380
+ }
381
+ // Filter inactive if not requested
382
+ let filtered = children;
383
+ if (!include_inactive) {
384
+ filtered = filtered.filter((org) => org.active !== false);
385
+ }
386
+ return {
387
+ success: true,
388
+ orgs: filtered,
389
+ };
390
+ }
391
+ catch (error) {
392
+ const logger = create_app_logger();
393
+ const error_message = sanitize_error_for_user(error, {
394
+ logToConsole: true,
395
+ logToLogger: true,
396
+ logger,
397
+ context: {
398
+ filename: "org_service.ts",
399
+ line_number: 0,
400
+ operation: "get_org_children",
401
+ org_id,
402
+ },
403
+ });
404
+ return {
405
+ success: false,
406
+ error: error_message,
407
+ };
408
+ }
409
+ }
410
+ /**
411
+ * Gets all ancestors of an organization up to root
412
+ * Returns array ordered from immediate parent to root
413
+ * @param adapter - HazoConnect adapter
414
+ * @param org_id - Organization ID
415
+ * @returns Ancestor organizations
416
+ */
417
+ export async function get_org_ancestors(adapter, org_id) {
418
+ try {
419
+ const ancestors = [];
420
+ // Get the org first
421
+ const org_result = await get_org_by_id(adapter, org_id);
422
+ if (!org_result.success || !org_result.org) {
423
+ return {
424
+ success: false,
425
+ error: org_result.error || "Organization not found",
426
+ };
427
+ }
428
+ let current_org = org_result.org;
429
+ // Walk up the hierarchy
430
+ while (current_org.parent_org_id) {
431
+ const parent_result = await get_org_by_id(adapter, current_org.parent_org_id);
432
+ if (!parent_result.success || !parent_result.org)
433
+ break;
434
+ ancestors.push(parent_result.org);
435
+ current_org = parent_result.org;
436
+ }
437
+ return {
438
+ success: true,
439
+ orgs: ancestors,
440
+ };
441
+ }
442
+ catch (error) {
443
+ const logger = create_app_logger();
444
+ const error_message = sanitize_error_for_user(error, {
445
+ logToConsole: true,
446
+ logToLogger: true,
447
+ logger,
448
+ context: {
449
+ filename: "org_service.ts",
450
+ line_number: 0,
451
+ operation: "get_org_ancestors",
452
+ org_id,
453
+ },
454
+ });
455
+ return {
456
+ success: false,
457
+ error: error_message,
458
+ };
459
+ }
460
+ }
461
+ /**
462
+ * Gets all descendants of an organization
463
+ * Returns flat array of all descendant orgs
464
+ * @param adapter - HazoConnect adapter
465
+ * @param org_id - Organization ID
466
+ * @returns Descendant organizations
467
+ */
468
+ export async function get_org_descendants(adapter, org_id, include_inactive) {
469
+ try {
470
+ const descendants = [];
471
+ // Recursive function to collect all children
472
+ async function collect_descendants(current_id) {
473
+ const children_result = await get_org_children(adapter, current_id, include_inactive);
474
+ if (children_result.success && children_result.orgs) {
475
+ for (const child of children_result.orgs) {
476
+ descendants.push(child);
477
+ await collect_descendants(child.id);
478
+ }
479
+ }
480
+ }
481
+ await collect_descendants(org_id);
482
+ return {
483
+ success: true,
484
+ orgs: descendants,
485
+ };
486
+ }
487
+ catch (error) {
488
+ const logger = create_app_logger();
489
+ const error_message = sanitize_error_for_user(error, {
490
+ logToConsole: true,
491
+ logToLogger: true,
492
+ logger,
493
+ context: {
494
+ filename: "org_service.ts",
495
+ line_number: 0,
496
+ operation: "get_org_descendants",
497
+ org_id,
498
+ },
499
+ });
500
+ return {
501
+ success: false,
502
+ error: error_message,
503
+ };
504
+ }
505
+ }
506
+ /**
507
+ * Gets organization hierarchy tree
508
+ * @param adapter - HazoConnect adapter
509
+ * @param root_org_id - Optional root org ID to start from (global admin: no filter)
510
+ * @param include_inactive - Include inactive orgs in tree
511
+ * @returns Nested organization tree
512
+ */
513
+ export async function get_org_tree(adapter, root_org_id, include_inactive) {
514
+ try {
515
+ const org_service = createCrudService(adapter, TABLE_NAME);
516
+ // Get root-level orgs (those without parent_org_id)
517
+ let root_orgs;
518
+ if (root_org_id) {
519
+ // Get specific root org
520
+ root_orgs = await org_service.findBy({ id: root_org_id });
521
+ }
522
+ else {
523
+ // Get all root orgs (no parent)
524
+ const all_orgs = await org_service.findBy({});
525
+ if (Array.isArray(all_orgs)) {
526
+ root_orgs = all_orgs.filter((org) => org.parent_org_id === null);
527
+ }
528
+ else {
529
+ root_orgs = [];
530
+ }
531
+ }
532
+ if (!Array.isArray(root_orgs)) {
533
+ return {
534
+ success: true,
535
+ tree: [],
536
+ };
537
+ }
538
+ // Filter inactive if not requested
539
+ let filtered_roots = root_orgs;
540
+ if (!include_inactive) {
541
+ filtered_roots = filtered_roots.filter((org) => org.active !== false);
542
+ }
543
+ // Build tree recursively
544
+ async function build_tree(org) {
545
+ // Get user count
546
+ const count_result = await get_org_user_count(adapter, org.id);
547
+ const current_user_count = count_result.success ? count_result.count : 0;
548
+ const node = Object.assign(Object.assign({}, org), { current_user_count, children: [] });
549
+ const children_result = await get_org_children(adapter, org.id, include_inactive);
550
+ if (children_result.success && children_result.orgs) {
551
+ for (const child of children_result.orgs) {
552
+ const child_node = await build_tree(child);
553
+ node.children.push(child_node);
554
+ }
555
+ }
556
+ return node;
557
+ }
558
+ const tree = [];
559
+ for (const root_org of filtered_roots) {
560
+ const node = await build_tree(root_org);
561
+ tree.push(node);
562
+ }
563
+ return {
564
+ success: true,
565
+ tree,
566
+ };
567
+ }
568
+ catch (error) {
569
+ const logger = create_app_logger();
570
+ const error_message = sanitize_error_for_user(error, {
571
+ logToConsole: true,
572
+ logToLogger: true,
573
+ logger,
574
+ context: {
575
+ filename: "org_service.ts",
576
+ line_number: 0,
577
+ operation: "get_org_tree",
578
+ root_org_id,
579
+ },
580
+ });
581
+ return {
582
+ success: false,
583
+ error: error_message,
584
+ };
585
+ }
586
+ }
587
+ /**
588
+ * Checks if a user can be added to an organization (user_limit check)
589
+ * Only applies to root-level orgs (checks root_org's user_limit)
590
+ * @param adapter - HazoConnect adapter
591
+ * @param org_id - Organization ID
592
+ * @returns Whether user can be added and reason if not
593
+ */
594
+ export async function can_add_user_to_org(adapter, org_id) {
595
+ try {
596
+ // Get the org
597
+ const org_result = await get_org_by_id(adapter, org_id);
598
+ if (!org_result.success || !org_result.org) {
599
+ return {
600
+ success: false,
601
+ can_add: false,
602
+ error: org_result.error || "Organization not found",
603
+ };
604
+ }
605
+ const org = org_result.org;
606
+ // If org is inactive, can't add users
607
+ if (org.active === false) {
608
+ return {
609
+ success: true,
610
+ can_add: false,
611
+ reason: "Organization is inactive",
612
+ };
613
+ }
614
+ // Determine which user_limit to check (root org's limit)
615
+ let root_org_id = org.root_org_id || org.id;
616
+ let root_org;
617
+ if (root_org_id === org.id) {
618
+ root_org = org;
619
+ }
620
+ else {
621
+ const root_result = await get_org_by_id(adapter, root_org_id);
622
+ if (!root_result.success || !root_result.org) {
623
+ // If can't find root, assume no limit
624
+ return {
625
+ success: true,
626
+ can_add: true,
627
+ };
628
+ }
629
+ root_org = root_result.org;
630
+ }
631
+ // If user_limit is 0, unlimited
632
+ if (root_org.user_limit === 0) {
633
+ return {
634
+ success: true,
635
+ can_add: true,
636
+ };
637
+ }
638
+ // Get total user count for root org tree
639
+ const count_result = await get_root_org_user_count(adapter, root_org.id);
640
+ if (!count_result.success) {
641
+ return {
642
+ success: true,
643
+ can_add: true, // Assume can add if can't check
644
+ };
645
+ }
646
+ const current_count = count_result.count;
647
+ if (current_count >= root_org.user_limit) {
648
+ return {
649
+ success: true,
650
+ can_add: false,
651
+ reason: `Organization user limit reached (${current_count}/${root_org.user_limit})`,
652
+ };
653
+ }
654
+ return {
655
+ success: true,
656
+ can_add: true,
657
+ };
658
+ }
659
+ catch (error) {
660
+ const logger = create_app_logger();
661
+ const error_message = sanitize_error_for_user(error, {
662
+ logToConsole: true,
663
+ logToLogger: true,
664
+ logger,
665
+ context: {
666
+ filename: "org_service.ts",
667
+ line_number: 0,
668
+ operation: "can_add_user_to_org",
669
+ org_id,
670
+ },
671
+ });
672
+ return {
673
+ success: false,
674
+ can_add: false,
675
+ error: error_message,
676
+ };
677
+ }
678
+ }
679
+ /**
680
+ * Checks if user has access to an organization (is in org's hierarchy)
681
+ * @param adapter - HazoConnect adapter
682
+ * @param user_org_id - User's org_id
683
+ * @param user_root_org_id - User's root_org_id
684
+ * @param target_org_id - Target org to check access to
685
+ * @returns Whether user has access
686
+ */
687
+ export async function check_user_org_access(adapter, user_org_id, user_root_org_id, target_org_id) {
688
+ try {
689
+ // If user has no org, no access
690
+ if (!user_org_id && !user_root_org_id) {
691
+ return {
692
+ success: true,
693
+ has_access: false,
694
+ };
695
+ }
696
+ // Get target org
697
+ const target_result = await get_org_by_id(adapter, target_org_id);
698
+ if (!target_result.success || !target_result.org) {
699
+ return {
700
+ success: true,
701
+ has_access: false,
702
+ };
703
+ }
704
+ const target_org = target_result.org;
705
+ // Check if target is in user's org tree
706
+ const target_root = target_org.root_org_id || target_org.id;
707
+ // User has access if they share the same root org
708
+ if (user_root_org_id === target_root || user_org_id === target_root) {
709
+ return {
710
+ success: true,
711
+ has_access: true,
712
+ };
713
+ }
714
+ // Or if user's org_id matches the target
715
+ if (user_org_id === target_org_id) {
716
+ return {
717
+ success: true,
718
+ has_access: true,
719
+ };
720
+ }
721
+ return {
722
+ success: true,
723
+ has_access: false,
724
+ };
725
+ }
726
+ catch (error) {
727
+ const logger = create_app_logger();
728
+ const error_message = sanitize_error_for_user(error, {
729
+ logToConsole: true,
730
+ logToLogger: true,
731
+ logger,
732
+ context: {
733
+ filename: "org_service.ts",
734
+ line_number: 0,
735
+ operation: "check_user_org_access",
736
+ user_org_id,
737
+ target_org_id,
738
+ },
739
+ });
740
+ return {
741
+ success: false,
742
+ has_access: false,
743
+ error: error_message,
744
+ };
745
+ }
746
+ }