@finos/legend-application-studio 28.21.14 → 28.21.15
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/lib/application/LegendStudioApplicationConfig.d.ts +17 -0
- package/lib/application/LegendStudioApplicationConfig.d.ts.map +1 -1
- package/lib/application/LegendStudioApplicationConfig.js +2 -0
- package/lib/application/LegendStudioApplicationConfig.js.map +1 -1
- package/lib/components/editor/StatusBar.d.ts.map +1 -1
- package/lib/components/editor/StatusBar.js +8 -3
- package/lib/components/editor/StatusBar.js.map +1 -1
- package/lib/index.css +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/LegendStudioBaseStore.d.ts +118 -0
- package/lib/stores/LegendStudioBaseStore.d.ts.map +1 -1
- package/lib/stores/LegendStudioBaseStore.js +380 -32
- package/lib/stores/LegendStudioBaseStore.js.map +1 -1
- package/package.json +16 -16
- package/src/application/LegendStudioApplicationConfig.ts +25 -1
- package/src/components/editor/StatusBar.tsx +21 -1
- package/src/stores/LegendStudioBaseStore.ts +432 -41
|
@@ -54,6 +54,18 @@ export type LegendStudioApplicationStore = ApplicationStore<
|
|
|
54
54
|
LegendStudioPluginManager
|
|
55
55
|
>;
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Same-origin path of the static page that the SDLC OAuth flow redirects to
|
|
59
|
+
* when re-authenticating in a popup window. Shipped by the Studio deployment
|
|
60
|
+
* (see `assets/popup-callback.html`); deployments enabling the popup
|
|
61
|
+
* re-auth feature must register `<base-url>/popup-callback.html` on the
|
|
62
|
+
* SDLC server's OAuth client allow-list.
|
|
63
|
+
*
|
|
64
|
+
* The leading slash is required because `navigator.generateAddress` is
|
|
65
|
+
* `origin + baseUrl + location` and baseUrl has its trailing slash stripped.
|
|
66
|
+
*/
|
|
67
|
+
const SDLC_POPUP_REAUTH_CALLBACK_PATH = '/popup-callback.html';
|
|
68
|
+
|
|
57
69
|
export class LegendStudioBaseStore {
|
|
58
70
|
readonly applicationStore: LegendStudioApplicationStore;
|
|
59
71
|
readonly sdlcServerClient: SDLCServerClient;
|
|
@@ -61,18 +73,72 @@ export class LegendStudioBaseStore {
|
|
|
61
73
|
readonly pluginManager: LegendStudioPluginManager;
|
|
62
74
|
|
|
63
75
|
readonly initState = ActionState.create();
|
|
76
|
+
readonly popupReAuthState = ActionState.create();
|
|
64
77
|
|
|
65
78
|
isSDLCAuthorized: boolean | undefined = false;
|
|
66
79
|
private isSDLCServerInitialized = false;
|
|
67
80
|
SDLCServerTermsOfServicesUrlsToView: string[] = [];
|
|
68
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Tracks whether an automatic popup re-auth has already been attempted for
|
|
84
|
+
* the current "failure episode". An episode is the window between two
|
|
85
|
+
* successful SDLC interactions: the flag is set on the first auto-attempt
|
|
86
|
+
* and cleared whenever a re-auth (auto or manual) succeeds. This is what
|
|
87
|
+
* prevents endless 401 → popup → 401 loops.
|
|
88
|
+
*/
|
|
89
|
+
private autoPopupReAuthAttempted = false;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Dedupes concurrent auto callers of `handleSDLCUnauthorized`. Wraps the
|
|
93
|
+
* raw popup promise with the auto-flow notification logic (manual-fallback
|
|
94
|
+
* warning on failure) so all concurrent 401-driven callers get the same
|
|
95
|
+
* resolution and only one warning fires per attempt.
|
|
96
|
+
*/
|
|
97
|
+
private inFlightAutoReAuth: Promise<boolean> | undefined;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The raw popup promise, set by `reAuthorizeSDLCInPopup` for the duration
|
|
101
|
+
* of an on-screen popup REGARDLESS of who opened it (manual shield click
|
|
102
|
+
* OR auto-trigger). `handleSDLCUnauthorized` consults this before deciding
|
|
103
|
+
* to consume the episode and warn — without it, a 401 raised while the
|
|
104
|
+
* user is in a manually-opened popup would either stack a second popup or
|
|
105
|
+
* pop a "click the shield" warning at them while the shield's popup is in
|
|
106
|
+
* front of them.
|
|
107
|
+
*/
|
|
108
|
+
private inFlightReAuth: Promise<boolean> | undefined;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Per-episode dedup flag for the "click the shield to retry" warning.
|
|
112
|
+
* `handleSDLCUnauthorized` can be re-entered many times within a single
|
|
113
|
+
* failure episode (every subsequent failing SDLC request in a burst lands
|
|
114
|
+
* in the loop-guard branch); without this flag, each one would stack an
|
|
115
|
+
* identical toast. Cleared alongside `autoPopupReAuthAttempted` on a
|
|
116
|
+
* successful re-auth so the next episode can warn again.
|
|
117
|
+
*/
|
|
118
|
+
private manualReAuthNotifiedForEpisode = false;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Re-entry guard for the network layer's 401 hook. Set to `true` while we
|
|
122
|
+
* are re-bootstrapping the SDLC session inside the popup-success path so
|
|
123
|
+
* that a 401 raised by the re-bootstrap requests themselves (e.g. the
|
|
124
|
+
* `isAuthorized` verification GET coming back 401 because cookies didn't
|
|
125
|
+
* actually land) cannot recurse into `handleSDLCUnauthorized` — that would
|
|
126
|
+
* return the still-in-flight outer promise and deadlock the whole chain.
|
|
127
|
+
*/
|
|
128
|
+
private suppressAutoReAuth = false;
|
|
129
|
+
|
|
69
130
|
constructor(applicationStore: LegendStudioApplicationStore) {
|
|
70
|
-
makeObservable<
|
|
131
|
+
makeObservable<
|
|
132
|
+
LegendStudioBaseStore,
|
|
133
|
+
'initializeSDLCServerClient' | 'verifySDLCSessionAfterReAuth'
|
|
134
|
+
>(this, {
|
|
71
135
|
isSDLCAuthorized: observable,
|
|
72
136
|
SDLCServerTermsOfServicesUrlsToView: observable,
|
|
73
137
|
needsToAcceptSDLCServerTermsOfServices: computed,
|
|
138
|
+
isSDLCPopupReAuthEnabled: computed,
|
|
74
139
|
initialize: flow,
|
|
75
140
|
initializeSDLCServerClient: flow,
|
|
141
|
+
verifySDLCSessionAfterReAuth: flow,
|
|
76
142
|
dismissSDLCServerTermsOfServicesAlert: action,
|
|
77
143
|
});
|
|
78
144
|
|
|
@@ -93,6 +159,11 @@ export class LegendStudioBaseStore {
|
|
|
93
159
|
serverUrl: this.applicationStore.config.sdlcServerUrl,
|
|
94
160
|
baseHeaders: this.applicationStore.config.sdlcServerBaseHeaders,
|
|
95
161
|
client: this.applicationStore.config.sdlcServerClient,
|
|
162
|
+
// On a mid-session 401, auto-launch the popup re-auth flow. The
|
|
163
|
+
// handler enforces a one-attempt-per-episode guard to prevent endless
|
|
164
|
+
// retry loops; when it gives up, the StatusBar manual button remains
|
|
165
|
+
// available.
|
|
166
|
+
autoReAuthenticate: () => this.handleSDLCUnauthorized(),
|
|
96
167
|
});
|
|
97
168
|
this.sdlcServerClient.setTracerService(this.applicationStore.tracerService);
|
|
98
169
|
}
|
|
@@ -206,6 +277,364 @@ export class LegendStudioBaseStore {
|
|
|
206
277
|
this.SDLCServerTermsOfServicesUrlsToView = [];
|
|
207
278
|
}
|
|
208
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Whether the popup-based SDLC re-authentication flow is available.
|
|
282
|
+
*
|
|
283
|
+
* The feature is opt-in via the `sdlc.enablePopupReAuth` config flag
|
|
284
|
+
* because the derived callback URL must be explicitly registered as an
|
|
285
|
+
* OAuth `redirect_uri` on the SDLC server. When `false` (or unset), only
|
|
286
|
+
* the legacy top-level redirect flow is available.
|
|
287
|
+
*/
|
|
288
|
+
get isSDLCPopupReAuthEnabled(): boolean {
|
|
289
|
+
return this.applicationStore.config.sdlcEnablePopupReAuth;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 401 interception hook wired into the SDLC server client. Triggers the
|
|
294
|
+
* popup re-auth flow automatically — without any user action — but only
|
|
295
|
+
* once per failure episode to guard against retry loops.
|
|
296
|
+
*
|
|
297
|
+
* @returns a promise resolving to `true` if the SDLC server is now
|
|
298
|
+
* authorized (i.e. the underlying network layer should retry the original
|
|
299
|
+
* request), `false` otherwise.
|
|
300
|
+
*/
|
|
301
|
+
private handleSDLCUnauthorized(): Promise<boolean> {
|
|
302
|
+
// Feature off → propagate the 401 to the caller as before.
|
|
303
|
+
if (!this.isSDLCPopupReAuthEnabled) {
|
|
304
|
+
return Promise.resolve(false);
|
|
305
|
+
}
|
|
306
|
+
// Re-entry guard: a 401 raised from inside the post-popup re-bootstrap
|
|
307
|
+
// (e.g. the verification `isAuthorized()` call itself coming back 401
|
|
308
|
+
// because cookies didn't actually land) MUST NOT recurse into the popup
|
|
309
|
+
// flow — that would return the in-flight outer promise and deadlock.
|
|
310
|
+
if (this.suppressAutoReAuth) {
|
|
311
|
+
return Promise.resolve(false);
|
|
312
|
+
}
|
|
313
|
+
// Coalesce concurrent auto callers onto the wrapped auto-flow promise so
|
|
314
|
+
// the manual-fallback warning only fires once per attempt.
|
|
315
|
+
if (this.inFlightAutoReAuth) {
|
|
316
|
+
return this.inFlightAutoReAuth;
|
|
317
|
+
}
|
|
318
|
+
// If a popup is already on screen — most commonly because the user just
|
|
319
|
+
// clicked the StatusBar shield, but also possibly because a prior auto-
|
|
320
|
+
// attempt's popup is still finalising — defer to its outcome instead of
|
|
321
|
+
// consuming this episode's one auto-attempt and warning at the user
|
|
322
|
+
// about a manual button that the popup is in the way of.
|
|
323
|
+
if (this.inFlightReAuth) {
|
|
324
|
+
return this.inFlightReAuth;
|
|
325
|
+
}
|
|
326
|
+
// Loop guard: we've already auto-attempted once this episode. Surface
|
|
327
|
+
// the manual fallback (deduped) and stop auto-retrying.
|
|
328
|
+
if (this.autoPopupReAuthAttempted) {
|
|
329
|
+
this.notifyManualReAuthAvailable();
|
|
330
|
+
return Promise.resolve(false);
|
|
331
|
+
}
|
|
332
|
+
this.autoPopupReAuthAttempted = true;
|
|
333
|
+
// NOTE: we deliberately don't fire an upfront "session expired" toast —
|
|
334
|
+
// the popup window itself is the user-visible signal that re-auth is
|
|
335
|
+
// happening. We only notify on completion (success or failure) so the
|
|
336
|
+
// toast stack stays clean.
|
|
337
|
+
this.inFlightAutoReAuth = this.reAuthorizeSDLCInPopup()
|
|
338
|
+
.then((ok) => {
|
|
339
|
+
this.inFlightAutoReAuth = undefined;
|
|
340
|
+
if (!ok) {
|
|
341
|
+
this.notifyManualReAuthAvailable();
|
|
342
|
+
}
|
|
343
|
+
// success path resets `autoPopupReAuthAttempted` inside
|
|
344
|
+
// `reAuthorizeSDLCInPopup` so the next episode can auto-attempt again
|
|
345
|
+
return ok;
|
|
346
|
+
})
|
|
347
|
+
.catch(() => {
|
|
348
|
+
this.inFlightAutoReAuth = undefined;
|
|
349
|
+
this.notifyManualReAuthAvailable();
|
|
350
|
+
return false;
|
|
351
|
+
});
|
|
352
|
+
return this.inFlightAutoReAuth;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Best-effort notification that nudges the user toward the manual
|
|
357
|
+
* re-authentication entry point (the StatusBar shield button) when the
|
|
358
|
+
* automatic flow could not complete (popup blocked, user dismissed, or
|
|
359
|
+
* the retry still 401'd).
|
|
360
|
+
*
|
|
361
|
+
* Deduped per failure episode: subsequent 401s within the same episode
|
|
362
|
+
* (a burst of editor requests after session expiry, for example) all hit
|
|
363
|
+
* the loop-guard branch and would otherwise each stack an identical
|
|
364
|
+
* warning toast. The flag is cleared when a re-auth attempt succeeds.
|
|
365
|
+
*/
|
|
366
|
+
private notifyManualReAuthAvailable(): void {
|
|
367
|
+
if (this.manualReAuthNotifiedForEpisode) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
this.manualReAuthNotifiedForEpisode = true;
|
|
371
|
+
this.applicationStore.notificationService.notifyWarning(
|
|
372
|
+
`Automatic SDLC re-authentication did not complete. Click the shield icon in the status bar to retry, or reload the page to start a fresh session.`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Re-initiate the SDLC authorization step in a popup window so that the
|
|
378
|
+
* user can recover from an expired SDLC session without losing in-memory
|
|
379
|
+
* editor state (open tabs, unsaved buffers, etc.).
|
|
380
|
+
*
|
|
381
|
+
* The popup loads the SDLC server's `/auth/authorize?redirect_uri=...`
|
|
382
|
+
* endpoint with a same-origin callback page as the `redirect_uri`. That
|
|
383
|
+
* URL is derived at runtime via the navigator — the same way the
|
|
384
|
+
* boot-time auth flow derives its own `redirect_uri` — so deployments
|
|
385
|
+
* only need to declare intent (`sdlc.enablePopupReAuth: true`) and ship
|
|
386
|
+
* the static `popup-callback.html` page at their base URL.
|
|
387
|
+
*
|
|
388
|
+
* The callback page is expected to `postMessage` back to the opener with
|
|
389
|
+
* payload `{ type: 'SDLC_REAUTH_DONE' }` and then close itself.
|
|
390
|
+
*
|
|
391
|
+
* On success, the SDLC server client is re-checked and re-initialized in
|
|
392
|
+
* place — no top-level navigation, no state loss.
|
|
393
|
+
*
|
|
394
|
+
* @returns a promise that resolves to `true` if the SDLC server is
|
|
395
|
+
* authorized after the popup flow completes, `false` otherwise (popup
|
|
396
|
+
* blocked, user cancelled, authorization check still failing, ...).
|
|
397
|
+
*/
|
|
398
|
+
async reAuthorizeSDLCInPopup(): Promise<boolean> {
|
|
399
|
+
if (!this.isSDLCPopupReAuthEnabled) {
|
|
400
|
+
this.applicationStore.notificationService.notifyWarning(
|
|
401
|
+
`Popup re-authentication is not enabled. Set 'sdlc.enablePopupReAuth' to true in the Studio config to enable it.`,
|
|
402
|
+
);
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
// Coalesce concurrent callers (manual shield click + auto 401 trigger)
|
|
406
|
+
// onto a single popup. Without this, a 401 raised while the user is in
|
|
407
|
+
// a manually-opened popup would either stack a second popup OR fire a
|
|
408
|
+
// confusing "click the shield" warning at the user while the shield's
|
|
409
|
+
// popup is literally on screen.
|
|
410
|
+
if (this.inFlightReAuth) {
|
|
411
|
+
return this.inFlightReAuth;
|
|
412
|
+
}
|
|
413
|
+
this.popupReAuthState.inProgress();
|
|
414
|
+
|
|
415
|
+
// Derive the callback URL the same way the boot-time SDLC auth flow
|
|
416
|
+
// derives its `redirect_uri` — via the navigator's same-origin address
|
|
417
|
+
// builder. The callback page is a constant static asset shipped with
|
|
418
|
+
// every Studio deployment.
|
|
419
|
+
const callbackUrl =
|
|
420
|
+
this.applicationStore.navigationService.navigator.generateAddress(
|
|
421
|
+
SDLC_POPUP_REAUTH_CALLBACK_PATH,
|
|
422
|
+
);
|
|
423
|
+
const callbackOrigin = new URL(callbackUrl).origin;
|
|
424
|
+
|
|
425
|
+
const authorizeUrl = SDLCServerClient.authorizeCallbackUrl(
|
|
426
|
+
this.applicationStore.config.sdlcServerUrl,
|
|
427
|
+
callbackUrl,
|
|
428
|
+
this.applicationStore.config.sdlcServerClient,
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const popup = window.open(
|
|
432
|
+
authorizeUrl,
|
|
433
|
+
'legend-studio-sdlc-reauth',
|
|
434
|
+
'width=560,height=720,menubar=no,toolbar=no,location=no,status=no',
|
|
435
|
+
);
|
|
436
|
+
if (!popup) {
|
|
437
|
+
this.popupReAuthState.fail();
|
|
438
|
+
this.applicationStore.notificationService.notifyError(
|
|
439
|
+
`Failed to open the re-authentication popup. Please allow popups for this site and try again.`,
|
|
440
|
+
);
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const promise = new Promise<boolean>((resolve) => {
|
|
445
|
+
let closedPoll: ReturnType<typeof setInterval> | undefined;
|
|
446
|
+
let settled = false;
|
|
447
|
+
|
|
448
|
+
// `finalize` and `onMessage` form a small cycle:
|
|
449
|
+
// onMessage -> finalize (call on success postMessage)
|
|
450
|
+
// finalize -> onMessage (removeEventListener on teardown)
|
|
451
|
+
// We declare `finalize` first so only the one forward reference to
|
|
452
|
+
// `onMessage` from inside `finalize`'s body needs to be excused.
|
|
453
|
+
const finalize = (signalled: boolean): void => {
|
|
454
|
+
if (settled) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
settled = true;
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
459
|
+
window.removeEventListener('message', onMessage);
|
|
460
|
+
if (closedPoll !== undefined) {
|
|
461
|
+
clearInterval(closedPoll);
|
|
462
|
+
closedPoll = undefined;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
popup.close();
|
|
466
|
+
} catch {
|
|
467
|
+
// ignored — popup may already be closed or cross-origin
|
|
468
|
+
}
|
|
469
|
+
// Always re-verify the SDLC session in place, whether the callback
|
|
470
|
+
// page managed to `postMessage` (`signalled === true`) or the popup
|
|
471
|
+
// simply closed (`signalled === false`). The popup-closed path used
|
|
472
|
+
// to fail outright, but that silently broke environments where the
|
|
473
|
+
// browser severs `window.opener` somewhere along the auth redirect
|
|
474
|
+
// chain (most commonly an IdP or the SDLC server sending
|
|
475
|
+
// `Cross-Origin-Opener-Policy: same-origin`): the callback page's
|
|
476
|
+
// `postMessage` becomes a no-op, the popup closes itself, and the
|
|
477
|
+
// only way to learn whether auth actually took is to ask the server.
|
|
478
|
+
// The cost is one cheap GET on user-cancel; the upside is robust
|
|
479
|
+
// recovery for COOP-severed deployments.
|
|
480
|
+
//
|
|
481
|
+
// We deliberately do NOT route through `initializeSDLCServerClient`
|
|
482
|
+
// here — that would do a top-level redirect on `!isAuthorized` and
|
|
483
|
+
// lose all the in-memory editor state this whole feature exists to
|
|
484
|
+
// protect. `suppressAutoReAuth` is set for the duration of the
|
|
485
|
+
// verify so that a 401 raised by the verify GET itself can't recurse
|
|
486
|
+
// back into `handleSDLCUnauthorized` (which would return the still-
|
|
487
|
+
// in-flight outer promise and deadlock).
|
|
488
|
+
this.isSDLCServerInitialized = false;
|
|
489
|
+
this.suppressAutoReAuth = true;
|
|
490
|
+
flowResult(this.verifySDLCSessionAfterReAuth())
|
|
491
|
+
.then((ok) => {
|
|
492
|
+
this.suppressAutoReAuth = false;
|
|
493
|
+
if (ok) {
|
|
494
|
+
// reset the per-episode flags so the next failure episode
|
|
495
|
+
// can auto-attempt re-auth and warn the user again
|
|
496
|
+
this.autoPopupReAuthAttempted = false;
|
|
497
|
+
this.manualReAuthNotifiedForEpisode = false;
|
|
498
|
+
this.popupReAuthState.pass();
|
|
499
|
+
this.applicationStore.notificationService.notifySuccess(
|
|
500
|
+
`Successfully re-authenticated with the SDLC server.`,
|
|
501
|
+
);
|
|
502
|
+
} else {
|
|
503
|
+
this.popupReAuthState.fail();
|
|
504
|
+
if (signalled) {
|
|
505
|
+
// The popup explicitly told us it completed, yet the SDLC
|
|
506
|
+
// server still reports the session as unauthorized. That's
|
|
507
|
+
// a real problem the user should know about (wrong identity
|
|
508
|
+
// chosen in the popup, third-party-cookie / SameSite issue,
|
|
509
|
+
// ToS race, etc.). Surface a loud error.
|
|
510
|
+
this.applicationStore.notificationService.notifyError(
|
|
511
|
+
`Re-authentication popup completed but the SDLC session is still not authorized. Please reload the page to start a fresh session.`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
// !signalled + verify failed is indistinguishable from a
|
|
515
|
+
// deliberate user-cancel — stay quiet. The auto-trigger
|
|
516
|
+
// caller (`handleSDLCUnauthorized`) will fire a single
|
|
517
|
+
// "click the shield to retry" hint if this was an auto
|
|
518
|
+
// attempt; the manual button caller simply gets `false` back.
|
|
519
|
+
}
|
|
520
|
+
resolve(ok);
|
|
521
|
+
})
|
|
522
|
+
.catch((error: unknown) => {
|
|
523
|
+
this.suppressAutoReAuth = false;
|
|
524
|
+
assertErrorThrown(error);
|
|
525
|
+
this.popupReAuthState.fail();
|
|
526
|
+
this.applicationStore.logService.error(
|
|
527
|
+
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
|
|
528
|
+
error,
|
|
529
|
+
);
|
|
530
|
+
this.applicationStore.notificationService.notifyError(error);
|
|
531
|
+
resolve(false);
|
|
532
|
+
});
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const onMessage = (event: MessageEvent): void => {
|
|
536
|
+
// strict origin + payload-shape check
|
|
537
|
+
if (event.origin !== callbackOrigin) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const data = event.data as { type?: unknown } | null;
|
|
541
|
+
if (!data || data.type !== 'SDLC_REAUTH_DONE') {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
finalize(true);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
window.addEventListener('message', onMessage);
|
|
548
|
+
|
|
549
|
+
// Fallback: if the user closes the popup without completing auth,
|
|
550
|
+
// we still want to release the in-progress state.
|
|
551
|
+
closedPoll = setInterval(() => {
|
|
552
|
+
if (popup.closed) {
|
|
553
|
+
finalize(false);
|
|
554
|
+
}
|
|
555
|
+
}, 500);
|
|
556
|
+
});
|
|
557
|
+
this.inFlightReAuth = promise;
|
|
558
|
+
try {
|
|
559
|
+
return await promise;
|
|
560
|
+
} finally {
|
|
561
|
+
// Clear the slot AFTER awaiting so all concurrent callers (which got
|
|
562
|
+
// `this.inFlightReAuth` back from the early-return branch) resolve
|
|
563
|
+
// against the same promise instance. Subsequent attempts then start
|
|
564
|
+
// a fresh popup.
|
|
565
|
+
this.inFlightReAuth = undefined;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Shared post-auth bootstrap: ToS gate, platforms, features. Plain
|
|
571
|
+
* generator (not a flow) — delegated to from both `initializeSDLCServerClient`
|
|
572
|
+
* (the boot-time flow) and `verifySDLCSessionAfterReAuth` (the popup-success
|
|
573
|
+
* flow) via `yield*`. Caller is responsible for asserting `isSDLCAuthorized`
|
|
574
|
+
* beforehand.
|
|
575
|
+
*/
|
|
576
|
+
private *bootstrapSDLCSessionAfterAuth(): GeneratorFn<void> {
|
|
577
|
+
// check terms of service agreement status
|
|
578
|
+
this.SDLCServerTermsOfServicesUrlsToView =
|
|
579
|
+
(yield this.sdlcServerClient.hasAcceptedTermsOfService()) as string[];
|
|
580
|
+
if (this.SDLCServerTermsOfServicesUrlsToView.length) {
|
|
581
|
+
this.applicationStore.alertService.setActionAlertInfo({
|
|
582
|
+
message: `Please read and accept the SDLC servers' terms of service`,
|
|
583
|
+
prompt: `Click 'Done' when you have accepted all the terms`,
|
|
584
|
+
type: ActionAlertType.CAUTION,
|
|
585
|
+
actions: [
|
|
586
|
+
{
|
|
587
|
+
label: 'See terms of services',
|
|
588
|
+
default: true,
|
|
589
|
+
handler: (): void =>
|
|
590
|
+
this.SDLCServerTermsOfServicesUrlsToView.forEach((url) =>
|
|
591
|
+
this.applicationStore.navigationService.navigator.visitAddress(
|
|
592
|
+
url,
|
|
593
|
+
),
|
|
594
|
+
),
|
|
595
|
+
type: ActionAlertActionType.PROCEED,
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
label: 'Done',
|
|
599
|
+
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
|
|
600
|
+
handler: (): void => {
|
|
601
|
+
this.dismissSDLCServerTermsOfServicesAlert();
|
|
602
|
+
this.applicationStore.navigationService.navigator.reload();
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// fetch server features config and platforms
|
|
610
|
+
yield this.sdlcServerClient.fetchServerPlatforms();
|
|
611
|
+
yield this.sdlcServerClient.fetchServerFeaturesConfiguration();
|
|
612
|
+
|
|
613
|
+
// the sdlc server client is authorized and initialized
|
|
614
|
+
this.isSDLCServerInitialized = true;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Verify the SDLC session after a popup re-auth attempt completes, and
|
|
619
|
+
* re-bootstrap the session in place if it took. Returns `true` if the
|
|
620
|
+
* session is now usable, `false` if the SDLC server still reports the
|
|
621
|
+
* session as unauthorized (e.g. cookies didn't actually land, user authed
|
|
622
|
+
* against the wrong identity).
|
|
623
|
+
*
|
|
624
|
+
* Unlike `initializeSDLCServerClient`, this does NOT do a top-level
|
|
625
|
+
* redirect on `!isAuthorized` — preserving in-memory editor state is the
|
|
626
|
+
* entire reason the popup flow exists.
|
|
627
|
+
*/
|
|
628
|
+
private *verifySDLCSessionAfterReAuth(): GeneratorFn<boolean> {
|
|
629
|
+
this.isSDLCAuthorized =
|
|
630
|
+
(yield this.sdlcServerClient.isAuthorized()) as boolean;
|
|
631
|
+
if (!this.isSDLCAuthorized) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
yield* this.bootstrapSDLCSessionAfterAuth();
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
|
|
209
638
|
private *initializeSDLCServerClient(): GeneratorFn<void> {
|
|
210
639
|
try {
|
|
211
640
|
this.isSDLCAuthorized =
|
|
@@ -219,46 +648,8 @@ export class LegendStudioBaseStore {
|
|
|
219
648
|
),
|
|
220
649
|
);
|
|
221
650
|
} else {
|
|
222
|
-
// Only proceed
|
|
223
|
-
|
|
224
|
-
// check terms of service agreement status
|
|
225
|
-
this.SDLCServerTermsOfServicesUrlsToView =
|
|
226
|
-
(yield this.sdlcServerClient.hasAcceptedTermsOfService()) as string[];
|
|
227
|
-
if (this.SDLCServerTermsOfServicesUrlsToView.length) {
|
|
228
|
-
this.applicationStore.alertService.setActionAlertInfo({
|
|
229
|
-
message: `Please read and accept the SDLC servers' terms of service`,
|
|
230
|
-
prompt: `Click 'Done' when you have accepted all the terms`,
|
|
231
|
-
type: ActionAlertType.CAUTION,
|
|
232
|
-
actions: [
|
|
233
|
-
{
|
|
234
|
-
label: 'See terms of services',
|
|
235
|
-
default: true,
|
|
236
|
-
handler: (): void =>
|
|
237
|
-
this.SDLCServerTermsOfServicesUrlsToView.forEach((url) =>
|
|
238
|
-
this.applicationStore.navigationService.navigator.visitAddress(
|
|
239
|
-
url,
|
|
240
|
-
),
|
|
241
|
-
),
|
|
242
|
-
type: ActionAlertActionType.PROCEED,
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
label: 'Done',
|
|
246
|
-
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
|
|
247
|
-
handler: (): void => {
|
|
248
|
-
this.dismissSDLCServerTermsOfServicesAlert();
|
|
249
|
-
this.applicationStore.navigationService.navigator.reload();
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// fetch server features config and platforms
|
|
257
|
-
yield this.sdlcServerClient.fetchServerPlatforms();
|
|
258
|
-
yield this.sdlcServerClient.fetchServerFeaturesConfiguration();
|
|
259
|
-
|
|
260
|
-
// the sdlc server client is authorized and initialized
|
|
261
|
-
this.isSDLCServerInitialized = true;
|
|
651
|
+
// Only proceed initialization after passing authorization check
|
|
652
|
+
yield* this.bootstrapSDLCSessionAfterAuth();
|
|
262
653
|
}
|
|
263
654
|
} catch (error) {
|
|
264
655
|
assertErrorThrown(error);
|