@mindline/sync 1.0.110 → 1.0.112

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindline/sync",
3
- "version": "1.0.110",
3
+ "version": "1.0.112",
4
4
  "description": "sync is a node.js package encapsulating JavaScript classes required for configuring Mindline sync service.",
5
5
  "main": "dist/sync.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,382 @@
1
+ /**
2
+ * API Client for backend proxy endpoints
3
+ * This file contains functions that call backend controllers instead of directly calling
4
+ * Microsoft Graph API and Azure Management API from the frontend.
5
+ * This prevents token exposure in the browser.
6
+ */
7
+
8
+ // ======================= Backend Response Types =======================
9
+
10
+ interface ApiResponse<T> {
11
+ data?: T;
12
+ error: string;
13
+ success: boolean;
14
+ }
15
+
16
+ // Backend DTOs (match the C# DTOs)
17
+ interface GroupDto {
18
+ id: string;
19
+ displayName: string;
20
+ description?: string;
21
+ mail?: string;
22
+ }
23
+
24
+ interface GroupsResponseDto {
25
+ groups: GroupDto[];
26
+ }
27
+
28
+ interface OAuth2PermissionGrantDto {
29
+ grants: string | null;
30
+ id: string | null;
31
+ }
32
+
33
+ interface ServicePrincipalDto {
34
+ servicePrincipalId: string;
35
+ displayName: string;
36
+ }
37
+
38
+ interface UserDto {
39
+ id: string;
40
+ displayName: string;
41
+ mail?: string;
42
+ userPrincipalName?: string;
43
+ }
44
+
45
+ interface UsersResponseDto {
46
+ users: UserDto[];
47
+ }
48
+
49
+ interface CanListRootAssignmentsResponseDto {
50
+ canList: boolean;
51
+ }
52
+
53
+ interface ElevateAccessResponseDto {
54
+ success: boolean;
55
+ }
56
+
57
+ interface ResourceDto {
58
+ id: string;
59
+ name: string;
60
+ type: string;
61
+ location: string;
62
+ tags?: { [key: string]: string };
63
+ }
64
+
65
+ interface ResourcesResponseDto {
66
+ resources: ResourceDto[];
67
+ }
68
+
69
+ // ======================= Frontend Types (for return values) =======================
70
+ // These match the types expected by index.ts callers
71
+
72
+ export interface Group {
73
+ id: string;
74
+ displayName: string;
75
+ description: string;
76
+ }
77
+
78
+ export interface Resource {
79
+ id: string;
80
+ name: string;
81
+ type: string;
82
+ location: string;
83
+ tags?: { [key: string]: string };
84
+ }
85
+
86
+ // ======================= Microsoft Graph API Proxy Functions =======================
87
+
88
+ /**
89
+ * Search for groups by display name
90
+ * Calls backend: GET /api/graphapi/groups
91
+ */
92
+ export async function groupsGet(groupSearchString: string): Promise<{ groups: Group[], error: string }> {
93
+ try {
94
+ if (!groupSearchString) {
95
+ return { groups: [], error: '400: Search string is required' };
96
+ }
97
+
98
+ const url = `/api/graphapi/groups?search=${encodeURIComponent(groupSearchString)}`;
99
+ const response = await fetch(url, {
100
+ method: 'GET',
101
+ headers: {
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ });
105
+
106
+ if (!response.ok) {
107
+ return { groups: [], error: `${response.status}: Failed to retrieve groups` };
108
+ }
109
+
110
+ const apiResponse: ApiResponse<GroupsResponseDto> = await response.json();
111
+
112
+ if (!apiResponse.success || !apiResponse.data) {
113
+ return { groups: [], error: apiResponse.error || 'Unknown error' };
114
+ }
115
+
116
+ // Convert backend types to frontend types
117
+ const groups: Group[] = apiResponse.data.groups.map(g => ({
118
+ id: g.id,
119
+ displayName: g.displayName,
120
+ description: g.description || ''
121
+ }));
122
+
123
+ return { groups, error: '' };
124
+ } catch (error: any) {
125
+ console.error('Error in groupsGet:', error);
126
+ return { groups: [], error: `Exception: ${error.message}` };
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Get OAuth2 permission grants for a service principal and principal
132
+ * Calls backend: GET /api/graphapi/oauth2-permission-grants
133
+ */
134
+ export async function oauth2PermissionGrantsGet(
135
+ spid: string,
136
+ oid: string
137
+ ): Promise<{ grants: string | null, id: string | null, error: string }> {
138
+ try {
139
+ if (!spid || !oid) {
140
+ return { grants: null, id: null, error: '400: Service Principal ID and Principal ID are required' };
141
+ }
142
+
143
+ const url = `/api/graphapi/oauth2-permission-grants?spid=${encodeURIComponent(spid)}&oid=${encodeURIComponent(oid)}`;
144
+ const response = await fetch(url, {
145
+ method: 'GET',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ },
149
+ });
150
+
151
+ if (!response.ok) {
152
+ return { grants: null, id: null, error: `${response.status}: Failed to retrieve permission grants` };
153
+ }
154
+
155
+ const apiResponse: ApiResponse<OAuth2PermissionGrantDto> = await response.json();
156
+
157
+ if (!apiResponse.success || !apiResponse.data) {
158
+ return { grants: null, id: null, error: apiResponse.error || 'Unknown error' };
159
+ }
160
+
161
+ return {
162
+ grants: apiResponse.data.grants,
163
+ id: apiResponse.data.id,
164
+ error: ''
165
+ };
166
+ } catch (error: any) {
167
+ console.error('Error in oauth2PermissionGrantsGet:', error);
168
+ return { grants: null, id: null, error: `Exception: ${error.message}` };
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Update OAuth2 permission grants
174
+ * Calls backend: PATCH /api/graphapi/oauth2-permission-grants/{id}
175
+ */
176
+ export async function oauth2PermissionGrantsSet(id: string, scopes: string): Promise<boolean> {
177
+ try {
178
+ if (!id || !scopes) {
179
+ console.error('oauth2PermissionGrantsSet: ID and scopes are required');
180
+ return false;
181
+ }
182
+
183
+ const url = `/api/graphapi/oauth2-permission-grants/${encodeURIComponent(id)}`;
184
+ const response = await fetch(url, {
185
+ method: 'PATCH',
186
+ headers: {
187
+ 'Content-Type': 'application/json',
188
+ },
189
+ body: JSON.stringify({ scopes }),
190
+ });
191
+
192
+ if (!response.ok) {
193
+ console.error(`oauth2PermissionGrantsSet: PATCH failed with status ${response.status}`);
194
+ return false;
195
+ }
196
+
197
+ const apiResponse: ApiResponse<boolean> = await response.json();
198
+
199
+ if (!apiResponse.success || !apiResponse.data) {
200
+ console.error(`oauth2PermissionGrantsSet: ${apiResponse.error}`);
201
+ return false;
202
+ }
203
+
204
+ return apiResponse.data;
205
+ } catch (error: any) {
206
+ console.error('Error in oauth2PermissionGrantsSet:', error);
207
+ return false;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Get service principal by application ID
213
+ * Calls backend: GET /api/graphapi/service-principals
214
+ */
215
+ export async function servicePrincipalGet(appid: string): Promise<{ spid: string, error: string }> {
216
+ try {
217
+ if (!appid) {
218
+ return { spid: '', error: '400: Application ID is required' };
219
+ }
220
+
221
+ const url = `/api/graphapi/service-principals?appId=${encodeURIComponent(appid)}`;
222
+ const response = await fetch(url, {
223
+ method: 'GET',
224
+ headers: {
225
+ 'Content-Type': 'application/json',
226
+ },
227
+ });
228
+
229
+ if (!response.ok) {
230
+ return { spid: '', error: `${response.status}: Failed to retrieve service principal` };
231
+ }
232
+
233
+ const apiResponse: ApiResponse<ServicePrincipalDto> = await response.json();
234
+
235
+ if (!apiResponse.success || !apiResponse.data) {
236
+ return { spid: '', error: apiResponse.error || 'Unknown error' };
237
+ }
238
+
239
+ return { spid: apiResponse.data.servicePrincipalId, error: '' };
240
+ } catch (error: any) {
241
+ console.error('Error in servicePrincipalGet:', error);
242
+ return { spid: '', error: `Exception: ${error.message}` };
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Search for users. When no search string is provided, lists all users (matching original behavior).
248
+ * Calls backend: GET /api/graphapi/users
249
+ */
250
+ export async function usersGet(userSearchString?: string): Promise<{ users: string[], error: string }> {
251
+ try {
252
+ const url = userSearchString
253
+ ? `/api/graphapi/users?search=${encodeURIComponent(userSearchString)}`
254
+ : `/api/graphapi/users`;
255
+ const response = await fetch(url, {
256
+ method: 'GET',
257
+ headers: {
258
+ 'Content-Type': 'application/json',
259
+ },
260
+ });
261
+
262
+ if (!response.ok) {
263
+ return { users: [], error: `${response.status}: Failed to retrieve users` };
264
+ }
265
+
266
+ const apiResponse: ApiResponse<UsersResponseDto> = await response.json();
267
+
268
+ if (!apiResponse.success || !apiResponse.data) {
269
+ return { users: [], error: apiResponse.error || 'Unknown error' };
270
+ }
271
+
272
+ // Convert to array of mail addresses (matching original behavior that returned user.mail)
273
+ const users = apiResponse.data.users.map(u => u.mail || u.userPrincipalName || u.displayName);
274
+
275
+ return { users, error: '' };
276
+ } catch (error: any) {
277
+ console.error('Error in usersGet:', error);
278
+ return { users: [], error: `Exception: ${error.message}` };
279
+ }
280
+ }
281
+
282
+ // ======================= Azure Management API Proxy Functions =======================
283
+
284
+ /**
285
+ * Check if the current user can list root assignments
286
+ * Calls backend: GET /api/azuremanagement/can-list-root-assignments
287
+ */
288
+ export async function canListRootAssignments(): Promise<boolean> {
289
+ try {
290
+ const url = '/api/azuremanagement/can-list-root-assignments';
291
+ const response = await fetch(url, {
292
+ method: 'GET',
293
+ headers: {
294
+ 'Content-Type': 'application/json',
295
+ },
296
+ });
297
+
298
+ if (!response.ok) {
299
+ console.error(`canListRootAssignments: Request failed with status ${response.status}`);
300
+ return false;
301
+ }
302
+
303
+ const apiResponse: ApiResponse<CanListRootAssignmentsResponseDto> = await response.json();
304
+
305
+ if (!apiResponse.success || !apiResponse.data) {
306
+ console.error(`canListRootAssignments: ${apiResponse.error}`);
307
+ return false;
308
+ }
309
+
310
+ return apiResponse.data.canList;
311
+ } catch (error: any) {
312
+ console.error('Error in canListRootAssignments:', error);
313
+ return false;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Elevate Global Admin to User Access Administrator role
319
+ * WARNING: This is a highly sensitive operation
320
+ * Calls backend: POST /api/azuremanagement/elevate-access
321
+ */
322
+ export async function elevateGlobalAdminToUserAccessAdmin(): Promise<boolean> {
323
+ try {
324
+ const url = '/api/azuremanagement/elevate-access';
325
+ const response = await fetch(url, {
326
+ method: 'POST',
327
+ headers: {
328
+ 'Content-Type': 'application/json',
329
+ },
330
+ });
331
+
332
+ if (!response.ok) {
333
+ console.error(`elevateGlobalAdminToUserAccessAdmin: Request failed with status ${response.status}`);
334
+ return false;
335
+ }
336
+
337
+ const apiResponse: ApiResponse<ElevateAccessResponseDto> = await response.json();
338
+
339
+ if (!apiResponse.success || !apiResponse.data) {
340
+ console.error(`elevateGlobalAdminToUserAccessAdmin: ${apiResponse.error}`);
341
+ return false;
342
+ }
343
+
344
+ return apiResponse.data.success;
345
+ } catch (error: any) {
346
+ console.error('Error in elevateGlobalAdminToUserAccessAdmin:', error);
347
+ return false;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Read Azure resources accessible to the current user
353
+ * Calls backend: GET /api/azuremanagement/resources
354
+ */
355
+ export async function readResources(): Promise<Resource[]> {
356
+ try {
357
+ const url = '/api/azuremanagement/resources';
358
+ const response = await fetch(url, {
359
+ method: 'GET',
360
+ headers: {
361
+ 'Content-Type': 'application/json',
362
+ },
363
+ });
364
+
365
+ if (!response.ok) {
366
+ console.error(`readResources: Request failed with status ${response.status}`);
367
+ return [];
368
+ }
369
+
370
+ const apiResponse: ApiResponse<ResourcesResponseDto> = await response.json();
371
+
372
+ if (!apiResponse.success || !apiResponse.data) {
373
+ console.error(`readResources: ${apiResponse.error}`);
374
+ return [];
375
+ }
376
+
377
+ return apiResponse.data.resources;
378
+ } catch (error: any) {
379
+ console.error('Error in readResources:', error);
380
+ return [];
381
+ }
382
+ }