hazo_auth 3.0.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -0
- package/SETUP_CHECKLIST.md +369 -0
- package/dist/app/api/hazo_auth/library_photo/[category]/[filename]/route.d.ts +2 -2
- package/dist/app/api/hazo_auth/library_photo/[category]/[filename]/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/library_photo/[category]/[filename]/route.js +1 -1
- package/dist/app/api/hazo_auth/profile_picture/[filename]/route.d.ts +2 -2
- package/dist/app/api/hazo_auth/profile_picture/[filename]/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/profile_picture/[filename]/route.js +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +2 -2
- package/dist/components/layouts/rbac_test/index.d.ts +15 -0
- package/dist/components/layouts/rbac_test/index.d.ts.map +1 -0
- package/dist/components/layouts/rbac_test/index.js +378 -0
- package/dist/components/layouts/shared/components/password_field.js +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
- package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
- package/dist/components/layouts/user_management/components/roles_matrix.d.ts +2 -3
- package/dist/components/layouts/user_management/components/roles_matrix.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/roles_matrix.js +133 -8
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +12 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +291 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.js +158 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts +11 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.js +267 -0
- package/dist/components/layouts/user_management/index.d.ts +9 -2
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +22 -6
- package/dist/components/ui/select.d.ts +14 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +59 -0
- package/dist/components/ui/tree-view.d.ts +108 -0
- package/dist/components/ui/tree-view.d.ts.map +1 -0
- package/dist/components/ui/tree-view.js +194 -0
- package/dist/lib/auth/auth_types.d.ts +45 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +13 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts +4 -2
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +107 -3
- package/dist/lib/auth/scope_cache.d.ts +92 -0
- package/dist/lib/auth/scope_cache.d.ts.map +1 -0
- package/dist/lib/auth/scope_cache.js +171 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts +39 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -0
- package/dist/lib/scope_hierarchy_config.server.js +96 -0
- package/dist/lib/services/email_service.d.ts.map +1 -1
- package/dist/lib/services/email_service.js +7 -2
- package/dist/lib/services/profile_picture_service.d.ts +1 -7
- package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
- package/dist/lib/services/profile_picture_service.js +77 -32
- package/dist/lib/services/registration_service.js +1 -1
- package/dist/lib/services/scope_labels_service.d.ts +48 -0
- package/dist/lib/services/scope_labels_service.d.ts.map +1 -0
- package/dist/lib/services/scope_labels_service.js +277 -0
- package/dist/lib/services/scope_service.d.ts +114 -0
- package/dist/lib/services/scope_service.d.ts.map +1 -0
- package/dist/lib/services/scope_service.js +582 -0
- package/dist/lib/services/user_scope_service.d.ts +74 -0
- package/dist/lib/services/user_scope_service.d.ts.map +1 -0
- package/dist/lib/services/user_scope_service.js +415 -0
- package/hazo_auth_config.example.ini +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,582 @@
|
|
|
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
|
+
export const SCOPE_LEVELS = [
|
|
6
|
+
"hazo_scopes_l1",
|
|
7
|
+
"hazo_scopes_l2",
|
|
8
|
+
"hazo_scopes_l3",
|
|
9
|
+
"hazo_scopes_l4",
|
|
10
|
+
"hazo_scopes_l5",
|
|
11
|
+
"hazo_scopes_l6",
|
|
12
|
+
"hazo_scopes_l7",
|
|
13
|
+
];
|
|
14
|
+
export const SCOPE_LEVEL_NUMBERS = {
|
|
15
|
+
hazo_scopes_l1: 1,
|
|
16
|
+
hazo_scopes_l2: 2,
|
|
17
|
+
hazo_scopes_l3: 3,
|
|
18
|
+
hazo_scopes_l4: 4,
|
|
19
|
+
hazo_scopes_l5: 5,
|
|
20
|
+
hazo_scopes_l6: 6,
|
|
21
|
+
hazo_scopes_l7: 7,
|
|
22
|
+
};
|
|
23
|
+
// section: helpers
|
|
24
|
+
/**
|
|
25
|
+
* Validates that the provided string is a valid scope level
|
|
26
|
+
*/
|
|
27
|
+
export function is_valid_scope_level(level) {
|
|
28
|
+
return SCOPE_LEVELS.includes(level);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Gets the parent level for a given scope level
|
|
32
|
+
* Returns undefined for L1 (root level)
|
|
33
|
+
*/
|
|
34
|
+
export function get_parent_level(level) {
|
|
35
|
+
const level_num = SCOPE_LEVEL_NUMBERS[level];
|
|
36
|
+
if (level_num === 1)
|
|
37
|
+
return undefined;
|
|
38
|
+
return `hazo_scopes_l${level_num - 1}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Gets the child level for a given scope level
|
|
42
|
+
* Returns undefined for L7 (leaf level)
|
|
43
|
+
*/
|
|
44
|
+
export function get_child_level(level) {
|
|
45
|
+
const level_num = SCOPE_LEVEL_NUMBERS[level];
|
|
46
|
+
if (level_num === 7)
|
|
47
|
+
return undefined;
|
|
48
|
+
return `hazo_scopes_l${level_num + 1}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Gets all scopes for a given level, optionally filtered by organization
|
|
52
|
+
*/
|
|
53
|
+
export async function get_scopes_by_level(adapter, level, org) {
|
|
54
|
+
try {
|
|
55
|
+
const scope_service = createCrudService(adapter, level);
|
|
56
|
+
let scopes;
|
|
57
|
+
if (org) {
|
|
58
|
+
scopes = await scope_service.findBy({ org });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
scopes = await scope_service.findBy({});
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(scopes)) {
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
scopes: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
scopes: scopes,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const logger = create_app_logger();
|
|
76
|
+
const error_message = sanitize_error_for_user(error, {
|
|
77
|
+
logToConsole: true,
|
|
78
|
+
logToLogger: true,
|
|
79
|
+
logger,
|
|
80
|
+
context: {
|
|
81
|
+
filename: "scope_service.ts",
|
|
82
|
+
line_number: 0,
|
|
83
|
+
operation: "get_scopes_by_level",
|
|
84
|
+
level,
|
|
85
|
+
org,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: error_message,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Gets a single scope by ID
|
|
96
|
+
*/
|
|
97
|
+
export async function get_scope_by_id(adapter, level, scope_id) {
|
|
98
|
+
try {
|
|
99
|
+
const scope_service = createCrudService(adapter, level);
|
|
100
|
+
const scopes = await scope_service.findBy({ id: scope_id });
|
|
101
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: "Scope not found",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
scope: scopes[0],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const logger = create_app_logger();
|
|
114
|
+
const error_message = sanitize_error_for_user(error, {
|
|
115
|
+
logToConsole: true,
|
|
116
|
+
logToLogger: true,
|
|
117
|
+
logger,
|
|
118
|
+
context: {
|
|
119
|
+
filename: "scope_service.ts",
|
|
120
|
+
line_number: 0,
|
|
121
|
+
operation: "get_scope_by_id",
|
|
122
|
+
level,
|
|
123
|
+
scope_id,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: error_message,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Gets a single scope by seq (friendly ID)
|
|
134
|
+
*/
|
|
135
|
+
export async function get_scope_by_seq(adapter, level, seq) {
|
|
136
|
+
try {
|
|
137
|
+
const scope_service = createCrudService(adapter, level);
|
|
138
|
+
const scopes = await scope_service.findBy({ seq });
|
|
139
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: "Scope not found",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
scope: scopes[0],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const logger = create_app_logger();
|
|
152
|
+
const error_message = sanitize_error_for_user(error, {
|
|
153
|
+
logToConsole: true,
|
|
154
|
+
logToLogger: true,
|
|
155
|
+
logger,
|
|
156
|
+
context: {
|
|
157
|
+
filename: "scope_service.ts",
|
|
158
|
+
line_number: 0,
|
|
159
|
+
operation: "get_scope_by_seq",
|
|
160
|
+
level,
|
|
161
|
+
seq,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: error_message,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Creates a new scope
|
|
172
|
+
* Note: The seq field is auto-generated by the database via hazo_scope_id_generator function
|
|
173
|
+
*/
|
|
174
|
+
export async function create_scope(adapter, level, data) {
|
|
175
|
+
try {
|
|
176
|
+
const scope_service = createCrudService(adapter, level);
|
|
177
|
+
const now = new Date().toISOString();
|
|
178
|
+
// Validate parent_scope_id is required for L2-L7
|
|
179
|
+
const parent_level = get_parent_level(level);
|
|
180
|
+
if (parent_level && !data.parent_scope_id) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: `parent_scope_id is required for ${level}`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Validate parent exists if provided
|
|
187
|
+
if (data.parent_scope_id && parent_level) {
|
|
188
|
+
const parent_result = await get_scope_by_id(adapter, parent_level, data.parent_scope_id);
|
|
189
|
+
if (!parent_result.success) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: "Parent scope not found",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const insert_data = {
|
|
197
|
+
org: data.org,
|
|
198
|
+
name: data.name,
|
|
199
|
+
created_at: now,
|
|
200
|
+
changed_at: now,
|
|
201
|
+
};
|
|
202
|
+
if (data.parent_scope_id) {
|
|
203
|
+
insert_data.parent_scope_id = data.parent_scope_id;
|
|
204
|
+
}
|
|
205
|
+
const inserted = await scope_service.insert(insert_data);
|
|
206
|
+
if (!Array.isArray(inserted) || inserted.length === 0) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
error: "Failed to create scope",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
scope: inserted[0],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
const logger = create_app_logger();
|
|
219
|
+
const error_message = sanitize_error_for_user(error, {
|
|
220
|
+
logToConsole: true,
|
|
221
|
+
logToLogger: true,
|
|
222
|
+
logger,
|
|
223
|
+
context: {
|
|
224
|
+
filename: "scope_service.ts",
|
|
225
|
+
line_number: 0,
|
|
226
|
+
operation: "create_scope",
|
|
227
|
+
level,
|
|
228
|
+
data,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
success: false,
|
|
233
|
+
error: error_message,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Updates an existing scope
|
|
239
|
+
*/
|
|
240
|
+
export async function update_scope(adapter, level, scope_id, data) {
|
|
241
|
+
try {
|
|
242
|
+
const scope_service = createCrudService(adapter, level);
|
|
243
|
+
const now = new Date().toISOString();
|
|
244
|
+
// Check scope exists
|
|
245
|
+
const existing = await get_scope_by_id(adapter, level, scope_id);
|
|
246
|
+
if (!existing.success) {
|
|
247
|
+
return existing;
|
|
248
|
+
}
|
|
249
|
+
// Validate parent if being changed
|
|
250
|
+
if (data.parent_scope_id !== undefined) {
|
|
251
|
+
const parent_level = get_parent_level(level);
|
|
252
|
+
if (parent_level && data.parent_scope_id) {
|
|
253
|
+
const parent_result = await get_scope_by_id(adapter, parent_level, data.parent_scope_id);
|
|
254
|
+
if (!parent_result.success) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: "Parent scope not found",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const update_data = {
|
|
263
|
+
changed_at: now,
|
|
264
|
+
};
|
|
265
|
+
if (data.name !== undefined) {
|
|
266
|
+
update_data.name = data.name;
|
|
267
|
+
}
|
|
268
|
+
if (data.parent_scope_id !== undefined) {
|
|
269
|
+
update_data.parent_scope_id = data.parent_scope_id;
|
|
270
|
+
}
|
|
271
|
+
const updated = await scope_service.updateById(scope_id, update_data);
|
|
272
|
+
if (!Array.isArray(updated) || updated.length === 0) {
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
error: "Failed to update scope",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
scope: updated[0],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
const logger = create_app_logger();
|
|
285
|
+
const error_message = sanitize_error_for_user(error, {
|
|
286
|
+
logToConsole: true,
|
|
287
|
+
logToLogger: true,
|
|
288
|
+
logger,
|
|
289
|
+
context: {
|
|
290
|
+
filename: "scope_service.ts",
|
|
291
|
+
line_number: 0,
|
|
292
|
+
operation: "update_scope",
|
|
293
|
+
level,
|
|
294
|
+
scope_id,
|
|
295
|
+
data,
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
error: error_message,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Deletes a scope (cascades to children via database FK constraints)
|
|
306
|
+
*/
|
|
307
|
+
export async function delete_scope(adapter, level, scope_id) {
|
|
308
|
+
try {
|
|
309
|
+
const scope_service = createCrudService(adapter, level);
|
|
310
|
+
// Check scope exists
|
|
311
|
+
const existing = await get_scope_by_id(adapter, level, scope_id);
|
|
312
|
+
if (!existing.success) {
|
|
313
|
+
return existing;
|
|
314
|
+
}
|
|
315
|
+
await scope_service.deleteById(scope_id);
|
|
316
|
+
return {
|
|
317
|
+
success: true,
|
|
318
|
+
scope: existing.scope,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
const logger = create_app_logger();
|
|
323
|
+
const error_message = sanitize_error_for_user(error, {
|
|
324
|
+
logToConsole: true,
|
|
325
|
+
logToLogger: true,
|
|
326
|
+
logger,
|
|
327
|
+
context: {
|
|
328
|
+
filename: "scope_service.ts",
|
|
329
|
+
line_number: 0,
|
|
330
|
+
operation: "delete_scope",
|
|
331
|
+
level,
|
|
332
|
+
scope_id,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
error: error_message,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Gets immediate children of a scope
|
|
343
|
+
*/
|
|
344
|
+
export async function get_scope_children(adapter, level, scope_id) {
|
|
345
|
+
try {
|
|
346
|
+
const child_level = get_child_level(level);
|
|
347
|
+
if (!child_level) {
|
|
348
|
+
return {
|
|
349
|
+
success: true,
|
|
350
|
+
scopes: [], // L7 has no children
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const child_service = createCrudService(adapter, child_level);
|
|
354
|
+
const children = await child_service.findBy({ parent_scope_id: scope_id });
|
|
355
|
+
return {
|
|
356
|
+
success: true,
|
|
357
|
+
scopes: Array.isArray(children) ? children : [],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
const logger = create_app_logger();
|
|
362
|
+
const error_message = sanitize_error_for_user(error, {
|
|
363
|
+
logToConsole: true,
|
|
364
|
+
logToLogger: true,
|
|
365
|
+
logger,
|
|
366
|
+
context: {
|
|
367
|
+
filename: "scope_service.ts",
|
|
368
|
+
line_number: 0,
|
|
369
|
+
operation: "get_scope_children",
|
|
370
|
+
level,
|
|
371
|
+
scope_id,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
success: false,
|
|
376
|
+
error: error_message,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Gets all ancestors of a scope up to L1 (root)
|
|
382
|
+
* Returns array ordered from immediate parent to root (L1)
|
|
383
|
+
*/
|
|
384
|
+
export async function get_scope_ancestors(adapter, level, scope_id) {
|
|
385
|
+
try {
|
|
386
|
+
const ancestors = [];
|
|
387
|
+
// Get the scope first
|
|
388
|
+
const scope_result = await get_scope_by_id(adapter, level, scope_id);
|
|
389
|
+
if (!scope_result.success || !scope_result.scope) {
|
|
390
|
+
return scope_result;
|
|
391
|
+
}
|
|
392
|
+
let current_scope = scope_result.scope;
|
|
393
|
+
let current_level = level;
|
|
394
|
+
// Walk up the hierarchy
|
|
395
|
+
while (current_scope.parent_scope_id) {
|
|
396
|
+
const parent_level = get_parent_level(current_level);
|
|
397
|
+
if (!parent_level)
|
|
398
|
+
break;
|
|
399
|
+
const parent_result = await get_scope_by_id(adapter, parent_level, current_scope.parent_scope_id);
|
|
400
|
+
if (!parent_result.success || !parent_result.scope)
|
|
401
|
+
break;
|
|
402
|
+
ancestors.push(parent_result.scope);
|
|
403
|
+
current_scope = parent_result.scope;
|
|
404
|
+
current_level = parent_level;
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
scopes: ancestors,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
const logger = create_app_logger();
|
|
413
|
+
const error_message = sanitize_error_for_user(error, {
|
|
414
|
+
logToConsole: true,
|
|
415
|
+
logToLogger: true,
|
|
416
|
+
logger,
|
|
417
|
+
context: {
|
|
418
|
+
filename: "scope_service.ts",
|
|
419
|
+
line_number: 0,
|
|
420
|
+
operation: "get_scope_ancestors",
|
|
421
|
+
level,
|
|
422
|
+
scope_id,
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
return {
|
|
426
|
+
success: false,
|
|
427
|
+
error: error_message,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Gets all descendants of a scope down to L7 (leaves)
|
|
433
|
+
* Returns flat array of all descendant scopes
|
|
434
|
+
*/
|
|
435
|
+
export async function get_scope_descendants(adapter, level, scope_id) {
|
|
436
|
+
try {
|
|
437
|
+
const descendants = [];
|
|
438
|
+
// Recursive function to get all children
|
|
439
|
+
async function collect_descendants(current_level, current_id) {
|
|
440
|
+
const children_result = await get_scope_children(adapter, current_level, current_id);
|
|
441
|
+
if (children_result.success && children_result.scopes) {
|
|
442
|
+
for (const child of children_result.scopes) {
|
|
443
|
+
descendants.push(child);
|
|
444
|
+
const child_level = get_child_level(current_level);
|
|
445
|
+
if (child_level) {
|
|
446
|
+
await collect_descendants(child_level, child.id);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
await collect_descendants(level, scope_id);
|
|
452
|
+
return {
|
|
453
|
+
success: true,
|
|
454
|
+
scopes: descendants,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
const logger = create_app_logger();
|
|
459
|
+
const error_message = sanitize_error_for_user(error, {
|
|
460
|
+
logToConsole: true,
|
|
461
|
+
logToLogger: true,
|
|
462
|
+
logger,
|
|
463
|
+
context: {
|
|
464
|
+
filename: "scope_service.ts",
|
|
465
|
+
line_number: 0,
|
|
466
|
+
operation: "get_scope_descendants",
|
|
467
|
+
level,
|
|
468
|
+
scope_id,
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
error: error_message,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
export async function get_scope_tree(adapter, org) {
|
|
478
|
+
try {
|
|
479
|
+
// Get all L1 scopes for this org
|
|
480
|
+
const l1_result = await get_scopes_by_level(adapter, "hazo_scopes_l1", org);
|
|
481
|
+
if (!l1_result.success || !l1_result.scopes) {
|
|
482
|
+
return l1_result;
|
|
483
|
+
}
|
|
484
|
+
// Build tree recursively
|
|
485
|
+
async function build_tree(scope, level) {
|
|
486
|
+
const node = Object.assign(Object.assign({}, scope), { level, children: [] });
|
|
487
|
+
const children_result = await get_scope_children(adapter, level, scope.id);
|
|
488
|
+
if (children_result.success && children_result.scopes) {
|
|
489
|
+
const child_level = get_child_level(level);
|
|
490
|
+
if (child_level) {
|
|
491
|
+
for (const child of children_result.scopes) {
|
|
492
|
+
const child_node = await build_tree(child, child_level);
|
|
493
|
+
node.children.push(child_node);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return node;
|
|
498
|
+
}
|
|
499
|
+
const tree = [];
|
|
500
|
+
for (const l1_scope of l1_result.scopes) {
|
|
501
|
+
const node = await build_tree(l1_scope, "hazo_scopes_l1");
|
|
502
|
+
tree.push(node);
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
success: true,
|
|
506
|
+
tree,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
const logger = create_app_logger();
|
|
511
|
+
const error_message = sanitize_error_for_user(error, {
|
|
512
|
+
logToConsole: true,
|
|
513
|
+
logToLogger: true,
|
|
514
|
+
logger,
|
|
515
|
+
context: {
|
|
516
|
+
filename: "scope_service.ts",
|
|
517
|
+
line_number: 0,
|
|
518
|
+
operation: "get_scope_tree",
|
|
519
|
+
org,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
return {
|
|
523
|
+
success: false,
|
|
524
|
+
error: error_message,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Gets all scope trees across all organizations
|
|
530
|
+
* Returns trees for all L1 scopes (top level)
|
|
531
|
+
*/
|
|
532
|
+
export async function get_all_scope_trees(adapter) {
|
|
533
|
+
try {
|
|
534
|
+
// Get all L1 scopes (no org filter)
|
|
535
|
+
const l1_result = await get_scopes_by_level(adapter, "hazo_scopes_l1");
|
|
536
|
+
if (!l1_result.success || !l1_result.scopes) {
|
|
537
|
+
return { success: true, trees: [] };
|
|
538
|
+
}
|
|
539
|
+
// Build tree recursively
|
|
540
|
+
async function build_tree(scope, level) {
|
|
541
|
+
const node = Object.assign(Object.assign({}, scope), { level, children: [] });
|
|
542
|
+
const children_result = await get_scope_children(adapter, level, scope.id);
|
|
543
|
+
if (children_result.success && children_result.scopes) {
|
|
544
|
+
const child_level = get_child_level(level);
|
|
545
|
+
if (child_level) {
|
|
546
|
+
for (const child of children_result.scopes) {
|
|
547
|
+
const child_node = await build_tree(child, child_level);
|
|
548
|
+
node.children.push(child_node);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return node;
|
|
553
|
+
}
|
|
554
|
+
// Build trees for all L1 scopes
|
|
555
|
+
const trees = [];
|
|
556
|
+
for (const scope of l1_result.scopes) {
|
|
557
|
+
const scopeTree = await build_tree(scope, "hazo_scopes_l1");
|
|
558
|
+
trees.push(scopeTree);
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
success: true,
|
|
562
|
+
trees,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
const logger = create_app_logger();
|
|
567
|
+
const error_message = sanitize_error_for_user(error, {
|
|
568
|
+
logToConsole: true,
|
|
569
|
+
logToLogger: true,
|
|
570
|
+
logger,
|
|
571
|
+
context: {
|
|
572
|
+
filename: "scope_service.ts",
|
|
573
|
+
line_number: 0,
|
|
574
|
+
operation: "get_all_scope_trees",
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
success: false,
|
|
579
|
+
error: error_message,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { HazoConnectAdapter } from "hazo_connect";
|
|
2
|
+
import { type ScopeLevel } from "./scope_service";
|
|
3
|
+
export type UserScope = {
|
|
4
|
+
user_id: string;
|
|
5
|
+
scope_id: string;
|
|
6
|
+
scope_seq: string;
|
|
7
|
+
scope_type: ScopeLevel;
|
|
8
|
+
created_at: string;
|
|
9
|
+
changed_at: string;
|
|
10
|
+
};
|
|
11
|
+
export type UserScopeResult = {
|
|
12
|
+
success: boolean;
|
|
13
|
+
scope?: UserScope;
|
|
14
|
+
scopes?: UserScope[];
|
|
15
|
+
error?: string;
|
|
16
|
+
};
|
|
17
|
+
export type ScopeAccessCheckResult = {
|
|
18
|
+
has_access: boolean;
|
|
19
|
+
access_via?: {
|
|
20
|
+
scope_type: ScopeLevel;
|
|
21
|
+
scope_id: string;
|
|
22
|
+
scope_seq: string;
|
|
23
|
+
};
|
|
24
|
+
user_scopes?: UserScope[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Gets all scope assignments for a user
|
|
28
|
+
*/
|
|
29
|
+
export declare function get_user_scopes(adapter: HazoConnectAdapter, user_id: string): Promise<UserScopeResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Gets all users assigned to a specific scope
|
|
32
|
+
*/
|
|
33
|
+
export declare function get_users_by_scope(adapter: HazoConnectAdapter, scope_type: ScopeLevel, scope_id: string): Promise<UserScopeResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Assigns a scope to a user
|
|
36
|
+
*/
|
|
37
|
+
export declare function assign_user_scope(adapter: HazoConnectAdapter, user_id: string, scope_type: ScopeLevel, scope_id: string, scope_seq: string): Promise<UserScopeResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Removes a scope assignment from a user
|
|
40
|
+
*/
|
|
41
|
+
export declare function remove_user_scope(adapter: HazoConnectAdapter, user_id: string, scope_type: ScopeLevel, scope_id: string): Promise<UserScopeResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Bulk update user scope assignments
|
|
44
|
+
* Replaces all existing assignments with the new set
|
|
45
|
+
*/
|
|
46
|
+
export declare function update_user_scopes(adapter: HazoConnectAdapter, user_id: string, new_scopes: Array<{
|
|
47
|
+
scope_type: ScopeLevel;
|
|
48
|
+
scope_id: string;
|
|
49
|
+
scope_seq: string;
|
|
50
|
+
}>): Promise<UserScopeResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Checks if a user has access to a specific scope
|
|
53
|
+
* Access is granted if:
|
|
54
|
+
* 1. User has the exact scope assigned, OR
|
|
55
|
+
* 2. User has access to an ancestor scope (L2 user can access L3, L4, etc.)
|
|
56
|
+
*
|
|
57
|
+
* @param adapter - HazoConnect adapter
|
|
58
|
+
* @param user_id - User ID to check
|
|
59
|
+
* @param target_scope_type - The scope level being accessed
|
|
60
|
+
* @param target_scope_id - The scope ID being accessed (optional if target_scope_seq provided)
|
|
61
|
+
* @param target_scope_seq - The scope seq being accessed (optional if target_scope_id provided)
|
|
62
|
+
*/
|
|
63
|
+
export declare function check_user_scope_access(adapter: HazoConnectAdapter, user_id: string, target_scope_type: ScopeLevel, target_scope_id?: string, target_scope_seq?: string): Promise<ScopeAccessCheckResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Gets the effective scopes a user has access to
|
|
66
|
+
* This includes directly assigned scopes and all their descendants
|
|
67
|
+
*/
|
|
68
|
+
export declare function get_user_effective_scopes(adapter: HazoConnectAdapter, user_id: string): Promise<{
|
|
69
|
+
success: boolean;
|
|
70
|
+
direct_scopes?: UserScope[];
|
|
71
|
+
inherited_scope_types?: ScopeLevel[];
|
|
72
|
+
error?: string;
|
|
73
|
+
}>;
|
|
74
|
+
//# sourceMappingURL=user_scope_service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user_scope_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/user_scope_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIvD,OAAO,EACL,KAAK,UAAU,EAOhB,MAAM,iBAAiB,CAAC;AAGzB,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE;QACX,UAAU,EAAE,UAAU,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC;CAC3B,CAAC;AAIF;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC,CA6B1B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAsE1B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC,CA0E1B;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,KAAK,CAAC;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GACjF,OAAO,CAAC,eAAe,CAAC,CAgE1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,UAAU,EAC7B,eAAe,CAAC,EAAE,MAAM,EACxB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,sBAAsB,CAAC,CAmHjC;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC;IAC5B,qBAAqB,CAAC,EAAE,UAAU,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAgDD"}
|