@mindline/sync 1.0.111 → 1.0.113

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/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import workspaces from "./workspaces.json";
9
9
  import syncmilestones from './syncmilestones.json';
10
10
  import resources from './resources.json';
11
11
  import actors from './actors.json';
12
+ import * as secureApiClient from './api-client';
12
13
 
13
14
  const FILTER_FIELD = "workspaceIDs";
14
15
  // called by unit tests
@@ -1629,152 +1630,14 @@ export class ActorNode {
1629
1630
  }
1630
1631
  }
1631
1632
  // ======================= Azure AD Graph API ===============================
1632
- // helper functions
1633
- function getGraphAPIScope(user: User): string {
1634
- user = user;
1635
- return "Group.Read.All User.Read.All openid profile offline_access User.Read Contacts.Read CrossTenantInformation.ReadBasic.All";
1633
+ export async function groupsGet(_instance: IPublicClientApplication, _user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
1634
+ return await secureApiClient.groupsGet(groupSearchString);
1636
1635
  }
1637
- // TODO: this is where you want to trigger a re-authentication if token expires
1638
- async function graphDefineHeaders(
1639
- instance: IPublicClientApplication,
1640
- user: User
1641
- ): Promise<Headers> {
1642
- const headers = new Headers();
1643
- headers.append("Content-Type", "application/json");
1644
- headers.append("accept", "*/*");
1645
- const graphAPIScope: string = getGraphAPIScope(user);
1646
- // only call acquireTokenByCode if we have never redeemed the code
1647
- if (user.graphAccessToken == null || user.graphAccessToken === "") {
1648
- try {
1649
- let response: AuthenticationResult = await instance.acquireTokenByCode({
1650
- code: user.spacode,
1651
- });
1652
- user.graphAccessToken = response.accessToken; // cache access token
1653
- console.log("Front end token acquired by code: " + user.graphAccessToken.slice(0, 20));
1654
- }
1655
- catch (error: any) {
1656
- console.log("Front end token failure: " + error);
1657
- }
1658
- }
1659
- // otherwise, call acquireTokenSilent and deal with token expiration on exception
1660
- else {
1661
- try {
1662
- let accounts: AccountInfo[] = instance.getAllAccounts();
1663
- let homeAccountId = user.oid + "." + user.tid;
1664
- let account: AccountInfo | undefined | null = null;
1665
- for (let i: number = 0; i < accounts.length; i++) {
1666
- if (accounts[i].homeAccountId == homeAccountId) {
1667
- account = accounts[i];
1668
- }
1669
- }
1670
- let response: AuthenticationResult = await instance.acquireTokenSilent({
1671
- scopes: [graphAPIScope],
1672
- account: account!
1673
- });
1674
- user.graphAccessToken = response.accessToken; // cache access token
1675
- console.log("Front end token graph acquired silently: " + user.graphAccessToken.slice(0, 20));
1676
- }
1677
- catch (error: any) {
1678
- try {
1679
- console.log("Front end graph token silent acquisition failure: " + error);
1680
- // fallback to redirect if silent acquisition fails
1681
- let accounts: AccountInfo[] = instance.getAllAccounts();
1682
- let homeAccountId = user.oid + "." + user.tid;
1683
- let account: AccountInfo | null = null;
1684
- for (let i: number = 0; i < accounts.length; i++) {
1685
- if (accounts[i].homeAccountId == homeAccountId) {
1686
- account = accounts[i];
1687
- }
1688
- }
1689
- // assumption: this redirect will trigger login flow callbacks in program.cs
1690
- instance.acquireTokenRedirect({
1691
- scopes: [graphAPIScope],
1692
- account: account!
1693
- });
1694
- }
1695
- catch (error: any) {
1696
- console.log("Front end graph token redirect acquisition failure: " + error);
1697
- }
1698
- }
1699
- }
1700
- headers.append("Authorization", `Bearer ${user.graphAccessToken}`);
1701
- return headers;
1636
+ export async function oauth2PermissionGrantsGet(_options: RequestInit, _user: User, spid: string, oid: string): Promise<{ grants: string | null, id: string | null, error: string }> {
1637
+ return await secureApiClient.oauth2PermissionGrantsGet(spid, oid);
1702
1638
  }
1703
- export async function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
1704
- // need a logged in user to get graph users
1705
- if (user == null || user.spacode == "") {
1706
- return { groups: [], error: `500: invalid user passed to groupsGet` };
1707
- }
1708
- // create headers
1709
- const headers = await graphDefineHeaders(instance, user);
1710
- let options = { method: "GET", headers: headers };
1711
- // make /groups endpoint call
1712
- try {
1713
- let groupsEndpoint: string = getGraphEndpoint(user.authority) + graphConfig.graphGroupsPredicate;
1714
- groupsEndpoint += `/?$filter=startsWith(displayName, '${groupSearchString}')`;
1715
- let response = await fetch(groupsEndpoint, options);
1716
- let data = await response.json();
1717
- if (typeof data.error !== "undefined") {
1718
- return { groups: [], error: `${data.error.code}: ${data.error.message}` };
1719
- }
1720
- return { groups: data.value, error: `` };
1721
- }
1722
- catch (error: any) {
1723
- console.log(error);
1724
- return { groups: [], error: `Exception: ${error}` };
1725
- }
1726
- }
1727
- export async function oauth2PermissionGrantsGet(options: RequestInit, user: User, spid: string, oid: string): Promise<{ grants: string | null, id: string | null, error: string }> {
1728
- try {
1729
- // make /oauth2PermissionGrants endpoint call
1730
- let spurl: string = getGraphEndpoint(user.authority) + graphConfig.graphOauth2PermissionGrantsPredicate;
1731
- let url: URL = new URL(spurl);
1732
- url.searchParams.append("$filter", `resourceId eq '${spid}' and consentType eq 'Principal' and principalId eq '${oid}'`);
1733
- let response = await fetch(url.href, options);
1734
- let data = await response.json();
1735
- if (typeof data.error != "undefined") {
1736
- return { grants: null, id: null, error: `${data.error.code}: ${data.error.message}` };
1737
- }
1738
- // we assume there is only one such grant
1739
- if (data.value.length != 1) {
1740
- debugger;
1741
- return { grants: null, id: null, error: `oauth2PermissionGrantsGet: more than one matching delegated consent grant.` };
1742
- }
1743
- return { grants: data.value[0].scope, id: data.value[0].id, error: `` };
1744
- }
1745
- catch (error: any) {
1746
- console.log(error);
1747
- return { grants: null, id: null, error: `Exception: ${error}` };
1748
- }
1749
- }
1750
- export async function oauth2PermissionGrantsSet(instance: IPublicClientApplication, loggedInUser: User, id: string, scopes: string): Promise<boolean> {
1751
- // need a logged in user to get graph users
1752
- if (loggedInUser == null || loggedInUser.spacode == "") {
1753
- return false;
1754
- }
1755
- // make /oauth2PermissionGrants endpoint call
1756
- try {
1757
- let grantsurl: string = getGraphEndpoint(loggedInUser.authority);
1758
- grantsurl += graphConfig.graphOauth2PermissionGrantsPredicate + `/${id}`;
1759
- let scopesBody: string = `{ "scope": "${scopes}" }`;
1760
- const headers = await graphDefineHeaders(instance, loggedInUser);
1761
- let options: RequestInit = { method: "PATCH", headers: headers, body: scopesBody };
1762
- let response = await fetch(grantsurl, options);
1763
- let data = await response.json();
1764
- if (response.status == 204 && response.statusText == "No Content") {
1765
- return true;
1766
- }
1767
- else {
1768
- debugger;
1769
- console.log(`oauth2PermissionGrantsSet: PATCH failed ${data.error.code}: ${data.error.message}`);
1770
- return false;
1771
- }
1772
- }
1773
- catch (error: any) {
1774
- debugger;
1775
- console.log(error);
1776
- return false;
1777
- }
1639
+ export async function oauth2PermissionGrantsSet(_instance: IPublicClientApplication, _loggedInUser: User, id: string, scopes: string): Promise<boolean> {
1640
+ return await secureApiClient.oauth2PermissionGrantsSet(id, scopes);
1778
1641
  }
1779
1642
  export function requestAdminConsent(admin: User, tct: TenantConfigType): void {
1780
1643
  //
@@ -1805,27 +1668,8 @@ export function requestAdminConsent(admin: User, tct: TenantConfigType): void {
1805
1668
  url.searchParams.append("login_hint", admin.mail);
1806
1669
  window.location.assign(url.href);
1807
1670
  }
1808
- export async function servicePrincipalGet(options: RequestInit, user: User, appid: string): Promise<{ spid: string, error: string }> {
1809
- try {
1810
- // make /servicePrincipals endpoint call to get the Service Principal ID
1811
- let spurl: string = getGraphEndpoint(user.authority);
1812
- spurl += graphConfig.graphServicePrincipalsPredicate;
1813
- spurl += `(appId='${appid}')`;
1814
- let url: URL = new URL(spurl);
1815
- url.searchParams.append("$select", "id,appId,displayName");
1816
- let response = await fetch(url.href, options);
1817
- let data = await response.json();
1818
- if (typeof data.error !== "undefined") {
1819
- return { spid: "", error: `${data.error.code}: ${data.error.message}` };
1820
- }
1821
- else {
1822
- return { spid: data.id, error: `` };
1823
- }
1824
- }
1825
- catch (error: any) {
1826
- console.log(error);
1827
- return { spid: "", error: `Exception: ${error}` };
1828
- }
1671
+ export async function servicePrincipalGet(_options: RequestInit, _user: User, appid: string): Promise<{ spid: string, error: string }> {
1672
+ return await secureApiClient.servicePrincipalGet(appid);
1829
1673
  }
1830
1674
  export async function signIn(user: User, tasks: TaskArray): Promise<boolean> {
1831
1675
  // admin authority is blank at signIn, lookup authority real-time
@@ -2116,59 +1960,47 @@ export async function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean
2116
1960
  }
2117
1961
  return false; // failed, no need for UX to re-render
2118
1962
  }
2119
- export async function userDelegatedScopesGet(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant): Promise<{ scopes: string | null, id: string | null, error: string }> {
2120
- // need a logged in user and valid tenant to query graph
2121
- if (loggedInUser == null || loggedInUser.spacode == "" || tenant == null) {
2122
- debugger;
1963
+ export async function userDelegatedScopesGet(_instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant): Promise<{ scopes: string | null, id: string | null, error: string }> {
1964
+ if (loggedInUser == null || tenant == null) {
2123
1965
  return { scopes: null, id: null, error: `500: invalid parameter(s) passed to getUserDelegatedScopes` };
2124
1966
  }
2125
- // create headers
2126
- const headers = await graphDefineHeaders(instance, loggedInUser);
2127
- let options: RequestInit = { method: "GET", headers: headers };
2128
1967
  try {
2129
1968
  // first, cache Graph resource ID (service principal) for this tenant if we don't have it already
2130
1969
  if (tenant.graphSP == "") {
2131
- let { spid, error } = await servicePrincipalGet(options, loggedInUser, "00000003-0000-0000-c000-000000000000");
1970
+ let { spid, error } = await secureApiClient.servicePrincipalGet("00000003-0000-0000-c000-000000000000");
2132
1971
  if (error != "") {
2133
- debugger;
2134
1972
  return { scopes: null, id: null, error: `${error}` };
2135
1973
  }
2136
1974
  tenant.graphSP = spid;
2137
1975
  }
2138
1976
  // then, retrieve the delegated Graph permissions assigned to this user
2139
- let { grants, id, error } = await oauth2PermissionGrantsGet(options, loggedInUser, tenant.graphSP, loggedInUser.oid);
1977
+ let { grants, id, error } = await secureApiClient.oauth2PermissionGrantsGet(tenant.graphSP, loggedInUser.oid);
2140
1978
  if (error != "") {
2141
- debugger;
2142
1979
  return { scopes: null, id: null, error: `${error}` };
2143
1980
  }
2144
1981
  return { scopes: grants, id: id, error: `` };
2145
1982
  }
2146
1983
  catch (error: any) {
2147
- debugger;
2148
1984
  console.log(error);
2149
1985
  return { scopes: null, id: null, error: `Exception: ${error}` };
2150
1986
  }
2151
1987
  }
2152
1988
  export async function userDelegatedScopesRemove(instance: IPublicClientApplication, loggedInUser: User, tenant: Tenant, scope: string): Promise<boolean> {
2153
- // need a logged in user and valid tenant to query graph
2154
- if (loggedInUser == null || loggedInUser.spacode == "" || tenant == null) {
2155
- debugger;
1989
+ if (loggedInUser == null || tenant == null) {
2156
1990
  return false;
2157
1991
  }
2158
1992
  // get current set of delegated scopes in order to remove passed scope
2159
1993
  let { scopes, id, error } = await userDelegatedScopesGet(instance, loggedInUser, tenant);
2160
1994
  if (error != "") {
2161
- debugger;
2162
1995
  console.log(`userDelegatedScopesRemove: cannot find userDelegatedScopes for ${loggedInUser.mail}: ${error}`);
2163
1996
  return false;
2164
1997
  }
2165
1998
  // remove passed scope (case sensitive)
2166
1999
  scopes = scopes!.replace(scope, "");
2167
- // set updated oauth2permissions
2168
- let removed: boolean = await oauth2PermissionGrantsSet(instance, loggedInUser, id!, scopes);
2000
+ // set updated oauth2permissions using secure backend API
2001
+ let removed: boolean = await secureApiClient.oauth2PermissionGrantsSet(id!, scopes);
2169
2002
  if (!removed) {
2170
- debugger;
2171
- console.log(`userDelegatedScopesRemove: cannot set oauth2PermissionGrants for ${loggedInUser.mail}: ${error}`);
2003
+ console.log(`userDelegatedScopesRemove: cannot set oauth2PermissionGrants for ${loggedInUser.mail}`);
2172
2004
  return false;
2173
2005
  }
2174
2006
  // replace scope array on logged in user
@@ -2176,33 +2008,8 @@ export async function userDelegatedScopesRemove(instance: IPublicClientApplicati
2176
2008
  return removed;
2177
2009
  }
2178
2010
  //usersGet - GET from AAD Users endpoint
2179
- export async function usersGet(instance: IPublicClientApplication, user: User | undefined): Promise<{ users: string[], error: string }> {
2180
- // need a logged in user to get graph users
2181
- if (user == null || user.spacode == "") {
2182
- return { users: [], error: `500: invalid user passed to usersGet` };
2183
- }
2184
- // make /users endpoint call
2185
- try {
2186
- // create headers
2187
- const headers = await graphDefineHeaders(instance, user);
2188
- let options = { method: "GET", headers: headers };
2189
- let usersEndpoint = getGraphEndpoint(user.authority);
2190
- usersEndpoint += graphConfig.graphUsersPredicate;
2191
- let response = await fetch(usersEndpoint, options);
2192
- let data = await response.json();
2193
- if (typeof data.error !== "undefined") {
2194
- return { users: [], error: `${data.error.code}: ${data.error.message}` };
2195
- }
2196
- let users = new Array<string>();
2197
- for (let user of data.value) {
2198
- users.push(user.mail);
2199
- }
2200
- return { users: users, error: `` };
2201
- }
2202
- catch (error: any) {
2203
- console.log(error);
2204
- return { users: [], error: `Exception: ${error}` };
2205
- }
2011
+ export async function usersGet(_instance: IPublicClientApplication, _user: User | undefined, searchString?: string): Promise<{ users: string[], error: string }> {
2012
+ return await secureApiClient.usersGet(searchString);
2206
2013
  }
2207
2014
  // ======================= Mindline SyncConfig API ===============================
2208
2015
  export async function auditConfigAdd(instance: IPublicClientApplication, user: User, ac: AuditConfig, debug: boolean): Promise<APIResult> {
@@ -2670,147 +2477,28 @@ export async function getPowerBIAccessToken(
2670
2477
  return accesstoken;
2671
2478
  }
2672
2479
  // ======================= Azure REST API ===============================
2673
- // TODO: this is where you want to trigger a re-authentication if token expires
2674
- async function azureDefineHeaders(
2675
- instance: IPublicClientApplication,
2676
- user: User
2677
- ): Promise<Headers> {
2678
- const headers = new Headers();
2679
- headers.append("Content-Type", "application/json");
2680
- headers.append("accept", "*/*");
2681
- // authorization header - if needed, retrieve and cache access token
2682
- if (user.azureAccessToken == null || user.azureAccessToken === "") {
2683
- try {
2684
- let accounts: AccountInfo[] = instance.getAllAccounts();
2685
- let homeAccountId = user.oid + "." + user.tid;
2686
- let account: AccountInfo | null = null;
2687
- for (let i: number = 0; i < accounts.length; i++) {
2688
- if (accounts[i].homeAccountId == homeAccountId) {
2689
- account = accounts[i];
2690
- }
2691
- }
2692
- let response: AuthenticationResult = await instance.acquireTokenSilent({
2693
- scopes: ["https://management.azure.com/user_impersonation"],
2694
- account: account!
2695
- });
2696
- user.azureAccessToken = response.accessToken; // cache access token
2697
- console.log("Front end token acquired silently: " + user.azureAccessToken.slice(0, 20));
2698
- }
2699
- catch (error: any) {
2700
- try {
2701
- console.log("Front end token silent acquisition failure: " + error);
2702
- // fallback to redirect if silent acquisition fails
2703
- let accounts: AccountInfo[] = instance.getAllAccounts();
2704
- let homeAccountId = user.oid + "." + user.tid;
2705
- let account: AccountInfo | null = null;
2706
- for (let i: number = 0; i < accounts.length; i++) {
2707
- if (accounts[i].homeAccountId == homeAccountId) {
2708
- account = accounts[i];
2709
- }
2710
- }
2711
- instance.acquireTokenRedirect({
2712
- scopes: ["https://management.azure.com/user_impersonation"],
2713
- account: account!
2714
- });
2715
- }
2716
- catch (error: any) {
2717
- console.log("Front end token popup acquisition failure: " + error);
2718
- }
2719
- }
2720
- }
2721
- headers.append("Authorization", `Bearer ${user.azureAccessToken}`);
2722
- return headers;
2480
+ export async function canListRootAssignments(_instance: IPublicClientApplication, _user: User): Promise<boolean> {
2481
+ return await secureApiClient.canListRootAssignments();
2723
2482
  }
2724
- export async function canListRootAssignments(instance: IPublicClientApplication, user: User): Promise<boolean> {
2725
- // need a logged in user to call Azure REST API
2726
- if (user == null || user.spacode == "") {
2727
- return false;
2728
- }
2729
- // make Azure REST API call
2730
- try {
2731
- // create headers
2732
- const headers = await azureDefineHeaders(instance, user);
2733
- let options = { method: "GET", headers: headers };
2734
- let listrootassignmentsEndpoint: string = azureConfig.azureListRootAssignments;
2735
- listrootassignmentsEndpoint += "'";
2736
- listrootassignmentsEndpoint += user.oid;
2737
- listrootassignmentsEndpoint += "'";
2738
- let response = await fetch(listrootassignmentsEndpoint, options);
2739
- if (response.status == 200) {
2740
- let data: any = await response.json();
2741
- data = data;
2742
- debugger;
2743
- console.log("Successful call to Azure Resource Graph list root assignments");
2744
- }
2745
- else {
2746
- console.log(await processErrors(response));
2747
- return false;
2748
- }
2749
- }
2750
- catch (error: any) {
2751
- console.log(error);
2752
- return false;
2753
- }
2754
- return true;
2755
- }
2756
- export async function elevateGlobalAdminToUserAccessAdmin(instance: IPublicClientApplication, user: User): Promise<boolean> {
2757
- // need a logged in user to call Azure REST API
2758
- if (user == null || user.spacode == "") {
2759
- return false;
2760
- }
2761
- // make Azure REST API call
2762
- try {
2763
- // create headers
2764
- const headers = await azureDefineHeaders(instance, user);
2765
- let options = { method: "POST", headers: headers };
2766
- let elevateaccessEndpoint: string = azureConfig.azureElevateAccess;
2767
- let response = await fetch(elevateaccessEndpoint, options);
2768
- if (response.status == 200) {
2769
- console.log("Successful call to Azure Resource Graph list root assignments");
2770
- }
2771
- else {
2772
- console.log(await processErrors(response));
2773
- return false;
2774
- }
2775
- }
2776
- catch (error: any) {
2777
- console.log(error);
2778
- return false;
2779
- }
2780
- return true;
2483
+ export async function elevateGlobalAdminToUserAccessAdmin(_instance: IPublicClientApplication, _user: User): Promise<boolean> {
2484
+ return await secureApiClient.elevateGlobalAdminToUserAccessAdmin();
2781
2485
  }
2782
- async function readResources(instance: IPublicClientApplication, user: User): Promise<ResourceNode[]> {
2783
- // need a logged in user to call Azure REST API
2784
- let resources: ResourceNode[] = new Array<ResourceNode>();
2785
- if (user == null || user.spacode == "") {
2786
- return resources;
2787
- }
2788
- // make Azure REST API call
2486
+ async function readResources(_instance: IPublicClientApplication, _user: User): Promise<ResourceNode[]> {
2789
2487
  try {
2790
- // create headers
2791
- const headers = await azureDefineHeaders(instance, user);
2792
- let options = { method: "GET", headers: headers };
2793
- let listrootassignmentsEndpoint: string = azureConfig.azureListRootAssignments;
2794
- listrootassignmentsEndpoint += "'";
2795
- listrootassignmentsEndpoint += user.oid;
2796
- listrootassignmentsEndpoint += "'";
2797
- let response = await fetch(listrootassignmentsEndpoint, options);
2798
- if (response.status == 200) {
2799
- let data = await response.json();
2800
- data = data;
2801
- debugger;
2802
- console.log("Successful call to Azure Resource Graph list root assignments");
2803
- }
2804
- else {
2805
- console.log(await processErrors(response));
2806
- return resources;
2807
- }
2808
- }
2809
- catch (error: any) {
2810
- console.log(error);
2811
- return resources;
2488
+ const backendResources = await secureApiClient.readResources();
2489
+
2490
+ // Convert backend Resource[] to ResourceNode[]
2491
+ // Note: The original implementation returned empty array, so we map basic fields
2492
+ const resourceNodes: ResourceNode[] = backendResources.map(r => {
2493
+ const node = new ResourceNode(r.type, r.name, 0); // cost not available from backend
2494
+ return node;
2495
+ });
2496
+
2497
+ return resourceNodes;
2498
+ } catch (error: any) {
2499
+ console.error('Error in readResources:', error);
2500
+ return [];
2812
2501
  }
2813
- return resources;
2814
2502
  }
2815
2503
  // ======================= HYBRIDSPA.TS -- Mindline SyncConfig API helper functions ===============================
2816
2504
  function getAPIScope(user: User): string {