@mindline/sync 1.0.56 → 1.0.58
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/.vs/VSWorkspaceState.json +0 -1
- package/.vs/slnx.sqlite +0 -0
- package/.vs/sync/FileContentIndex/e6f486c6-def7-4720-b195-014eeb787959.vsidx +0 -0
- package/.vs/sync/v17/.wsuo +0 -0
- package/index.d.ts +12 -4
- package/index.ts +188 -57
- package/package.json +1 -1
- package/tenants.json +2 -1
- package/users.json +1 -2
- package/users2.json +0 -6
- package/.vs/sync/FileContentIndex/41496172-9f1a-4c1e-8416-5afcea77e5af.vsidx +0 -0
package/.vs/slnx.sqlite
CHANGED
|
Binary file
|
package/.vs/sync/v17/.wsuo
CHANGED
|
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
|
|
@@ -223,6 +226,7 @@ declare module "@mindline/sync" {
|
|
|
223
226
|
pb_increment: number;
|
|
224
227
|
pb_idle: number;
|
|
225
228
|
pb_idleMax: number;
|
|
229
|
+
pb_total: number;
|
|
226
230
|
pb_timer: NodeJS.Timer;
|
|
227
231
|
milestoneArray: MilestoneArray;
|
|
228
232
|
constructor(config: Config|null, syncPortalGlobalState: InitInfo|null, bClearLocalStorage: boolean);
|
|
@@ -262,14 +266,18 @@ declare module "@mindline/sync" {
|
|
|
262
266
|
//
|
|
263
267
|
// Azure AD Graph API
|
|
264
268
|
//
|
|
265
|
-
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 }>;
|
|
266
273
|
export function signIn(user: User, tasks: TaskArray): boolean;
|
|
267
274
|
export function signInIncrementally(user: User, scope: string): void;
|
|
268
|
-
export function requestAdminConsent(user: User, scope: string): void;
|
|
269
275
|
export function signOut(user: User): boolean;
|
|
270
276
|
export function tenantRelationshipsGetByDomain(loggedInuser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): boolean;
|
|
271
277
|
export function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): boolean;
|
|
272
|
-
export function tenantUnauthenticatedLookup(tenant: Tenant, debug: 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;
|
|
273
281
|
export function usersGet(instance: IPublicClientApplication, user: User | undefined): { users: string[], error: string };
|
|
274
282
|
//
|
|
275
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;
|
|
@@ -774,6 +776,7 @@ export class BatchArray {
|
|
|
774
776
|
pb_increment: number;
|
|
775
777
|
pb_idle: number;
|
|
776
778
|
pb_idleMax: number;
|
|
779
|
+
pb_total: number;
|
|
777
780
|
pb_timer: NodeJS.Timer;
|
|
778
781
|
milestoneArray: MilestoneArray;
|
|
779
782
|
constructor(
|
|
@@ -789,6 +792,7 @@ export class BatchArray {
|
|
|
789
792
|
this.pb_timer = null;
|
|
790
793
|
this.pb_idle = 0;
|
|
791
794
|
this.pb_idleMax = 0;
|
|
795
|
+
this.pb_total = 0;
|
|
792
796
|
this.milestoneArray = new MilestoneArray(false);
|
|
793
797
|
}
|
|
794
798
|
// populate tenantNodes based on config tenants
|
|
@@ -878,7 +882,7 @@ export class BatchArray {
|
|
|
878
882
|
this.pb_increment = .25;
|
|
879
883
|
this.pb_idle = 0;
|
|
880
884
|
this.pb_idleMax = 0;
|
|
881
|
-
|
|
885
|
+
this.pb_total = 0;
|
|
882
886
|
this.pb_timer = setInterval(() => {
|
|
883
887
|
// if signalR has finished the sync, stop the timer
|
|
884
888
|
if (this.milestoneArray.milestones[0].Write != null) {
|
|
@@ -890,15 +894,14 @@ export class BatchArray {
|
|
|
890
894
|
}
|
|
891
895
|
else {
|
|
892
896
|
// if we've gone 60 seconds without a signalR message, finish the sync
|
|
897
|
+
this.pb_total = this.pb_total + 1;
|
|
893
898
|
this.pb_idle = this.pb_idle + 1;
|
|
894
899
|
this.pb_idleMax = Math.max(this.pb_idle, this.pb_idleMax);
|
|
895
|
-
setIdleText(
|
|
900
|
+
setIdleText(`${this.pb_total} seconds elapsed. Last update ${this.pb_idle} seconds ago. [max idle: ${this.pb_idleMax}/60]`);
|
|
896
901
|
if (this.pb_idle >= 60) {
|
|
897
|
-
clearInterval(this.pb_timer);
|
|
898
|
-
this.pb_timer = null;
|
|
899
902
|
if (this.milestoneArray.milestones[0].Write == null) {
|
|
900
903
|
this.milestoneArray.write(setMilestones);
|
|
901
|
-
setConfigSyncResult(`
|
|
904
|
+
setConfigSyncResult(`sync continuing, but no update for ${this.pb_idle} seconds`);
|
|
902
905
|
}
|
|
903
906
|
}
|
|
904
907
|
// if we get to 100, the progress bar stops but SignalR or countdown timer completes the sync
|
|
@@ -1195,7 +1198,6 @@ export class TenantNode {
|
|
|
1195
1198
|
//
|
|
1196
1199
|
// Azure AD Graph API
|
|
1197
1200
|
//
|
|
1198
|
-
//groupsGet - GET /groups
|
|
1199
1201
|
export async function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
|
|
1200
1202
|
// need a logged in user to get graph users
|
|
1201
1203
|
if (user == null || user.spacode == "") {
|
|
@@ -1219,10 +1221,106 @@ export async function groupsGet(instance: IPublicClientApplication, user: User |
|
|
|
1219
1221
|
return { groups: [], error: `Exception: ${error}` };
|
|
1220
1222
|
}
|
|
1221
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
|
+
}
|
|
1222
1321
|
export async function signIn(user: User, tasks: TaskArray): Promise<boolean> {
|
|
1223
1322
|
// admin authority is blank at signIn, lookup authority real-time
|
|
1224
1323
|
if (user.authority == "") {
|
|
1225
|
-
debugger;
|
|
1226
1324
|
// lookup authority for this user (the lookup call does it based on domain, but TID works as well to find authority)
|
|
1227
1325
|
let tenant: Tenant = new Tenant();
|
|
1228
1326
|
tenant.domain = user.tid;
|
|
@@ -1239,7 +1337,7 @@ export async function signIn(user: User, tasks: TaskArray): Promise<boolean> {
|
|
|
1239
1337
|
}
|
|
1240
1338
|
switch (user.authority) {
|
|
1241
1339
|
case graphConfig.authorityWW:
|
|
1242
|
-
// SignIn by an admin consents the app, Challenge adds incremental permissions
|
|
1340
|
+
// SignIn by an admin consents the app, Challenge adds incremental permissions dynamically, but requires a consented app
|
|
1243
1341
|
let tenantURL: string = window.location.href;
|
|
1244
1342
|
tenantURL += "MicrosoftIdentity/Account/SignIn";
|
|
1245
1343
|
let url: URL = new URL(tenantURL);
|
|
@@ -1289,32 +1387,6 @@ export function signInIncrementally(user: User, scope: string): void {
|
|
|
1289
1387
|
url.searchParams.append("loginHint", user.mail);
|
|
1290
1388
|
window.location.assign(url.href);
|
|
1291
1389
|
}
|
|
1292
|
-
export function requestAdminConsent(user: User, scope: string): void {
|
|
1293
|
-
if (user.oid == "1") return;
|
|
1294
|
-
//
|
|
1295
|
-
// for app permissions (app roles) we must use the /.default scope for admin consent
|
|
1296
|
-
// 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.
|
|
1297
|
-
// https://learn.microsoft.com/en-us/answers/questions/431784/how-to-grant-application-permissions-with-dynamic
|
|
1298
|
-
//
|
|
1299
|
-
// this means that, if we want to be granular about SyncReader vs. SyncWriter permissions, we must have separate Applications
|
|
1300
|
-
// for now, we request the /.default scope whenever any of the SyncReader or SyncWriter permissions are requested
|
|
1301
|
-
//
|
|
1302
|
-
// trying to use the Challenge endpoint for app permissions, setting this scope caused the call to quietly fail without error
|
|
1303
|
-
// scope = "63100afe-506e-4bb2-8ff7-d8d5ab373129/.default";
|
|
1304
|
-
//
|
|
1305
|
-
// 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
|
|
1306
|
-
// https://learn.microsoft.com/EN-US/azure/active-directory/develop/scopes-oidc#client-credentials-grant-flow-and-default
|
|
1307
|
-
// https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#request-the-permissions-from-a-directory-admin
|
|
1308
|
-
//
|
|
1309
|
-
let adminConsentURL: string = "https://login.microsoftonline.com/";
|
|
1310
|
-
adminConsentURL += user.tid;
|
|
1311
|
-
adminConsentURL += "/adminconsent";
|
|
1312
|
-
let url: URL = new URL(adminConsentURL);
|
|
1313
|
-
url.searchParams.append("client_id", "63100afe-506e-4bb2-8ff7-d8d5ab373129");
|
|
1314
|
-
url.searchParams.append("redirect_uri", window.location.origin);
|
|
1315
|
-
url.searchParams.append("domain_hint", user.companyDomain);
|
|
1316
|
-
window.location.assign(url.href);
|
|
1317
|
-
}
|
|
1318
1390
|
export async function signOut(user: User): Promise<boolean>{
|
|
1319
1391
|
if (user.oid == "1") return false;
|
|
1320
1392
|
// set logout_hint in the .NET session for streamlined logout
|
|
@@ -1353,10 +1425,10 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
|
|
|
1353
1425
|
try {
|
|
1354
1426
|
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
|
|
1355
1427
|
loggedInUser.accessToken = response.accessToken; // cache access token on the user
|
|
1356
|
-
console.log("Front end token acquired: " + loggedInUser.accessToken.slice(0, 20));
|
|
1428
|
+
console.log("tenantRelationshipsGetByDomain: Front end token acquired: " + loggedInUser.accessToken.slice(0, 20));
|
|
1357
1429
|
}
|
|
1358
1430
|
catch (error: any) {
|
|
1359
|
-
console.log("Front end token failure: " + error);
|
|
1431
|
+
console.log("tenantRelationshipsGetByDomain: Front end token failure: " + error);
|
|
1360
1432
|
return false; // failed to get access token, no need to re-render
|
|
1361
1433
|
}
|
|
1362
1434
|
}
|
|
@@ -1372,37 +1444,38 @@ export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant:
|
|
|
1372
1444
|
tenantEndpoint += "(domainName='";
|
|
1373
1445
|
tenantEndpoint += tenant.domain;
|
|
1374
1446
|
tenantEndpoint += "')";
|
|
1375
|
-
console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
|
|
1447
|
+
console.log("tenantRelationshipsGetByDomain: Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
|
|
1376
1448
|
let response = await fetch(tenantEndpoint, options);
|
|
1377
1449
|
if (response.status == 200 && response.statusText == "OK") {
|
|
1378
1450
|
let data = await response.json();
|
|
1379
1451
|
if (data) {
|
|
1380
1452
|
if (data.error != null) {
|
|
1381
1453
|
debugger;
|
|
1382
|
-
console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
|
|
1454
|
+
console.log("tenantRelationshipsGetByDomain: Failed GET from /findTenantInformationByDomainName: ", data.error.message);
|
|
1383
1455
|
return false;
|
|
1384
1456
|
}
|
|
1385
1457
|
else if (data.displayName != null && data.displayName !== "") {
|
|
1386
1458
|
// set domain information on passed tenant
|
|
1387
1459
|
tenant.tid = data.tenantId;
|
|
1388
1460
|
tenant.name = data.displayName;
|
|
1389
|
-
console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
|
|
1461
|
+
console.log("tenantRelationshipsGetByDomain: Successful GET from /findTenantInformationByDomainName: ", data.displayName);
|
|
1390
1462
|
return true; // success, need UX to re-render
|
|
1391
1463
|
}
|
|
1392
1464
|
}
|
|
1393
1465
|
else {
|
|
1394
|
-
console.log("Failed to GET from /
|
|
1466
|
+
console.log("tenantRelationshipsGetByDomain: Failed to GET from /findTenantInformationByDomainName: ", tenantEndpoint);
|
|
1395
1467
|
}
|
|
1396
1468
|
}
|
|
1397
1469
|
}
|
|
1398
1470
|
catch (error: any) {
|
|
1399
|
-
console.log("Failed to GET from /
|
|
1471
|
+
console.log("Failed to GET from /findTenantInformationByDomainName: ", error);
|
|
1400
1472
|
return false; // failed, no need for UX to re-render
|
|
1401
1473
|
}
|
|
1402
1474
|
return false; // failed, no need for UX to re-render
|
|
1403
1475
|
}
|
|
1404
1476
|
//tenantRelationshipsGetById - query AAD for associated company name and domain
|
|
1405
1477
|
export async function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): Promise<boolean> {
|
|
1478
|
+
console.log("**** tenantRelationshipsGetById");
|
|
1406
1479
|
if (debug) debugger;
|
|
1407
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
|
|
1408
1481
|
// if (user.companyName != "") return false;
|
|
@@ -1411,10 +1484,10 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
1411
1484
|
try {
|
|
1412
1485
|
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
|
|
1413
1486
|
user.accessToken = response.accessToken; // cache access token
|
|
1414
|
-
console.log("Front end token acquired: " + user.accessToken.slice(0, 20));
|
|
1487
|
+
console.log("tenantRelationshipsGetById: Front end token acquired: " + user.accessToken.slice(0, 20));
|
|
1415
1488
|
}
|
|
1416
1489
|
catch (error: any) {
|
|
1417
|
-
console.log("Front end token failure: " + error);
|
|
1490
|
+
console.log("tenantRelationshipsGetById: Front end token failure: " + error);
|
|
1418
1491
|
return false; // failed to get access token, no need to re-render
|
|
1419
1492
|
}
|
|
1420
1493
|
}
|
|
@@ -1432,7 +1505,7 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
1432
1505
|
tenantEndpoint += "')";
|
|
1433
1506
|
// track time of tenant details query
|
|
1434
1507
|
tasks.setTaskStart("GET tenant details", new Date());
|
|
1435
|
-
console.log("Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
|
|
1508
|
+
console.log("tenantRelationshipsGetById: Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
|
|
1436
1509
|
let response = await fetch(tenantEndpoint, options);
|
|
1437
1510
|
let data = await response.json();
|
|
1438
1511
|
if (data && typeof data.displayName !== undefined && data.displayName !== "") {
|
|
@@ -1449,16 +1522,16 @@ export async function tenantRelationshipsGetById(user: User, ii: InitInfo, insta
|
|
|
1449
1522
|
console.log("tenantRelationshipsGetById: missing associated tenant for logged in user.");
|
|
1450
1523
|
debugger;
|
|
1451
1524
|
}
|
|
1452
|
-
console.log("Successful GET from /findTenantInformationByTenantId: ", data.displayName);
|
|
1525
|
+
console.log("tenantRelationshipsGetById: Successful GET from /findTenantInformationByTenantId: ", data.displayName);
|
|
1453
1526
|
tasks.setTaskEnd("GET tenant details", new Date(), "complete");
|
|
1454
1527
|
return true; // success, need UX to re-render
|
|
1455
1528
|
}
|
|
1456
1529
|
else {
|
|
1457
|
-
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
1530
|
+
console.log("tenantRelationshipsGetById: Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
1458
1531
|
}
|
|
1459
1532
|
}
|
|
1460
1533
|
catch (error: any) {
|
|
1461
|
-
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
1534
|
+
console.log("tenantRelationshipsGetById: Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
1462
1535
|
tasks.setTaskEnd("GET tenant details", new Date(), "failed");
|
|
1463
1536
|
return false; // failed, no need for UX to re-render
|
|
1464
1537
|
}
|
|
@@ -1514,17 +1587,76 @@ export async function tenantUnauthenticatedLookup(tenant: Tenant, debug: boolean
|
|
|
1514
1587
|
}
|
|
1515
1588
|
return false; // failed, no need for UX to re-render
|
|
1516
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
|
+
}
|
|
1517
1649
|
//usersGet - GET from AAD Users endpoint
|
|
1518
1650
|
export async function usersGet(instance: IPublicClientApplication, user: User | undefined): Promise<{ users: string[], error: string }> {
|
|
1519
1651
|
// need a logged in user to get graph users
|
|
1520
1652
|
if (user == null || user.spacode == "") {
|
|
1521
1653
|
return { users: [], error: `500: invalid user passed to groupsGet` };
|
|
1522
1654
|
}
|
|
1523
|
-
// create headers
|
|
1524
|
-
const headers = await defineHeaders(instance, user);
|
|
1525
|
-
let options = { method: "GET", headers: headers };
|
|
1526
1655
|
// make /users endpoint call
|
|
1527
1656
|
try {
|
|
1657
|
+
// create headers
|
|
1658
|
+
const headers = await defineHeaders(instance, user);
|
|
1659
|
+
let options = { method: "GET", headers: headers };
|
|
1528
1660
|
let response = await fetch(graphConfig.graphUsersEndpoint, options);
|
|
1529
1661
|
let data = await response.json();
|
|
1530
1662
|
if (typeof data.error !== "undefined") {
|
|
@@ -1541,9 +1673,7 @@ export async function usersGet(instance: IPublicClientApplication, user: User |
|
|
|
1541
1673
|
return { users: [], error: `Exception: ${error}` };
|
|
1542
1674
|
}
|
|
1543
1675
|
}
|
|
1544
|
-
//
|
|
1545
|
-
// Mindline Config API
|
|
1546
|
-
//
|
|
1676
|
+
// ======================= Mindline Config API ===============================
|
|
1547
1677
|
export async function configEdit(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
1548
1678
|
let result: APIResult = new APIResult();
|
|
1549
1679
|
if (config.id === "1") {
|
|
@@ -1587,6 +1717,7 @@ export async function configsRefresh(instance: IPublicClientApplication, authori
|
|
|
1587
1717
|
}
|
|
1588
1718
|
// retrieve Workspace(s), User(s), Tenant(s), Config(s) given newly logged in user
|
|
1589
1719
|
export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult> {
|
|
1720
|
+
console.log(`>>>>>> initGet`);
|
|
1590
1721
|
let result: APIResult = new APIResult();
|
|
1591
1722
|
if (debug) debugger;
|
|
1592
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.
|
|
4
|
+
"version": "1.0.58",
|
|
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
package/users.json
CHANGED
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
|
]
|
|
Binary file
|