@mindline/sync 1.0.68 → 1.0.69
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 +1 -1
- package/.vs/slnx.sqlite +0 -0
- package/.vs/sync/FileContentIndex/cef98ab2-81c6-461c-ae50-7acd79df52a4.vsidx +0 -0
- package/.vs/sync/v17/.wsuo +0 -0
- package/.vs/sync/v17/DocumentLayout.json +16 -32
- package/hybridspa.ts +82 -65
- package/index.d.ts +8 -10
- package/index.ts +89 -48
- package/package.json +1 -1
- package/tasks.ts +0 -40
- package/tenants.json +0 -2
- package/.vs/sync/FileContentIndex/1a29ea84-8786-44a4-bc7a-0a55b0219c6f.vsidx +0 -0
package/.vs/slnx.sqlite
CHANGED
|
Binary file
|
package/.vs/sync/v17/.wsuo
CHANGED
|
Binary file
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
"Version": 1,
|
|
3
3
|
"WorkspaceRootPath": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\",
|
|
4
4
|
"Documents": [
|
|
5
|
-
{
|
|
6
|
-
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
|
7
|
-
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
|
8
|
-
},
|
|
9
5
|
{
|
|
10
6
|
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\index.d.ts||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
|
|
11
7
|
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:index.d.ts||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
|
|
@@ -64,57 +60,45 @@
|
|
|
64
60
|
},
|
|
65
61
|
{
|
|
66
62
|
"DockedWidth": 200,
|
|
67
|
-
"SelectedChildIndex":
|
|
63
|
+
"SelectedChildIndex": 2,
|
|
68
64
|
"Children": [
|
|
69
65
|
{
|
|
70
66
|
"$type": "Document",
|
|
71
|
-
"DocumentIndex":
|
|
72
|
-
"Title": "
|
|
73
|
-
"DocumentMoniker": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\
|
|
74
|
-
"RelativeDocumentMoniker": "
|
|
75
|
-
"ToolTip": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\
|
|
76
|
-
"RelativeToolTip": "
|
|
77
|
-
"ViewState": "
|
|
78
|
-
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.
|
|
79
|
-
"WhenOpened": "
|
|
80
|
-
"EditorCaption": ""
|
|
67
|
+
"DocumentIndex": 2,
|
|
68
|
+
"Title": "hybridspa.ts",
|
|
69
|
+
"DocumentMoniker": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\hybridspa.ts",
|
|
70
|
+
"RelativeDocumentMoniker": "hybridspa.ts",
|
|
71
|
+
"ToolTip": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\hybridspa.ts",
|
|
72
|
+
"RelativeToolTip": "hybridspa.ts",
|
|
73
|
+
"ViewState": "AQIAAO8AAAAAAAAAAAAuwAUBAAAAAAAA",
|
|
74
|
+
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
|
|
75
|
+
"WhenOpened": "2023-11-25T02:18:34.862Z"
|
|
81
76
|
},
|
|
82
77
|
{
|
|
83
78
|
"$type": "Document",
|
|
84
|
-
"DocumentIndex":
|
|
79
|
+
"DocumentIndex": 1,
|
|
85
80
|
"Title": "index.ts",
|
|
86
81
|
"DocumentMoniker": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\index.ts",
|
|
87
82
|
"RelativeDocumentMoniker": "index.ts",
|
|
88
83
|
"ToolTip": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\index.ts",
|
|
89
84
|
"RelativeToolTip": "index.ts",
|
|
90
|
-
"ViewState": "
|
|
85
|
+
"ViewState": "AQIAAEMEAAAAAAAAAAAjwGYEAAARAAAA",
|
|
91
86
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
|
|
92
|
-
"WhenOpened": "2023-12-05T02:10:16.04Z"
|
|
87
|
+
"WhenOpened": "2023-12-05T02:10:16.04Z",
|
|
88
|
+
"EditorCaption": ""
|
|
93
89
|
},
|
|
94
90
|
{
|
|
95
91
|
"$type": "Document",
|
|
96
|
-
"DocumentIndex":
|
|
92
|
+
"DocumentIndex": 0,
|
|
97
93
|
"Title": "index.d.ts",
|
|
98
94
|
"DocumentMoniker": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\index.d.ts",
|
|
99
95
|
"RelativeDocumentMoniker": "index.d.ts",
|
|
100
96
|
"ToolTip": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\index.d.ts",
|
|
101
97
|
"RelativeToolTip": "index.d.ts",
|
|
102
|
-
"ViewState": "
|
|
98
|
+
"ViewState": "AQIAANwAAAAAAAAAAAAAAO4AAAAZAAAA",
|
|
103
99
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
|
|
104
100
|
"WhenOpened": "2024-01-01T16:19:43.325Z",
|
|
105
101
|
"EditorCaption": ""
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"$type": "Document",
|
|
109
|
-
"DocumentIndex": 3,
|
|
110
|
-
"Title": "hybridspa.ts",
|
|
111
|
-
"DocumentMoniker": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\hybridspa.ts",
|
|
112
|
-
"RelativeDocumentMoniker": "hybridspa.ts",
|
|
113
|
-
"ToolTip": "C:\\Users\\ArvindSuthar\\source\\repos\\front\\sync\\hybridspa.ts",
|
|
114
|
-
"RelativeToolTip": "hybridspa.ts",
|
|
115
|
-
"ViewState": "AQIAAB8AAAAAAAAAAAAGwFUAAAAEAAAA",
|
|
116
|
-
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
|
|
117
|
-
"WhenOpened": "2023-11-25T02:18:34.862Z"
|
|
118
102
|
}
|
|
119
103
|
]
|
|
120
104
|
},
|
package/hybridspa.ts
CHANGED
|
@@ -217,6 +217,88 @@ export async function adminPost(
|
|
|
217
217
|
}
|
|
218
218
|
return result;
|
|
219
219
|
}
|
|
220
|
+
//configConsentReadPut
|
|
221
|
+
export async function configConsentReadPut(instance: IPublicClientApplication, authorizedUser: User, configId: string, tid: string, consent: boolean): Promise<APIResult> {
|
|
222
|
+
let result: APIResult = new APIResult();
|
|
223
|
+
// create parameterized config consent endpoint
|
|
224
|
+
let endpoint: string = mindlineConfig.configConsentEndpoint();
|
|
225
|
+
let url: URL = new URL(endpoint);
|
|
226
|
+
url.searchParams.append("configurationId", configId);
|
|
227
|
+
// create headers
|
|
228
|
+
const headers = await defineHeaders(instance, authorizedUser);
|
|
229
|
+
// create body
|
|
230
|
+
let configConsentReadBody: string = `
|
|
231
|
+
{
|
|
232
|
+
"tenantId": "${tid}",
|
|
233
|
+
"isReadPermissionConsented": ${consent ? "true" : "false"}
|
|
234
|
+
}`;
|
|
235
|
+
// make endpoint call
|
|
236
|
+
let options = { method: "PUT", headers: headers, body: configConsentReadBody };
|
|
237
|
+
try {
|
|
238
|
+
console.log("Attempting PUT read consent to /configuration/consent: " + url.href);
|
|
239
|
+
let response = await fetch(url.href, options);
|
|
240
|
+
if (response.status === 200 && response.statusText === "OK") {
|
|
241
|
+
console.log(`Successful PUT to ${url.href}`);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
result.error = await processErrors(response);
|
|
246
|
+
console.log(`Failed PUT to ${url.href}`);
|
|
247
|
+
console.log(result.error);
|
|
248
|
+
result.status = 500;
|
|
249
|
+
result.result = false;
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error: any) {
|
|
254
|
+
result.error = error.message;
|
|
255
|
+
result.status = 500;
|
|
256
|
+
result.result = false;
|
|
257
|
+
console.log(error.message);
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
//configConsentWritePut
|
|
262
|
+
export async function configConsentWritePut(instance: IPublicClientApplication, authorizedUser: User, configId: string, tid: string, consent: boolean): Promise<APIResult> {
|
|
263
|
+
let result: APIResult = new APIResult();
|
|
264
|
+
// create parameterized config consent endpoint
|
|
265
|
+
let endpoint: string = mindlineConfig.configConsentEndpoint();
|
|
266
|
+
let url: URL = new URL(endpoint);
|
|
267
|
+
url.searchParams.append("configurationId", configId);
|
|
268
|
+
// create headers
|
|
269
|
+
const headers = await defineHeaders(instance, authorizedUser);
|
|
270
|
+
// create body
|
|
271
|
+
let configConsentWriteBody: string = `
|
|
272
|
+
{
|
|
273
|
+
"tenantId": "${tid}",
|
|
274
|
+
"isWritePermissionConsented": ${consent ? "true" : "false"}
|
|
275
|
+
}`;
|
|
276
|
+
// make endpoint call
|
|
277
|
+
let options = { method: "PUT", headers: headers, body: configConsentWriteBody };
|
|
278
|
+
try {
|
|
279
|
+
console.log("Attempting PUT read consent to /configuration/consent: " + url.href);
|
|
280
|
+
let response = await fetch(url.href, options);
|
|
281
|
+
if (response.status === 200 && response.statusText === "OK") {
|
|
282
|
+
console.log(`Successful PUT to ${url.href}`);
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
result.error = await processErrors(response);
|
|
287
|
+
console.log(`Failed PUT to ${url.href}`);
|
|
288
|
+
console.log(result.error);
|
|
289
|
+
result.status = 500;
|
|
290
|
+
result.result = false;
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error: any) {
|
|
295
|
+
result.error = error.message;
|
|
296
|
+
result.status = 500;
|
|
297
|
+
result.result = false;
|
|
298
|
+
console.log(error.message);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
220
302
|
//configDelete
|
|
221
303
|
export async function configDelete(
|
|
222
304
|
instance: IPublicClientApplication,
|
|
@@ -696,71 +778,6 @@ export async function tenantPost(
|
|
|
696
778
|
}
|
|
697
779
|
return result;
|
|
698
780
|
}
|
|
699
|
-
//tenantPut: write access token for onboarded tenant
|
|
700
|
-
export async function tenantPut(
|
|
701
|
-
instance: IPublicClientApplication,
|
|
702
|
-
authorizedUser: User,
|
|
703
|
-
tenant: Tenant,
|
|
704
|
-
debug: boolean
|
|
705
|
-
): Promise<APIResult> {
|
|
706
|
-
let result: APIResult = new APIResult();
|
|
707
|
-
// we expect valid tid
|
|
708
|
-
if (tenant.tid === "") {
|
|
709
|
-
result.result = false;
|
|
710
|
-
result.error = "tenantPut: invalid tid";
|
|
711
|
-
result.status = 500;
|
|
712
|
-
return result;
|
|
713
|
-
}
|
|
714
|
-
// create tenant endpoint
|
|
715
|
-
let endpoint: string = mindlineConfig.tenantEndpoint();
|
|
716
|
-
// create tenant headers
|
|
717
|
-
const headers = await defineHeaders(instance, authorizedUser);
|
|
718
|
-
// establish read and write service principals ("notassigned" is default")
|
|
719
|
-
let readServicePrincipal: string = "null";
|
|
720
|
-
let writeServicePrincipal: string = "null";
|
|
721
|
-
if (tenant.permissionType == "write") {
|
|
722
|
-
writeServicePrincipal = `"1"`;
|
|
723
|
-
}
|
|
724
|
-
else if (tenant.permissionType == "read") {
|
|
725
|
-
readServicePrincipal = `"1"`;
|
|
726
|
-
}
|
|
727
|
-
// create tenant body
|
|
728
|
-
let tenantBody: string = `{"tenantId": "${tenant.tid}",
|
|
729
|
-
"name": "${tenant.name}",
|
|
730
|
-
"domain": "${tenant.domain}",
|
|
731
|
-
"type": "${tenant.tenantType}",
|
|
732
|
-
"permissionType": "${tenant.permissionType}",
|
|
733
|
-
"isOnboarded": ${tenant.onboarded == "true"},
|
|
734
|
-
"authority": "${tenant.authority}",
|
|
735
|
-
"readServicePrincipal": ${readServicePrincipal},
|
|
736
|
-
"writeServicePrincipal": ${writeServicePrincipal}}`;
|
|
737
|
-
let options = { method: "PUT", headers: headers, body: tenantBody };
|
|
738
|
-
// make tenant endpoint call
|
|
739
|
-
try {
|
|
740
|
-
if (debug) debugger;
|
|
741
|
-
console.log(`Attempting PUT to ${endpoint}`);
|
|
742
|
-
let response = await fetch(endpoint, options);
|
|
743
|
-
if (response.status === 200 && response.statusText === "OK") {
|
|
744
|
-
console.log(`Successful PUT to ${endpoint}`);
|
|
745
|
-
return result;
|
|
746
|
-
}
|
|
747
|
-
else {
|
|
748
|
-
console.log(`Failed PUT to ${endpoint}: ${tenantBody}`);
|
|
749
|
-
result.error = await processErrors(response);
|
|
750
|
-
console.log(result.error);
|
|
751
|
-
result.status = 500;
|
|
752
|
-
result.result = false;
|
|
753
|
-
return result;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
catch (error: any) {
|
|
757
|
-
result.error = error.message;
|
|
758
|
-
result.status = 500;
|
|
759
|
-
result.result = false;
|
|
760
|
-
console.log(result.error);
|
|
761
|
-
}
|
|
762
|
-
return result;
|
|
763
|
-
}
|
|
764
781
|
//workspacePut
|
|
765
782
|
export async function workspacePut(instance: IPublicClientApplication, authorizedUser: User, workspaceId: string, workspaceName: string): Promise<APIResult> {
|
|
766
783
|
let result: APIResult = new APIResult();
|
package/index.d.ts
CHANGED
|
@@ -99,8 +99,6 @@ declare module "@mindline/sync" {
|
|
|
99
99
|
name: string; // findTenantInformationByTenantId
|
|
100
100
|
domain: string; // findTenantInformationByTenantId
|
|
101
101
|
tenantType: TenantTypeStrings; // always "aad" for now
|
|
102
|
-
permissionType: TenantPermissionTypeStrings; // read/write/notassigned
|
|
103
|
-
onboarded: string; // have we onboarded this tenant? "true" or "false"
|
|
104
102
|
authority: string; // from AAD ID auth response
|
|
105
103
|
workspaceIDs: string;
|
|
106
104
|
sel: boolean; // selection state
|
|
@@ -126,6 +124,8 @@ declare module "@mindline/sync" {
|
|
|
126
124
|
usersWritten: number;
|
|
127
125
|
configId: string;
|
|
128
126
|
batchId: string;
|
|
127
|
+
isReadPermissionConsented: string;
|
|
128
|
+
isWritePermissionConsented: string;
|
|
129
129
|
}
|
|
130
130
|
export class Config {
|
|
131
131
|
id: string;
|
|
@@ -156,6 +156,8 @@ declare module "@mindline/sync" {
|
|
|
156
156
|
ts: Tenant[];
|
|
157
157
|
cs: Config[];
|
|
158
158
|
ws: Workspace[];
|
|
159
|
+
configlevelconsent_configid: string;
|
|
160
|
+
configlevelconsent_access: TenantConfigType;
|
|
159
161
|
constructor(bClearLocalStorage: boolean);
|
|
160
162
|
init(bClearLocalStorage: boolean): void;
|
|
161
163
|
save(): void;
|
|
@@ -166,12 +168,7 @@ declare module "@mindline/sync" {
|
|
|
166
168
|
"reload React" |
|
|
167
169
|
"GET tenant details" |
|
|
168
170
|
"POST config init" |
|
|
169
|
-
"GET workspaces"
|
|
170
|
-
"onboard tenant" |
|
|
171
|
-
"create 2nd tenant" |
|
|
172
|
-
"invite 2nd admin" |
|
|
173
|
-
"onboard 2nd tenant" |
|
|
174
|
-
"create config";
|
|
171
|
+
"GET workspaces";
|
|
175
172
|
export class TaskArray {
|
|
176
173
|
tasks: Task[];
|
|
177
174
|
constructor(bClearLocalStorage: boolean);
|
|
@@ -273,7 +270,7 @@ declare module "@mindline/sync" {
|
|
|
273
270
|
//
|
|
274
271
|
export function groupsGet(instance: IPublicClientApplication, user: User | undefined, groupSearchString: string): Promise<{ groups: Group[], error: string }>;
|
|
275
272
|
export function oauth2PermissionGrantsGet(options: RequestInit, user: User, spid: string, oid: string): Promise<{grants: string, error: string}>;
|
|
276
|
-
export function requestAdminConsent(
|
|
273
|
+
export function requestAdminConsent(admin: User, tct: TenantConfigType): void;
|
|
277
274
|
export function servicePrincipalGet(options: RequestInit, user: User, appid: string): Promise<{ spid: string, error: string }>;
|
|
278
275
|
export function signIn(user: User, tasks: TaskArray): boolean;
|
|
279
276
|
export function signInIncrementally(user: User, scope: string): void;
|
|
@@ -287,6 +284,8 @@ declare module "@mindline/sync" {
|
|
|
287
284
|
//
|
|
288
285
|
// Mindline Config API
|
|
289
286
|
//
|
|
287
|
+
export function configConsentForRead(instance: IPublicClientApplication, authorizedUser: User, configId: string, tid: string, consent: boolean): Promise<APIResult>;
|
|
288
|
+
export function configConsentForWrite(instance: IPublicClientApplication, authorizedUser: User, configId: string, tid: string, consent: boolean): Promise<APIResult>;
|
|
290
289
|
export function configEdit(
|
|
291
290
|
instance: IPublicClientApplication,
|
|
292
291
|
authorizedUser: User,
|
|
@@ -300,7 +299,6 @@ declare module "@mindline/sync" {
|
|
|
300
299
|
export function configsRefresh(instance: IPublicClientApplication, authorizedUser: User, workspaceId: string, ii: InitInfo, debug: boolean): APIResult;
|
|
301
300
|
export function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): APIResult;
|
|
302
301
|
export function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): APIResult;
|
|
303
|
-
export function tenantComplete(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, debug: boolean): APIResult;
|
|
304
302
|
export function tenantRemove(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string, debug: boolean): APIResult;
|
|
305
303
|
export function userAdd(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): APIResult;
|
|
306
304
|
export function userRemove(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): APIResult;
|
package/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as signalR from "@microsoft/signalr"
|
|
3
3
|
import { IPublicClientApplication, AuthenticationResult } from "@azure/msal-browser"
|
|
4
4
|
import { deserializeArray } from 'class-transformer';
|
|
5
|
-
import { defineHeaders, adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, initPost, readerPost,
|
|
5
|
+
import { defineHeaders, adminDelete, adminPost, adminsGet, configConsentReadPut, configConsentWritePut, configDelete, configsGet, configPost, configPut, initPost, readerPost, tenantPost, tenantDelete, tenantsGet, workspacePut, workspacesGet } from './hybridspa';
|
|
6
6
|
import { version } from './package.json';
|
|
7
7
|
import users from "./users.json";
|
|
8
8
|
import tenants from "./tenants.json";
|
|
@@ -40,6 +40,9 @@ export class mindlineConfig {
|
|
|
40
40
|
static adminsEndpoint(): string {
|
|
41
41
|
return `https://${mindlineConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/admins`;
|
|
42
42
|
};
|
|
43
|
+
static configConsentEndpoint(): string {
|
|
44
|
+
return `https://${mindlineConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/configuration/consent`;
|
|
45
|
+
};
|
|
43
46
|
static configEndpoint(): string {
|
|
44
47
|
return `https://${mindlineConfig.environmentTag}-configurationapi-westus.azurewebsites.net/api/v1/configuration`;
|
|
45
48
|
};
|
|
@@ -159,8 +162,6 @@ export class Tenant {
|
|
|
159
162
|
name: string;
|
|
160
163
|
domain: string;
|
|
161
164
|
tenantType: TenantTypeStrings;
|
|
162
|
-
permissionType: TenantPermissionTypeStrings;
|
|
163
|
-
onboarded: string;
|
|
164
165
|
authority: string;
|
|
165
166
|
workspaceIDs: string;
|
|
166
167
|
sel: boolean; // selection state
|
|
@@ -171,8 +172,6 @@ export class Tenant {
|
|
|
171
172
|
this.name = "";
|
|
172
173
|
this.domain = "";
|
|
173
174
|
this.tenantType = "aad";
|
|
174
|
-
this.permissionType = "notassigned";
|
|
175
|
-
this.onboarded = "false";
|
|
176
175
|
this.authority = "";
|
|
177
176
|
this.workspaceIDs = "";
|
|
178
177
|
this.sel = false;
|
|
@@ -180,14 +179,15 @@ export class Tenant {
|
|
|
180
179
|
this.lookupfield = "Domain";
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
|
-
function getAppId(authority: string): string {
|
|
182
|
+
function getAppId(authority: string, tct: TenantConfigType): string {
|
|
184
183
|
switch (authority) {
|
|
185
|
-
case graphConfig.authorityWW: return "63100afe-506e-4bb2-8ff7-d8d5ab373129";
|
|
186
|
-
case graphConfig.authorityUS: return "
|
|
187
|
-
case graphConfig.authorityCN: return "
|
|
184
|
+
case graphConfig.authorityWW: return tct === TenantConfigType.source ? "85d35da2-4118-4b03-aa05-605cedd7f2f8" : "63100afe-506e-4bb2-8ff7-d8d5ab373129";
|
|
185
|
+
case graphConfig.authorityUS: return tct === TenantConfigType.source ? "b08630c7-e227-4215-9746-afc9286fb864" : "17aa5d5a-f09f-4cec-87a6-28596f9fa513";
|
|
186
|
+
case graphConfig.authorityCN: return tct === TenantConfigType.source ? "7db7293b-add9-4a3f-8562-1a20bfe27d5e" : "debd015b-1154-4111-a4cb-fc220a537697";
|
|
188
187
|
default: debugger; return "";
|
|
189
188
|
}
|
|
190
189
|
}
|
|
190
|
+
// https://learn.microsoft.com/en-us/graph/deployments
|
|
191
191
|
function getGraphEndpoint(authority: string): string {
|
|
192
192
|
switch (authority) {
|
|
193
193
|
case graphConfig.authorityWW: return "https://graph.microsoft.com/";
|
|
@@ -196,10 +196,11 @@ function getGraphEndpoint(authority: string): string {
|
|
|
196
196
|
default: debugger; return "";
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
+
// https://learn.microsoft.com/en-us/entra/identity-platform/authentication-national-cloud
|
|
199
200
|
function getLoginEndpoint(authority: string): string {
|
|
200
201
|
switch (authority) {
|
|
201
|
-
case graphConfig.authorityWW: return "https://login.
|
|
202
|
-
case graphConfig.authorityUS: return "https://login.
|
|
202
|
+
case graphConfig.authorityWW: return "https://login.microsoftonline.com/";
|
|
203
|
+
case graphConfig.authorityUS: return "https://login.microsoftonline.us/";
|
|
203
204
|
case graphConfig.authorityCN: return "https://login.partner.microsoftonline.cn/";
|
|
204
205
|
default: debugger; return "";
|
|
205
206
|
}
|
|
@@ -221,6 +222,8 @@ export class TenantConfigInfo {
|
|
|
221
222
|
usersWritten: number;
|
|
222
223
|
configId: string;
|
|
223
224
|
batchId: string;
|
|
225
|
+
isReadPermissionConsented: string;
|
|
226
|
+
isWritePermissionConsented: string;
|
|
224
227
|
constructor() {
|
|
225
228
|
this.tid = "";
|
|
226
229
|
this.sourceGroupId = "";
|
|
@@ -232,6 +235,8 @@ export class TenantConfigInfo {
|
|
|
232
235
|
this.usersWritten = 0;
|
|
233
236
|
this.configId = "";
|
|
234
237
|
this.batchId = "";
|
|
238
|
+
this.isReadPermissionConsented = "";
|
|
239
|
+
this.isWritePermissionConsented = "";
|
|
235
240
|
}
|
|
236
241
|
}
|
|
237
242
|
export class Config {
|
|
@@ -304,6 +309,8 @@ export class InitInfo {
|
|
|
304
309
|
ts: Tenant[];
|
|
305
310
|
cs: Config[];
|
|
306
311
|
ws: Workspace[];
|
|
312
|
+
configlevelconsent_configid: string;
|
|
313
|
+
configlevelconsent_access: TenantConfigType;
|
|
307
314
|
constructor(bClearLocalStorage: boolean) {
|
|
308
315
|
this.init(bClearLocalStorage);
|
|
309
316
|
}
|
|
@@ -328,13 +335,15 @@ export class InitInfo {
|
|
|
328
335
|
}
|
|
329
336
|
}
|
|
330
337
|
// if storage unavailable or we were just asked to clear, read defaults to enable usable UI
|
|
338
|
+
this.tab = 0;
|
|
339
|
+
this.version = version;
|
|
340
|
+
this.configlevelconsent_configid = "";
|
|
341
|
+
this.configlevelconsent_access = TenantConfigType.sourcetarget;
|
|
331
342
|
var usersString = JSON.stringify(users);
|
|
332
343
|
var tenantsString = JSON.stringify(tenants);
|
|
333
344
|
var configsString = JSON.stringify(configs);
|
|
334
345
|
var workspacesString = JSON.stringify(workspaces);
|
|
335
346
|
try {
|
|
336
|
-
this.tab = 0;
|
|
337
|
-
this.version = version;
|
|
338
347
|
this.us = deserializeArray(User, usersString);
|
|
339
348
|
this.ts = deserializeArray(Tenant, tenantsString);
|
|
340
349
|
this.cs = deserializeArray(Config, configsString);
|
|
@@ -396,6 +405,9 @@ export class InitInfo {
|
|
|
396
405
|
}
|
|
397
406
|
#initFromObjects(ii: InitInfo): void {
|
|
398
407
|
this.tab = ii.tab;
|
|
408
|
+
this.version = version;
|
|
409
|
+
this.configlevelconsent_configid = ii.configlevelconsent_configid;
|
|
410
|
+
this.configlevelconsent_access = ii.configlevelconsent_access;
|
|
399
411
|
if (typeof ii.us === "undefined") {
|
|
400
412
|
this.us = new Array<User>();
|
|
401
413
|
}
|
|
@@ -431,8 +443,6 @@ export class InitInfo {
|
|
|
431
443
|
newtenant.name = tenant.name;
|
|
432
444
|
newtenant.domain = tenant.domain;
|
|
433
445
|
newtenant.tenantType = tenant.tenantType;
|
|
434
|
-
newtenant.permissionType = tenant.permissionType;
|
|
435
|
-
newtenant.onboarded = tenant.onboarded;
|
|
436
446
|
newtenant.authority = tenant.authority;
|
|
437
447
|
newtenant.workspaceIDs = tenant.workspaceIDs;
|
|
438
448
|
newtenant.sel = tenant.sel;
|
|
@@ -480,12 +490,7 @@ export type TaskType = "initialization" |
|
|
|
480
490
|
"reload React" |
|
|
481
491
|
"GET tenant details" |
|
|
482
492
|
"POST config init" |
|
|
483
|
-
"GET workspaces"
|
|
484
|
-
"onboard tenant" |
|
|
485
|
-
"create 2nd tenant" |
|
|
486
|
-
"invite 2nd admin" |
|
|
487
|
-
"onboard 2nd tenant" |
|
|
488
|
-
"create config";
|
|
493
|
+
"GET workspaces";
|
|
489
494
|
export class TaskArray {
|
|
490
495
|
tasks: Task[];
|
|
491
496
|
constructor(bClearLocalStorage: boolean) {
|
|
@@ -1299,31 +1304,33 @@ export async function oauth2PermissionGrantsSet(instance: IPublicClientApplicati
|
|
|
1299
1304
|
return false;
|
|
1300
1305
|
}
|
|
1301
1306
|
}
|
|
1302
|
-
export function requestAdminConsent(
|
|
1303
|
-
if (user.oid == "1") return;
|
|
1307
|
+
export function requestAdminConsent(admin: User, tct: TenantConfigType): void {
|
|
1304
1308
|
//
|
|
1305
1309
|
// for app permissions (app roles) we must use the /.default scope for admin consent
|
|
1306
1310
|
// 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.
|
|
1307
1311
|
// https://learn.microsoft.com/en-us/answers/questions/431784/how-to-grant-application-permissions-with-dynamic
|
|
1308
1312
|
//
|
|
1309
1313
|
// this means that, if we want to be granular about SyncReader vs. SyncWriter permissions, we must have separate Applications
|
|
1310
|
-
//
|
|
1314
|
+
// in addition, if there are permissions like Group.Read.All, that also must be admin consented
|
|
1315
|
+
// for now, this function will perform admin consent for the Reader and Writer applications, we will come back to Group.Read.All and User.Read.All
|
|
1311
1316
|
//
|
|
1312
|
-
//
|
|
1313
|
-
// scope
|
|
1317
|
+
// FYI - using the Challenge endpoint for app permissions caused the call to quietly fail without error
|
|
1318
|
+
// FYI - using the Challenge endpoint for this scope also caused the call to quietly fail withour error
|
|
1319
|
+
// scope = "63100afe-506e-4bb2-8ff7-d8d5ab373129/.default";
|
|
1314
1320
|
//
|
|
1315
|
-
// 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
|
|
1321
|
+
// thereforce, we are assuming that, for app permissions (app roles) or delegated permissions requiring admin consent, the Microsoft Identity Web Challenge endpoint does not work and we need to perform our own redirect to the admin consent endpoint
|
|
1316
1322
|
// https://learn.microsoft.com/EN-US/azure/active-directory/develop/scopes-oidc#client-credentials-grant-flow-and-default
|
|
1317
1323
|
// https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#request-the-permissions-from-a-directory-admin
|
|
1318
1324
|
//
|
|
1319
|
-
let adminConsentURL: string = getLoginEndpoint(
|
|
1320
|
-
adminConsentURL +=
|
|
1325
|
+
let adminConsentURL: string = getLoginEndpoint(admin.authority);
|
|
1326
|
+
adminConsentURL += admin.tid;
|
|
1321
1327
|
adminConsentURL += "/adminconsent";
|
|
1322
1328
|
let url: URL = new URL(adminConsentURL);
|
|
1323
|
-
let clientId: string = getAppId(
|
|
1329
|
+
let clientId: string = getAppId(admin.authority, tct);
|
|
1324
1330
|
url.searchParams.append("client_id", clientId);
|
|
1325
1331
|
url.searchParams.append("redirect_uri", window.location.origin);
|
|
1326
|
-
url.searchParams.append("domain_hint",
|
|
1332
|
+
url.searchParams.append("domain_hint", admin.companyDomain);
|
|
1333
|
+
url.searchParams.append("login_hint", admin.mail);
|
|
1327
1334
|
window.location.assign(url.href);
|
|
1328
1335
|
}
|
|
1329
1336
|
export async function servicePrincipalGet(options: RequestInit, user: User, appid: string): Promise<{ spid: string, error: string }> {
|
|
@@ -1736,7 +1743,9 @@ export async function configEdit(
|
|
|
1736
1743
|
result = await configPost(instance, authorizedUser, config, workspace.id, debug);
|
|
1737
1744
|
if (result.result) {
|
|
1738
1745
|
// config id was updated from "1"
|
|
1739
|
-
setConfigId
|
|
1746
|
+
if (setConfigId) {
|
|
1747
|
+
setConfigId(config.id);
|
|
1748
|
+
}
|
|
1740
1749
|
// set this config as the selected config
|
|
1741
1750
|
const newSelection = {};
|
|
1742
1751
|
Object.defineProperty(newSelection, config.id, { value: true, writable: true, enumerable: true });
|
|
@@ -1758,6 +1767,12 @@ export async function configEdit(
|
|
|
1758
1767
|
export async function configRemove(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
1759
1768
|
return configDelete(instance, authorizedUser, config, workspaceId, debug);
|
|
1760
1769
|
}
|
|
1770
|
+
export async function configConsentForRead(instance: IPublicClientApplication, authorizedUser: User, configId: string, tid: string, consent: boolean): Promise<APIResult> {
|
|
1771
|
+
return configConsentReadPut(instance, authorizedUser, configId, tid, consent);
|
|
1772
|
+
}
|
|
1773
|
+
export async function configConsentForWrite(instance: IPublicClientApplication, authorizedUser: User, configId: string, tid: string, consent: boolean): Promise<APIResult> {
|
|
1774
|
+
return configConsentWritePut(instance, authorizedUser, configId, tid, consent);
|
|
1775
|
+
}
|
|
1761
1776
|
export async function configsRefresh(instance: IPublicClientApplication, authorizedUser: User, workspaceId: string, ii: InitInfo, debug: boolean): Promise<APIResult> {
|
|
1762
1777
|
let result: APIResult = new APIResult();
|
|
1763
1778
|
if (debug) debugger;
|
|
@@ -1802,7 +1817,7 @@ export async function initGet(instance: IPublicClientApplication, authorizedUser
|
|
|
1802
1817
|
tasks.setTaskStart("GET tenant details", new Date());
|
|
1803
1818
|
result.result = await tenantRelationshipsGetById(user, ii, instance, debug);
|
|
1804
1819
|
tasks.setTaskEnd("GET tenant details", new Date(), "complete");
|
|
1805
|
-
// if this is the first time, we have just gotten tenant info, then we must POST user and
|
|
1820
|
+
// if this is the first time, we have just gotten tenant info, then we must POST user and tenant to back end
|
|
1806
1821
|
if (result.result) {
|
|
1807
1822
|
tasks.setTaskStart("POST config init", new Date());
|
|
1808
1823
|
result = await initPost(instance, authorizedUser, user, debug);
|
|
@@ -1832,9 +1847,6 @@ export async function initGet(instance: IPublicClientApplication, authorizedUser
|
|
|
1832
1847
|
export async function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): Promise<APIResult> {
|
|
1833
1848
|
return tenantPost(instance, authorizedUser, tenant, workspaceId);
|
|
1834
1849
|
}
|
|
1835
|
-
export async function tenantComplete(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, debug: boolean): Promise<APIResult> {
|
|
1836
|
-
return tenantPut(instance, authorizedUser, tenant, debug);
|
|
1837
|
-
}
|
|
1838
1850
|
export async function tenantRemove(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
1839
1851
|
return tenantDelete(instance, authorizedUser, tenant, workspaceId, debug);
|
|
1840
1852
|
}
|
|
@@ -1849,7 +1861,7 @@ export async function workspaceEdit(instance: IPublicClientApplication, authoriz
|
|
|
1849
1861
|
return await workspacePut(instance, authorizedUser, workspaceId, workspaceName);
|
|
1850
1862
|
}
|
|
1851
1863
|
// retrieve Workspace(s), User(s), Tenant(s), Config(s) given newly logged in user
|
|
1852
|
-
function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmins: Array<Object
|
|
1864
|
+
function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmins: Array<Object>, adminSelectedId: string) {
|
|
1853
1865
|
returnedAdmins.map((item) => {
|
|
1854
1866
|
// are we already tracking this user?
|
|
1855
1867
|
let user: User | null = null;
|
|
@@ -1876,6 +1888,8 @@ function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmin
|
|
|
1876
1888
|
// already tracking this user
|
|
1877
1889
|
user = ii.us.at(usIndex);
|
|
1878
1890
|
}
|
|
1891
|
+
// restore selection
|
|
1892
|
+
user.sel = (adminSelectedId === item.userId);
|
|
1879
1893
|
// refresh all the data available from the server
|
|
1880
1894
|
user.oid = item.userId ? item.userId : item.email;
|
|
1881
1895
|
user.name = item.firstName ?? user.name;
|
|
@@ -1900,7 +1914,7 @@ function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmin
|
|
|
1900
1914
|
});
|
|
1901
1915
|
ii.save();
|
|
1902
1916
|
}
|
|
1903
|
-
function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTenants: Array<Object
|
|
1917
|
+
function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTenants: Array<Object>, tenantSelectedId: string) {
|
|
1904
1918
|
returnedTenants.map((item) => {
|
|
1905
1919
|
// are we already tracking this tenant?
|
|
1906
1920
|
let tenant: Tenant | null = null;
|
|
@@ -1926,12 +1940,13 @@ function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTena
|
|
|
1926
1940
|
// already tracking this tenant
|
|
1927
1941
|
tenant = ii.ts.at(tsIndex);
|
|
1928
1942
|
}
|
|
1943
|
+
// restore selection
|
|
1944
|
+
tenant.sel = (tenantSelectedId === item.tenantId);
|
|
1945
|
+
// refresh all the data available from the server
|
|
1929
1946
|
tenant.tid = item.tenantId;
|
|
1930
1947
|
tenant.name = item.name;
|
|
1931
1948
|
tenant.domain = item.domain;
|
|
1932
1949
|
tenant.tenantType = item.type.toLowerCase(); // should now be strings
|
|
1933
|
-
tenant.permissionType = item.permissionType.toLowerCase(); // should now be strings
|
|
1934
|
-
tenant.onboarded = item.isOnboarded ? "true" : "false";
|
|
1935
1950
|
|
|
1936
1951
|
// canonicalize authority when getting it from config backend
|
|
1937
1952
|
const regex = /^(https:\/\/login.microsoftonline.(?:us|com)\/)organizations\/v2.0$/;
|
|
@@ -1944,7 +1959,7 @@ function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTena
|
|
|
1944
1959
|
});
|
|
1945
1960
|
ii.save();
|
|
1946
1961
|
}
|
|
1947
|
-
function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConfigs: Array<Object
|
|
1962
|
+
function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConfigs: Array<Object>, configSelectedId: string) {
|
|
1948
1963
|
// process returned configs
|
|
1949
1964
|
returnedConfigs.map((item) => {
|
|
1950
1965
|
// are we already tracking this config?
|
|
@@ -1971,6 +1986,9 @@ function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConf
|
|
|
1971
1986
|
// already tracking this config
|
|
1972
1987
|
config = ii.cs.at(csIndex);
|
|
1973
1988
|
}
|
|
1989
|
+
// restore selection
|
|
1990
|
+
config.sel = (configSelectedId === item.id);
|
|
1991
|
+
// refresh all the data available from the server
|
|
1974
1992
|
config!.id = item.id;
|
|
1975
1993
|
config!.workspaceId = item.workspaceId;
|
|
1976
1994
|
config!.name = item.name;
|
|
@@ -1989,6 +2007,8 @@ function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConf
|
|
|
1989
2007
|
tenantConfigInfo.deltaToken = tci.deltaToken ?? "";
|
|
1990
2008
|
tenantConfigInfo.configId = config!.id;
|
|
1991
2009
|
tenantConfigInfo.batchId = tci.batchId ?? "";
|
|
2010
|
+
tenantConfigInfo.isReadPermissionConsented = tci.isReadPermissionConsented;
|
|
2011
|
+
tenantConfigInfo.isWritePermissionConsented = tci.isWritePermissionConsented;
|
|
1992
2012
|
config!.tenants.push(tenantConfigInfo);
|
|
1993
2013
|
});
|
|
1994
2014
|
// ensure this workspace tracks this config
|
|
@@ -2023,11 +2043,32 @@ async function workspaceInfoGet(instance: IPublicClientApplication, authorizedUs
|
|
|
2023
2043
|
// already tracking this workspace
|
|
2024
2044
|
workspace = ii.ws.at(wsIndex);
|
|
2025
2045
|
}
|
|
2046
|
+
// preserve selected admin, tenant, and config
|
|
2047
|
+
let adminSelectedId: string = "";
|
|
2048
|
+
for (let oid of workspace.associatedUsers) {
|
|
2049
|
+
let user = ii.us.find((u: User) => u.oid === oid);
|
|
2050
|
+
if (user != null && user.sel) {
|
|
2051
|
+
adminSelectedId = user.oid;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
let tenantSelectedId: string = "";
|
|
2055
|
+
for (let tid of workspace.associatedTenants) {
|
|
2056
|
+
let tenant = ii.ts.find((t: Tenant) => t.tid === tid);
|
|
2057
|
+
if (tenant != null && tenant.sel) {
|
|
2058
|
+
tenantSelectedId = tenant.tid;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
let configSelectedId: string = "";
|
|
2062
|
+
for (let cid of workspace.associatedConfigs) {
|
|
2063
|
+
let config = ii.cs.find((c: Config) => c.id === cid);
|
|
2064
|
+
if (config != null && config.sel) {
|
|
2065
|
+
configSelectedId = config.id;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2026
2068
|
// clear associations as we are about to reset
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
//workspace.associatedConfigs.length = 0;
|
|
2069
|
+
workspace.associatedUsers.length = 0;
|
|
2070
|
+
workspace.associatedTenants.length = 0;
|
|
2071
|
+
workspace.associatedConfigs.length = 0;
|
|
2031
2072
|
// set id and name based on returned data
|
|
2032
2073
|
workspace.id = o.id;
|
|
2033
2074
|
workspace.name = o.name;
|
|
@@ -2042,9 +2083,9 @@ async function workspaceInfoGet(instance: IPublicClientApplication, authorizedUs
|
|
|
2042
2083
|
if (!tenantsResult.result) return tenantsResult;
|
|
2043
2084
|
if (!configsResult.result) return configsResult;
|
|
2044
2085
|
// process returned workspace components (tenants first as they have the authorities that admins depend on)
|
|
2045
|
-
processReturnedTenants(workspace, ii, tenantsResult.array
|
|
2046
|
-
processReturnedAdmins(workspace, ii, adminsResult.array
|
|
2047
|
-
processReturnedConfigs(workspace, ii, configsResult.array
|
|
2086
|
+
processReturnedTenants(workspace, ii, tenantsResult.array!, tenantSelectedId);
|
|
2087
|
+
processReturnedAdmins(workspace, ii, adminsResult.array!, adminSelectedId);
|
|
2088
|
+
processReturnedConfigs(workspace, ii, configsResult.array!, configSelectedId);
|
|
2048
2089
|
// tag components with workspaceIDs
|
|
2049
2090
|
ii.tagWithWorkspaces();
|
|
2050
2091
|
}
|
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.69",
|
|
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/tasks.ts
CHANGED
|
@@ -49,46 +49,6 @@ const data: any[] = [
|
|
|
49
49
|
status: "not started"
|
|
50
50
|
}
|
|
51
51
|
]
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 8,
|
|
55
|
-
task: "onboard tenant",
|
|
56
|
-
start: "1970-01-01T00:00:00",
|
|
57
|
-
end: "1970-01-01T00:00:00",
|
|
58
|
-
expected: "0:05",
|
|
59
|
-
status: "not started"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: 9,
|
|
63
|
-
task: "create 2nd tenant",
|
|
64
|
-
start: "1970-01-01T00:00:00",
|
|
65
|
-
end: "1970-01-01T00:00:00",
|
|
66
|
-
expected: "0:01",
|
|
67
|
-
status: "not started"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
id: 10,
|
|
71
|
-
task: "invite 2nd admin",
|
|
72
|
-
start: "1970-01-01T00:00:00",
|
|
73
|
-
end: "1970-01-01T00:00:00",
|
|
74
|
-
expected: "0:01",
|
|
75
|
-
status: "not started"
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: 11,
|
|
79
|
-
task: "onboard 2nd tenant",
|
|
80
|
-
start: "1970-01-01T00:00:00",
|
|
81
|
-
end: "1970-01-01T00:00:00",
|
|
82
|
-
expected: "0:05",
|
|
83
|
-
status: "not started"
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 12,
|
|
87
|
-
task: "create config",
|
|
88
|
-
start: "1970-01-01T00:00:00",
|
|
89
|
-
end: "1970-01-01T00:00:00",
|
|
90
|
-
expected: "0:01",
|
|
91
|
-
status: "not started"
|
|
92
52
|
}
|
|
93
53
|
];
|
|
94
54
|
|
package/tenants.json
CHANGED
|
Binary file
|