@mindline/sync 1.0.57 → 1.0.59

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.
@@ -2,6 +2,5 @@
2
2
  "ExpandedNodes": [
3
3
  ""
4
4
  ],
5
- "SelectedNode": "\\index.ts",
6
5
  "PreviewInSolutionExplorer": false
7
6
  }
package/.vs/slnx.sqlite CHANGED
Binary file
Binary file
package/index.d.ts CHANGED
@@ -30,6 +30,8 @@ declare module "@mindline/sync" {
30
30
  static graphGroupsEndpoint: string;
31
31
  static graphMailEndpoint: string;
32
32
  static graphMeEndpoint: string;
33
+ static graphOauth2PermissionGrants: string;
34
+ static graphServicePrincipalsEndpoint: string;
33
35
  static graphUsersEndpoint: string;
34
36
  // sovereign cloud tenant info endpoints
35
37
  static graphTenantByDomainPredicate: string;
@@ -52,6 +54,7 @@ declare module "@mindline/sync" {
52
54
  group: string;
53
55
  value: string;
54
56
  consented: boolean;
57
+ removable: boolean;
55
58
  expanded: string;
56
59
  static compareByValue(a: UserScope, b: UserScope): number;
57
60
  static compareByGroup(a: UserScope, b: UserScope): number;
@@ -64,7 +67,6 @@ declare module "@mindline/sync" {
64
67
  tid: string; // from AAD ID token
65
68
  companyName: string; // findTenantInformationByTenantId TODO: process changes to company name
66
69
  companyDomain: string; // findTenantInformationByTenantId TODO: process changes to company name
67
- associatedWorkspaces: string[];
68
70
  workspaceIDs: string;
69
71
  session: string;
70
72
  spacode: string;
@@ -99,6 +101,7 @@ declare module "@mindline/sync" {
99
101
  authority: string; // from AAD ID auth response
100
102
  workspaceIDs: string;
101
103
  sel: boolean; // selection state
104
+ graphSP: string; // graph resource ID (service principal) for this tenant
102
105
  constructor();
103
106
  }
104
107
  // config
@@ -263,14 +266,18 @@ declare module "@mindline/sync" {
263
266
  //
264
267
  // Azure AD Graph API
265
268
  //
266
- export function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{groups: Group[], error: string}>;
269
+ export function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }>;
270
+ export function oauth2PermissionGrantsGet(options: RequestInit, spid: string, oid: string): Promise<{grants: string, error: string}>;
271
+ export function requestAdminConsent(user: User, scope: string): void;
272
+ export function servicePrincipalGet(options: RequestInit, appid: string): Promise<{ spid: string, error: string }>;
267
273
  export function signIn(user: User, tasks: TaskArray): boolean;
268
274
  export function signInIncrementally(user: User, scope: string): void;
269
- export function requestAdminConsent(user: User, scope: string): void;
270
275
  export function signOut(user: User): boolean;
271
276
  export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
272
277
  export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
273
- export function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): Promise<boolean>;
278
+ export function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean): boolean;
279
+ export function userDelegatedScopesGet(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant): { scopes: string, id: string, error: string };
280
+ export function userDelegatedScopesRemove(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant, scope: string): boolean;
274
281
  export function usersGet(instance: IPublicClientApplication, user: User | undefined): { users: string[], error: string };
275
282
  //
276
283
  // Mindline Config API
package/index.ts CHANGED
@@ -69,6 +69,8 @@ export class graphConfig {
69
69
  static graphGroupsEndpoint: string = "https://graph.microsoft.com/v1.0/groups";
70
70
  static graphMailEndpoint: string = "https://graph.microsoft.com/v1.0/me/messages";
71
71
  static graphMeEndpoint: string = "https://graph.microsoft.com/v1.0/me";
72
+ static graphOauth2PermissionGrants: string = "https://graph.microsoft.com/v1.0/oauth2PermissionGrants";
73
+ static graphServicePrincipalsEndpoint: string = "https://graph.microsoft.com/v1.0/servicePrincipals";
72
74
  static graphUsersEndpoint: string = "https://graph.microsoft.com/v1.0/users";
73
75
  // sovereign cloud tenant info endpoints
74
76
  static graphTenantByDomainPredicate: string = "beta/tenantRelationships/findTenantInformationByDomainName";
@@ -90,6 +92,7 @@ export class UserScope {
90
92
  group: string;
91
93
  value: string;
92
94
  consented: boolean;
95
+ removable: boolean;
93
96
  expanded: string;
94
97
  static compareByValue(a: UserScope, b: UserScope): number {
95
98
  return a.value.localeCompare(b.value);
@@ -106,7 +109,6 @@ export class User {
106
109
  tid: string;
107
110
  companyName: string;
108
111
  companyDomain: string;
109
- associatedWorkspaces: string[];
110
112
  workspaceIDs: string;
111
113
  session: string; // button text
112
114
  spacode: string; // to get front end access token
@@ -119,11 +121,10 @@ export class User {
119
121
  this.oid = "";
120
122
  this.name = "";
121
123
  this.mail = "";
122
- this.authority = "";
124
+ this.authority = "https://login.microsoftonline.com/";
123
125
  this.tid = "";
124
126
  this.companyName = "";
125
127
  this.companyDomain = "";
126
- this.associatedWorkspaces = new Array();
127
128
  this.workspaceIDs = "";
128
129
  this.session = "Sign In";
129
130
  this.spacode = "";
@@ -157,6 +158,7 @@ export class Tenant {
157
158
  authority: string;
158
159
  workspaceIDs: string;
159
160
  sel: boolean; // selection state
161
+ graphSP: string; // graph resource ID (service principal) for this tenant
160
162
  constructor() {
161
163
  this.tid = "";
162
164
  this.name = "";
@@ -167,6 +169,7 @@ export class Tenant {
167
169
  this.authority = "";
168
170
  this.workspaceIDs = "";
169
171
  this.sel = false;
172
+ this.graphSP = "";
170
173
  }
171
174
  }
172
175
  function getGraphEndpoint(authority: string): string {
@@ -381,7 +384,6 @@ export class InitInfo {
381
384
  newuser.tid = user.tid;
382
385
  newuser.companyName = user.companyName;
383
386
  newuser.companyDomain = user.companyDomain;
384
- newuser.associatedWorkspaces = user.associatedWorkspaces;
385
387
  newuser.workspaceIDs = user.workspaceIDs;
386
388
  newuser.session = user.session;
387
389
  newuser.spacode = user.spacode;
@@ -897,11 +899,9 @@ export class BatchArray {
897
899
  this.pb_idleMax = Math.max(this.pb_idle, this.pb_idleMax);
898
900
  setIdleText(`${this.pb_total} seconds elapsed. Last update ${this.pb_idle} seconds ago. [max idle: ${this.pb_idleMax}/60]`);
899
901
  if (this.pb_idle >= 60) {
900
- clearInterval(this.pb_timer);
901
- this.pb_timer = null;
902
902
  if (this.milestoneArray.milestones[0].Write == null) {
903
903
  this.milestoneArray.write(setMilestones);
904
- setConfigSyncResult(`finished sync, no updates for ${this.pb_idle} seconds`);
904
+ setConfigSyncResult(`sync continuing, but no update for ${this.pb_idle} seconds`);
905
905
  }
906
906
  }
907
907
  // if we get to 100, the progress bar stops but SignalR or countdown timer completes the sync
@@ -1198,7 +1198,6 @@ export class TenantNode {
1198
1198
  //
1199
1199
  // Azure AD Graph API
1200
1200
  //
1201
- //groupsGet - GET /groups
1202
1201
  export async function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
1203
1202
  // need a logged in user to get graph users
1204
1203
  if (user == null || user.spacode == "") {
@@ -1222,6 +1221,103 @@ export async function groupsGet(instance: IPublicClientApplication, user: User |
1222
1221
  return { groups: [], error: `Exception: ${error}` };
1223
1222
  }
1224
1223
  }
1224
+ export async function oauth2PermissionGrantsGet(options: RequestInit, spid: string, oid: string): Promise<{ grants: string, id: string, error: string }> {
1225
+ try {
1226
+ // make /oauth2PermissionGrants endpoint call
1227
+ let spurl: string = graphConfig.graphOauth2PermissionGrants;
1228
+ let url: URL = new URL(spurl);
1229
+ url.searchParams.append("$filter", `resourceId eq '${spid}' and consentType eq 'Principal' and principalId eq '${oid}'`);
1230
+ let response = await fetch(url.href, options);
1231
+ let data = await response.json();
1232
+ if (typeof data.error != "undefined") {
1233
+ return { grants: null, id: null, error: `${data.error.code}: ${data.error.message}` };
1234
+ }
1235
+ // we assume there is only one such grant
1236
+ if (data.value.length != 1) {
1237
+ debugger;
1238
+ return { grants: null, id: null, error: `oauth2PermissionGrantsGet: more than one matching delegated consent grant.` };
1239
+ }
1240
+ return { grants: data.value[0].scope, id: data.value[0].id, error: `` };
1241
+ }
1242
+ catch (error: any) {
1243
+ console.log(error);
1244
+ return { grants: null, id: null, error: `Exception: ${error}` };
1245
+ }
1246
+ }
1247
+ export async function oauth2PermissionGrantsSet(instance: IPublicClientApplication, loggedInUser: User, id: string, scopes: string): Promise<boolean> {
1248
+ // need a logged in user to get graph users
1249
+ if (loggedInUser == null || loggedInUser.spacode == "") {
1250
+ return false;
1251
+ }
1252
+ // make /oauth2PermissionGrants endpoint call
1253
+ try {
1254
+ let grantsurl: string = graphConfig.graphOauth2PermissionGrants + `/${id}`;
1255
+ let scopesBody: string = `{ "scope": "${scopes}" }`;
1256
+ const headers = await defineHeaders(instance, loggedInUser);
1257
+ let options: RequestInit = { method: "PATCH", headers: headers, body: scopesBody };
1258
+ let response = await fetch(grantsurl, options);
1259
+ if (response.status == 204 && response.statusText == "No Content") {
1260
+ return true;
1261
+ }
1262
+ else {
1263
+ debugger;
1264
+ console.log(`oauth2PermissionGrantsSet: PATCH failed ${data.error.code}: ${data.error.message}`);
1265
+ return false;
1266
+ }
1267
+ }
1268
+ catch (error: any) {
1269
+ debugger;
1270
+ console.log(error);
1271
+ return false;
1272
+ }
1273
+ }
1274
+ export function requestAdminConsent(user: User, scope: string): void {
1275
+ if (user.oid == "1") return;
1276
+ //
1277
+ // for app permissions (app roles) we must use the /.default scope for admin consent
1278
+ // https://learn.microsoft.com/EN-US/azure/active-directory/develop/v2-admin-consent#:~:text=In%20order%20to%20request%20app%20permissions%2C%20you%20must%20use%20the%20/.default%20value.
1279
+ // https://learn.microsoft.com/en-us/answers/questions/431784/how-to-grant-application-permissions-with-dynamic
1280
+ //
1281
+ // this means that, if we want to be granular about SyncReader vs. SyncWriter permissions, we must have separate Applications
1282
+ // for now, we request the /.default scope whenever any of the SyncReader or SyncWriter permissions are requested
1283
+ //
1284
+ // trying to use the Challenge endpoint for app permissions, setting this scope caused the call to quietly fail without error
1285
+ // scope = "63100afe-506e-4bb2-8ff7-d8d5ab373129/.default";
1286
+ //
1287
+ // thereforce, we are assuming that, for app permissions (app roles), the Microsoft Identity Web Challenge endpoint does not work and we need to perform our own redirect to the admin consent endpoint
1288
+ // https://learn.microsoft.com/EN-US/azure/active-directory/develop/scopes-oidc#client-credentials-grant-flow-and-default
1289
+ // https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#request-the-permissions-from-a-directory-admin
1290
+ //
1291
+ let adminConsentURL: string = "https://login.microsoftonline.com/";
1292
+ adminConsentURL += user.tid;
1293
+ adminConsentURL += "/adminconsent";
1294
+ let url: URL = new URL(adminConsentURL);
1295
+ url.searchParams.append("client_id", "63100afe-506e-4bb2-8ff7-d8d5ab373129");
1296
+ url.searchParams.append("redirect_uri", window.location.origin);
1297
+ url.searchParams.append("domain_hint", user.companyDomain);
1298
+ window.location.assign(url.href);
1299
+ }
1300
+ export async function servicePrincipalGet(options: RequestInit, appid: string): Promise<{ spid: string, error: string }> {
1301
+ try {
1302
+ // make /servicePrincipals endpoint call to get the Service Principal ID
1303
+ let spurl: string = graphConfig.graphServicePrincipalsEndpoint;
1304
+ spurl += `(appId='${appid}')`;
1305
+ let url: URL = new URL(spurl);
1306
+ url.searchParams.append("$select", "id,appId,displayName");
1307
+ let response = await fetch(url.href, options);
1308
+ let data = await response.json();
1309
+ if (typeof data.error !== "undefined") {
1310
+ return { spid: "", error: `${data.error.code}: ${data.error.message}` };
1311
+ }
1312
+ else {
1313
+ return { spid: data.id, error: `` };
1314
+ }
1315
+ }
1316
+ catch (error: any) {
1317
+ console.log(error);
1318
+ return { spid: "", error: `Exception: ${error}` };
1319
+ }
1320
+ }
1225
1321
  export async function signIn(user: User, tasks: TaskArray): Promise<boolean> {
1226
1322
  // admin authority is blank at signIn, lookup authority real-time
1227
1323
  if (user.authority == "") {
@@ -1291,32 +1387,6 @@ export function signInIncrementally(user: User, scope: string): void {
1291
1387
  url.searchParams.append("loginHint", user.mail);
1292
1388
  window.location.assign(url.href);
1293
1389
  }
1294
- export function requestAdminConsent(user: User, scope: string): void {
1295
- if (user.oid == "1") return;
1296
- //
1297
- // for app permissions (app roles) we must use the /.default scope for admin consent
1298
- // https://learn.microsoft.com/EN-US/azure/active-directory/develop/v2-admin-consent#:~:text=In%20order%20to%20request%20app%20permissions%2C%20you%20must%20use%20the%20/.default%20value.
1299
- // https://learn.microsoft.com/en-us/answers/questions/431784/how-to-grant-application-permissions-with-dynamic
1300
- //
1301
- // this means that, if we want to be granular about SyncReader vs. SyncWriter permissions, we must have separate Applications
1302
- // for now, we request the /.default scope whenever any of the SyncReader or SyncWriter permissions are requested
1303
- //
1304
- // trying to use the Challenge endpoint for app permissions, setting this scope caused the call to quietly fail without error
1305
- // scope = "63100afe-506e-4bb2-8ff7-d8d5ab373129/.default";
1306
- //
1307
- // thereforce, we are assuming that, for app permissions (app roles), the Microsoft Identity Web Challenge endpoint does not work and we need to perform our own redirect to the admin consent endpoint
1308
- // https://learn.microsoft.com/EN-US/azure/active-directory/develop/scopes-oidc#client-credentials-grant-flow-and-default
1309
- // https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#request-the-permissions-from-a-directory-admin
1310
- //
1311
- let adminConsentURL: string = "https://login.microsoftonline.com/";
1312
- adminConsentURL += user.tid;
1313
- adminConsentURL += "/adminconsent";
1314
- let url: URL = new URL(adminConsentURL);
1315
- url.searchParams.append("client_id", "63100afe-506e-4bb2-8ff7-d8d5ab373129");
1316
- url.searchParams.append("redirect_uri", window.location.origin);
1317
- url.searchParams.append("domain_hint", user.companyDomain);
1318
- window.location.assign(url.href);
1319
- }
1320
1390
  export async function signOut(user: User): Promise<boolean>{
1321
1391
  if (user.oid == "1") return false;
1322
1392
  // set logout_hint in the .NET session for streamlined logout
@@ -1355,10 +1425,10 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
1355
1425
  try {
1356
1426
  let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
1357
1427
  loggedInUser.accessToken = response.accessToken; // cache access token on the user
1358
- console.log("Front end token acquired: " + loggedInUser.accessToken.slice(0, 20));
1428
+ console.log("tenantRelationshipsGetByDomain: Front end token acquired: " + loggedInUser.accessToken.slice(0, 20));
1359
1429
  }
1360
1430
  catch (error: any) {
1361
- console.log("Front end token failure: " + error);
1431
+ console.log("tenantRelationshipsGetByDomain: Front end token failure: " + error);
1362
1432
  return false; // failed to get access token, no need to re-render
1363
1433
  }
1364
1434
  }
@@ -1374,37 +1444,38 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
1374
1444
  tenantEndpoint += "(domainName='";
1375
1445
  tenantEndpoint += tenant.domain;
1376
1446
  tenantEndpoint += "')";
1377
- console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
1447
+ console.log("tenantRelationshipsGetByDomain: Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
1378
1448
  let response = await fetch(tenantEndpoint, options);
1379
1449
  if (response.status == 200 && response.statusText == "OK") {
1380
1450
  let data = await response.json();
1381
1451
  if (data) {
1382
1452
  if (data.error != null) {
1383
1453
  debugger;
1384
- console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
1454
+ console.log("tenantRelationshipsGetByDomain: Failed GET from /findTenantInformationByDomainName: ", data.error.message);
1385
1455
  return false;
1386
1456
  }
1387
1457
  else if (data.displayName != null && data.displayName !== "") {
1388
1458
  // set domain information on passed tenant
1389
1459
  tenant.tid = data.tenantId;
1390
1460
  tenant.name = data.displayName;
1391
- console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
1461
+ console.log("tenantRelationshipsGetByDomain: Successful GET from /findTenantInformationByDomainName: ", data.displayName);
1392
1462
  return true; // success, need UX to re-render
1393
1463
  }
1394
1464
  }
1395
1465
  else {
1396
- console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
1466
+ console.log("tenantRelationshipsGetByDomain: Failed to GET from /findTenantInformationByDomainName: ", tenantEndpoint);
1397
1467
  }
1398
1468
  }
1399
1469
  }
1400
1470
  catch (error: any) {
1401
- console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
1471
+ console.log("Failed to GET from /findTenantInformationByDomainName: ", error);
1402
1472
  return false; // failed, no need for UX to re-render
1403
1473
  }
1404
1474
  return false; // failed, no need for UX to re-render
1405
1475
  }
1406
1476
  //tenantRelationshipsGetById - query AAD for associated company name and domain
1407
1477
  export async function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): Promise<boolean> {
1478
+ console.log("**** tenantRelationshipsGetById");
1408
1479
  if (debug) debugger;
1409
1480
  // since we should mainly be called when a user has newly logged in, we can afford the performance hit of looking up the tenant name and domain again
1410
1481
  // if (user.companyName != "") return false;
@@ -1413,10 +1484,10 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
1413
1484
  try {
1414
1485
  let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
1415
1486
  user.accessToken = response.accessToken; // cache access token
1416
- console.log("Front end token acquired: " + user.accessToken.slice(0, 20));
1487
+ console.log("tenantRelationshipsGetById: Front end token acquired: " + user.accessToken.slice(0, 20));
1417
1488
  }
1418
1489
  catch (error: any) {
1419
- console.log("Front end token failure: " + error);
1490
+ console.log("tenantRelationshipsGetById: Front end token failure: " + error);
1420
1491
  return false; // failed to get access token, no need to re-render
1421
1492
  }
1422
1493
  }
@@ -1434,7 +1505,7 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
1434
1505
  tenantEndpoint += "')";
1435
1506
  // track time of tenant details query
1436
1507
  tasks.setTaskStart("GET tenant details", new Date());
1437
- console.log("Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
1508
+ console.log("tenantRelationshipsGetById: Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
1438
1509
  let response = await fetch(tenantEndpoint, options);
1439
1510
  let data = await response.json();
1440
1511
  if (data && typeof data.displayName !== undefined && data.displayName !== "") {
@@ -1451,16 +1522,16 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
1451
1522
  console.log("tenantRelationshipsGetById: missing associated tenant for logged in user.");
1452
1523
  debugger;
1453
1524
  }
1454
- console.log("Successful GET from /findTenantInformationByTenantId: ", data.displayName);
1525
+ console.log("tenantRelationshipsGetById: Successful GET from /findTenantInformationByTenantId: ", data.displayName);
1455
1526
  tasks.setTaskEnd("GET tenant details", new Date(), "complete");
1456
1527
  return true; // success, need UX to re-render
1457
1528
  }
1458
1529
  else {
1459
- console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
1530
+ console.log("tenantRelationshipsGetById: Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
1460
1531
  }
1461
1532
  }
1462
1533
  catch (error: any) {
1463
- console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
1534
+ console.log("tenantRelationshipsGetById: Failed to GET from /findTenantInformationByTenantId: ", error);
1464
1535
  tasks.setTaskEnd("GET tenant details", new Date(), "failed");
1465
1536
  return false; // failed, no need for UX to re-render
1466
1537
  }
@@ -1516,17 +1587,76 @@ export async function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean
1516
1587
  }
1517
1588
  return false; // failed, no need for UX to re-render
1518
1589
  }
1590
+ export async function userDelegatedScopesGet(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant): Promise<{ scopes: string, id: string, error: string }> {
1591
+ // need a logged in user and valid tenant to query graph
1592
+ if (loggedInUser == null || loggedInUser.spacode == "" || tenant == null) {
1593
+ debugger;
1594
+ return { scopes: null, id: null, error: `500: invalid parameter(s) passed to getUserDelegatedScopes` };
1595
+ }
1596
+ // create headers
1597
+ const headers = await defineHeaders(instance, loggedInUser);
1598
+ let options: RequestInit = { method: "GET", headers: headers };
1599
+ try {
1600
+ // first, cache Graph resource ID (service principal) for this tenant if we don't have it already
1601
+ if (tenant.graphSP == "") {
1602
+ let { spid, error } = await servicePrincipalGet(options, "00000003-0000-0000-c000-000000000000");
1603
+ if (error != "") {
1604
+ debugger;
1605
+ return { scopes: null, id: null, error: `${error}` };
1606
+ }
1607
+ tenant.graphSP = spid;
1608
+ }
1609
+ // then, retrieve the delegated Graph permissions assigned to this user
1610
+ let { grants, id, error } = await oauth2PermissionGrantsGet(options, tenant.graphSP, loggedInUser.oid);
1611
+ if (error != "") {
1612
+ debugger;
1613
+ return { scopes: null, id: null, error: `${error}` };
1614
+ }
1615
+ return { scopes: grants, id: id, error: `` };
1616
+ }
1617
+ catch (error: any) {
1618
+ debugger;
1619
+ console.log(error);
1620
+ return { scopes: null, id: null, error: `Exception: ${error}` };
1621
+ }
1622
+ }
1623
+ export async function userDelegatedScopesRemove(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant, scope: string): Promise<boolean> {
1624
+ // need a logged in user and valid tenant to query graph
1625
+ if (loggedInUser == null || loggedInUser.spacode == "" || tenant == null) {
1626
+ debugger;
1627
+ return false;
1628
+ }
1629
+ // get current set of delegated scopes in order to remove passed scope
1630
+ let { scopes, id, error } = await userDelegatedScopesGet(instance, loggedInUser, tenant);
1631
+ if (error != "") {
1632
+ debugger;
1633
+ console.log(`userDelegatedScopesRemove: cannot find userDelegatedScopes for ${loggedInUser.mail}: ${error}`);
1634
+ return false;
1635
+ }
1636
+ // remove passed scope (case sensitive)
1637
+ scopes = scopes.replace(scope, "");
1638
+ // set updated oauth2permissions
1639
+ let removed: boolean = await oauth2PermissionGrantsSet(instance, loggedInUser, id, scopes);
1640
+ if (!removed) {
1641
+ debugger;
1642
+ console.log(`userDelegatedScopesRemove: cannot set oauth2PermissionGrants for ${loggedInUser.mail}: ${error}`);
1643
+ return false;
1644
+ }
1645
+ // replace scope array on logged in user
1646
+ loggedInUser.scopes = scopes.split(" ");
1647
+ return removed;
1648
+ }
1519
1649
  //usersGet - GET from AAD Users endpoint
1520
1650
  export async function usersGet(instance: IPublicClientApplication, user: User | undefined): Promise<{ users: string[], error: string }> {
1521
1651
  // need a logged in user to get graph users
1522
1652
  if (user == null || user.spacode == "") {
1523
1653
  return { users: [], error: `500: invalid user passed to groupsGet` };
1524
1654
  }
1525
- // create headers
1526
- const headers = await defineHeaders(instance, user);
1527
- let options = { method: "GET", headers: headers };
1528
1655
  // make /users endpoint call
1529
1656
  try {
1657
+ // create headers
1658
+ const headers = await defineHeaders(instance, user);
1659
+ let options = { method: "GET", headers: headers };
1530
1660
  let response = await fetch(graphConfig.graphUsersEndpoint, options);
1531
1661
  let data = await response.json();
1532
1662
  if (typeof data.error !== "undefined") {
@@ -1543,9 +1673,7 @@ export async function usersGet(instance: IPublicClientApplication, user: User |
1543
1673
  return { users: [], error: `Exception: ${error}` };
1544
1674
  }
1545
1675
  }
1546
- //
1547
- // Mindline Config API
1548
- //
1676
+ // ======================= Mindline Config API ===============================
1549
1677
  export async function configEdit(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
1550
1678
  let result: APIResult = new APIResult();
1551
1679
  if (config.id === "1") {
@@ -1589,6 +1717,7 @@ export async function configsRefresh(instance: IPublicClientApplication, authori
1589
1717
  }
1590
1718
  // retrieve Workspace(s), User(s), Tenant(s), Config(s) given newly logged in user
1591
1719
  export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult> {
1720
+ console.log(`>>>>>> initGet`);
1592
1721
  let result: APIResult = new APIResult();
1593
1722
  if (debug) debugger;
1594
1723
  // lookup authority for this user (the lookup call does it based on domain, but TID works as well to find authority)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mindline/sync",
3
3
  "type": "module",
4
- "version": "1.0.57",
4
+ "version": "1.0.59",
5
5
  "types": "index.d.ts",
6
6
  "exports": "./index.ts",
7
7
  "description": "sync is a node.js package encapsulating javscript classes required for configuring Mindline sync service.",
package/tenants.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "permissionType": "",
8
8
  "onboarded": false,
9
9
  "authority": "",
10
- "sel": true
10
+ "sel": true,
11
+ "graphSP": ""
11
12
  }
12
13
  ]
package/users.json CHANGED
@@ -7,7 +7,6 @@
7
7
  "tid": "",
8
8
  "companyName": "",
9
9
  "companyDomain": "",
10
- "associatedWorkspaces": [ "1" ],
11
10
  "session": "Sign In",
12
11
  "sel": true
13
12
  }
package/users2.json CHANGED
@@ -7,7 +7,6 @@
7
7
  "tid": "7f4567b8-f9a9-4ad3-9cb5-ef16a80e5744",
8
8
  "companyName": "Mindline1",
9
9
  "companyDomain": "mindline1.onmicrosoft.com",
10
- "associatedWorkspaces": [ "1", "2", "3" ],
11
10
  "session": "Sign In"
12
11
  },
13
12
  {
@@ -18,7 +17,6 @@
18
17
  "tid": "df9c2e0a-f6fe-43bb-a155-d51f66dffe0e",
19
18
  "companyName": "Mindline2",
20
19
  "companyDomain": "mindline2.onmicrosoft.com",
21
- "associatedWorkspaces": [ "1" ],
22
20
  "session": "Sign In"
23
21
  },
24
22
  {
@@ -29,7 +27,6 @@
29
27
  "tid": "1",
30
28
  "companyName": "WhoIAm",
31
29
  "companyDomain": "whoiam.onmicrosoft.com",
32
- "associatedWorkspaces": [ "2" ],
33
30
  "session": "Sign In"
34
31
  },
35
32
  {
@@ -40,7 +37,6 @@
40
37
  "tid": "2",
41
38
  "companyName": "Grit Sofware",
42
39
  "companyDomain": "gritsoftware.onmicrosoft.com",
43
- "associatedWorkspaces": [ "2" ],
44
40
  "session": "Sign In"
45
41
  },
46
42
  {
@@ -51,7 +47,6 @@
51
47
  "tid": "3",
52
48
  "companyName": "Google",
53
49
  "companyDomain": "google.onmicrosoft.com",
54
- "associatedWorkspaces": [ "3" ],
55
50
  "session": "Sign In"
56
51
  },
57
52
  {
@@ -62,7 +57,6 @@
62
57
  "tid": "4",
63
58
  "companyName": "Trackman Golf",
64
59
  "companyDomain": "trackman.onmicrosoft.com",
65
- "associatedWorkspaces": [ "3" ],
66
60
  "session": "Sign In"
67
61
  }
68
62
  ]