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.
- package/cli-src/lib/already_logged_in_config.server.ts +1 -1
- package/cli-src/lib/app_logger.ts +8 -18
- package/cli-src/lib/auth/auth_types.ts +7 -0
- package/cli-src/lib/auth/auth_utils.server.ts +2 -2
- package/cli-src/lib/auth/dev_lock_validator.edge.ts +171 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +84 -13
- package/cli-src/lib/auth/index.ts +5 -5
- package/cli-src/lib/auth/nextauth_config.ts +4 -4
- package/cli-src/lib/auth/org_cache.ts +148 -0
- package/cli-src/lib/auth/server_auth.ts +2 -2
- package/cli-src/lib/auth/session_token_validator.edge.ts +4 -0
- package/cli-src/lib/auth_utility_config.server.ts +1 -1
- package/cli-src/lib/config/config_loader.server.ts +1 -1
- package/cli-src/lib/config/default_config.ts +44 -0
- package/cli-src/lib/dev_lock_config.server.ts +148 -0
- package/cli-src/lib/email_verification_config.server.ts +3 -3
- package/cli-src/lib/file_types_config.server.ts +1 -1
- package/cli-src/lib/forgot_password_config.server.ts +3 -3
- package/cli-src/lib/hazo_connect_instance.server.ts +2 -2
- package/cli-src/lib/hazo_connect_setup.server.ts +2 -2
- package/cli-src/lib/index.ts +24 -24
- package/cli-src/lib/login_config.server.ts +4 -4
- package/cli-src/lib/messages_config.server.ts +1 -1
- package/cli-src/lib/multi_tenancy_config.server.ts +94 -0
- package/cli-src/lib/my_settings_config.server.ts +7 -7
- package/cli-src/lib/oauth_config.server.ts +2 -2
- package/cli-src/lib/password_requirements_config.server.ts +2 -2
- package/cli-src/lib/profile_pic_menu_config.server.ts +1 -1
- package/cli-src/lib/profile_picture_config.server.ts +2 -2
- package/cli-src/lib/register_config.server.ts +5 -5
- package/cli-src/lib/reset_password_config.server.ts +4 -4
- package/cli-src/lib/scope_hierarchy_config.server.ts +2 -2
- package/cli-src/lib/services/email_service.ts +2 -2
- package/cli-src/lib/services/email_verification_service.ts +3 -3
- package/cli-src/lib/services/login_service.ts +3 -3
- package/cli-src/lib/services/oauth_service.ts +4 -4
- package/cli-src/lib/services/org_service.ts +965 -0
- package/cli-src/lib/services/password_change_service.ts +3 -3
- package/cli-src/lib/services/password_reset_service.ts +3 -3
- package/cli-src/lib/services/profile_picture_remove_service.ts +3 -3
- package/cli-src/lib/services/profile_picture_service.ts +5 -5
- package/cli-src/lib/services/registration_service.ts +8 -8
- package/cli-src/lib/services/scope_labels_service.ts +3 -3
- package/cli-src/lib/services/scope_service.ts +2 -2
- package/cli-src/lib/services/session_token_service.ts +6 -2
- package/cli-src/lib/services/token_service.ts +2 -2
- package/cli-src/lib/services/user_profiles_service.ts +4 -4
- package/cli-src/lib/services/user_scope_service.ts +3 -3
- package/cli-src/lib/services/user_update_service.ts +4 -4
- package/cli-src/lib/ui_shell_config.server.ts +1 -1
- package/cli-src/lib/ui_sizes_config.server.ts +1 -1
- package/cli-src/lib/user_fields_config.server.ts +1 -1
- package/cli-src/lib/user_management_config.server.ts +1 -1
- package/cli-src/lib/user_profiles_config.server.ts +1 -1
- package/cli-src/lib/utils/error_sanitizer.ts +1 -1
- package/cli-src/server/types/app_types.ts +72 -0
- package/cli-src/server/types/express.d.ts +16 -0
- package/dist/components/layouts/dev_lock/index.d.ts +29 -0
- package/dist/components/layouts/dev_lock/index.d.ts.map +1 -0
- package/dist/components/layouts/dev_lock/index.js +60 -0
- package/dist/components/layouts/index.d.ts +2 -0
- package/dist/components/layouts/index.d.ts.map +1 -1
- package/dist/components/layouts/index.js +1 -0
- package/dist/components/layouts/login/hooks/use_login_form.js +2 -2
- package/dist/components/layouts/org_management/index.d.ts +26 -0
- package/dist/components/layouts/org_management/index.d.ts.map +1 -0
- package/dist/components/layouts/org_management/index.js +75 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
- package/dist/components/layouts/user_management/index.d.ts +3 -1
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +10 -4
- package/dist/components/ui/button.d.ts +1 -1
- 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 +6 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
- package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
- package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +61 -1
- package/dist/lib/auth/org_cache.d.ts +65 -0
- package/dist/lib/auth/org_cache.d.ts.map +1 -0
- package/dist/lib/auth/org_cache.js +103 -0
- package/dist/lib/config/default_config.d.ts +76 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +42 -0
- package/dist/lib/dev_lock_config.server.d.ts +41 -0
- package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
- package/dist/lib/dev_lock_config.server.js +50 -0
- package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
- package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
- package/dist/lib/multi_tenancy_config.server.js +41 -0
- package/dist/lib/services/org_service.d.ts +191 -0
- package/dist/lib/services/org_service.d.ts.map +1 -0
- package/dist/lib/services/org_service.js +746 -0
- package/dist/page_components/dev_lock.d.ts +11 -0
- package/dist/page_components/dev_lock.d.ts.map +1 -0
- package/dist/page_components/dev_lock.js +17 -0
- package/dist/page_components/index.d.ts +1 -0
- package/dist/page_components/index.d.ts.map +1 -1
- package/dist/page_components/index.js +1 -0
- package/dist/page_components/login.d.ts.map +1 -1
- package/dist/page_components/login.js +3 -7
- package/dist/page_components/org_management.d.ts +27 -0
- package/dist/page_components/org_management.d.ts.map +1 -0
- package/dist/page_components/org_management.js +18 -0
- 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/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 +30 -0
- package/package.json +29 -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
|
+
}
|