@ibgib/web-gib 0.0.57 → 0.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/api/ibgib-api-bridge.d.mts +63 -1
- package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
- package/dist/api/ibgib-api-bridge.mjs +135 -0
- package/dist/api/ibgib-api-bridge.mjs.map +1 -1
- package/dist/identity/custodian-delegate-helper.d.mts +22 -0
- package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
- package/dist/identity/custodian-delegate-helper.mjs +50 -0
- package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
- package/dist/identity/sso-popup-helper.d.mts +33 -0
- package/dist/identity/sso-popup-helper.d.mts.map +1 -0
- package/dist/identity/sso-popup-helper.mjs +124 -0
- package/dist/identity/sso-popup-helper.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.mjs +5 -1
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +445 -73
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/package.json +4 -4
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/api/ibgib-api-bridge.mts +195 -1
- package/src/identity/custodian-delegate-helper.mts +61 -0
- package/src/identity/sso-popup-helper.mts +155 -0
- package/src/index.mts +3 -0
- package/src/ui/component/identity/add-device/add-device.mts +5 -1
- package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +507 -77
- package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
- package/tools/auto-generated-agent-skills/skills/ibgib-implementation-plan/SKILL.md +3 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
8
8
|
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
9
|
+
import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
|
|
9
10
|
|
|
10
11
|
const logalot = false;
|
|
11
12
|
|
|
@@ -279,7 +280,7 @@ export class IbGibApiBridge {
|
|
|
279
280
|
primaryTjpAddr
|
|
280
281
|
}: {
|
|
281
282
|
email: string;
|
|
282
|
-
primaryTjpAddr
|
|
283
|
+
primaryTjpAddr?: string;
|
|
283
284
|
}): Promise<{ success: boolean; message?: string }> {
|
|
284
285
|
const lc = `${this.lc}[${this.postVerificationCode.name}]`;
|
|
285
286
|
try {
|
|
@@ -360,6 +361,199 @@ export class IbGibApiBridge {
|
|
|
360
361
|
return { success: false, message: errorMsg };
|
|
361
362
|
}
|
|
362
363
|
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Posts a custodian genesis request (`POST /api/identity/custodian/genesis`)
|
|
367
|
+
* to obtain the primary identity keystone (or primaryTjpAddr) and a fresh, single-turn
|
|
368
|
+
* `CustodianRequirements` payload for delegate registration.
|
|
369
|
+
*/
|
|
370
|
+
public async postCustodianGenesis({
|
|
371
|
+
email,
|
|
372
|
+
code,
|
|
373
|
+
username
|
|
374
|
+
}: {
|
|
375
|
+
email: string;
|
|
376
|
+
code?: string;
|
|
377
|
+
username?: string;
|
|
378
|
+
}): Promise<{
|
|
379
|
+
success: boolean;
|
|
380
|
+
isNewIdentity?: boolean;
|
|
381
|
+
primaryTjpAddr?: string;
|
|
382
|
+
primaryKeystone?: IbGib_V1;
|
|
383
|
+
primaryGraph?: FlatIbGibGraph;
|
|
384
|
+
custodianReqs?: any;
|
|
385
|
+
message?: string;
|
|
386
|
+
}> {
|
|
387
|
+
const lc = `${this.lc}[${this.postCustodianGenesis.name}]`;
|
|
388
|
+
try {
|
|
389
|
+
const url = '/api/identity/custodian/genesis';
|
|
390
|
+
const response = await fetch(url, {
|
|
391
|
+
method: 'POST',
|
|
392
|
+
headers: {
|
|
393
|
+
'Content-Type': 'application/json',
|
|
394
|
+
},
|
|
395
|
+
body: JSON.stringify({ email, code, username }),
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const data = await this.parseResponseJson(response, url);
|
|
399
|
+
|
|
400
|
+
if (!response.ok) {
|
|
401
|
+
const errorMsg = data.message || data.error || 'Custodian genesis failed';
|
|
402
|
+
console.error(`${lc} custodian genesis failed: ${errorMsg}`);
|
|
403
|
+
return { success: false, message: errorMsg };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
isNewIdentity: !!data.isNewIdentity,
|
|
409
|
+
primaryTjpAddr: data.primaryTjpAddr,
|
|
410
|
+
primaryKeystone: data.primaryKeystone,
|
|
411
|
+
primaryGraph: data.primaryGraph,
|
|
412
|
+
custodianReqs: data.custodianReqs,
|
|
413
|
+
message: data.message
|
|
414
|
+
};
|
|
415
|
+
} catch (error) {
|
|
416
|
+
const errorMsg = extractErrorMsg(error);
|
|
417
|
+
console.error(`${lc} post custodian genesis error: ${errorMsg}`);
|
|
418
|
+
return { success: false, message: errorMsg };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Posts a client delegate keystone containing single-turn `custodianReqs`
|
|
424
|
+
* to register it with the server custodian node (`POST /api/identity/custodian/register-delegate`).
|
|
425
|
+
*/
|
|
426
|
+
public async postCustodianRegisterDelegate({
|
|
427
|
+
delegateKeystone
|
|
428
|
+
}: {
|
|
429
|
+
delegateKeystone: IbGib_V1;
|
|
430
|
+
}): Promise<{
|
|
431
|
+
success: boolean;
|
|
432
|
+
primaryTjpAddr?: string;
|
|
433
|
+
evolvedPrimaryKeystone?: IbGib_V1;
|
|
434
|
+
primaryGraph?: FlatIbGibGraph;
|
|
435
|
+
message?: string;
|
|
436
|
+
}> {
|
|
437
|
+
const lc = `${this.lc}[${this.postCustodianRegisterDelegate.name}]`;
|
|
438
|
+
try {
|
|
439
|
+
const url = '/api/identity/custodian/register-delegate';
|
|
440
|
+
const response = await fetch(url, {
|
|
441
|
+
method: 'POST',
|
|
442
|
+
headers: {
|
|
443
|
+
'Content-Type': 'application/json',
|
|
444
|
+
},
|
|
445
|
+
body: JSON.stringify({ delegateKeystone }),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const data = await this.parseResponseJson(response, url);
|
|
449
|
+
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
const errorMsg = data.message || data.error || 'Custodian delegate registration failed';
|
|
452
|
+
console.error(`${lc} custodian delegate registration failed: ${errorMsg}`);
|
|
453
|
+
return { success: false, message: errorMsg };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
success: true,
|
|
458
|
+
primaryTjpAddr: data.primaryTjpAddr,
|
|
459
|
+
evolvedPrimaryKeystone: data.evolvedPrimaryKeystone,
|
|
460
|
+
primaryGraph: data.primaryGraph,
|
|
461
|
+
message: data.message
|
|
462
|
+
};
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const errorMsg = extractErrorMsg(error);
|
|
465
|
+
console.error(`${lc} post custodian register delegate error: ${errorMsg}`);
|
|
466
|
+
return { success: false, message: errorMsg };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Retrieves safe public SSO client configuration (`GET /api/identity/sso/config`).
|
|
472
|
+
*/
|
|
473
|
+
public async getSsoConfig(): Promise<{
|
|
474
|
+
success: boolean;
|
|
475
|
+
providers?: Record<string, { providerId: string; clientId: string; authUrl: string }>;
|
|
476
|
+
message?: string;
|
|
477
|
+
}> {
|
|
478
|
+
const lc = `${this.lc}[${this.getSsoConfig.name}]`;
|
|
479
|
+
try {
|
|
480
|
+
const url = '/api/identity/sso/config';
|
|
481
|
+
const response = await fetch(url, { method: 'GET' });
|
|
482
|
+
const data = await this.parseResponseJson(response, url);
|
|
483
|
+
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
const errorMsg = data.message || data.error || 'Failed to fetch SSO config';
|
|
486
|
+
return { success: false, message: errorMsg };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
success: true,
|
|
491
|
+
providers: data.providers
|
|
492
|
+
};
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const errorMsg = extractErrorMsg(error);
|
|
495
|
+
console.error(`${lc} fetch SSO config error: ${errorMsg}`);
|
|
496
|
+
return { success: false, message: errorMsg };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Posts an OAuth authorization code (`POST /api/identity/sso/login`)
|
|
502
|
+
* to complete SSO token exchange and obtain custodian requirements.
|
|
503
|
+
*/
|
|
504
|
+
public async postSsoLogin({
|
|
505
|
+
code,
|
|
506
|
+
providerId,
|
|
507
|
+
redirectUri
|
|
508
|
+
}: {
|
|
509
|
+
code: string;
|
|
510
|
+
providerId: string;
|
|
511
|
+
redirectUri: string;
|
|
512
|
+
}): Promise<{
|
|
513
|
+
success: boolean;
|
|
514
|
+
isNewIdentity?: boolean;
|
|
515
|
+
primaryTjpAddr?: string;
|
|
516
|
+
primaryKeystone?: IbGib_V1;
|
|
517
|
+
primaryGraph?: FlatIbGibGraph;
|
|
518
|
+
custodianReqs?: any;
|
|
519
|
+
userInfo?: any;
|
|
520
|
+
message?: string;
|
|
521
|
+
}> {
|
|
522
|
+
const lc = `${this.lc}[${this.postSsoLogin.name}]`;
|
|
523
|
+
try {
|
|
524
|
+
const url = '/api/identity/sso/login';
|
|
525
|
+
const response = await fetch(url, {
|
|
526
|
+
method: 'POST',
|
|
527
|
+
headers: {
|
|
528
|
+
'Content-Type': 'application/json',
|
|
529
|
+
},
|
|
530
|
+
body: JSON.stringify({ code, providerId, redirectUri }),
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const data = await this.parseResponseJson(response, url);
|
|
534
|
+
|
|
535
|
+
if (!response.ok) {
|
|
536
|
+
const errorMsg = data.message || data.error || 'SSO login code exchange failed';
|
|
537
|
+
console.error(`${lc} SSO login code exchange failed: ${errorMsg}`);
|
|
538
|
+
return { success: false, message: errorMsg };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
success: true,
|
|
543
|
+
isNewIdentity: !!data.isNewIdentity,
|
|
544
|
+
primaryTjpAddr: data.primaryTjpAddr,
|
|
545
|
+
primaryKeystone: data.primaryKeystone,
|
|
546
|
+
primaryGraph: data.primaryGraph,
|
|
547
|
+
custodianReqs: data.custodianReqs,
|
|
548
|
+
userInfo: data.userInfo,
|
|
549
|
+
message: data.message
|
|
550
|
+
};
|
|
551
|
+
} catch (error) {
|
|
552
|
+
const errorMsg = extractErrorMsg(error);
|
|
553
|
+
console.error(`${lc} post SSO login error: ${errorMsg}`);
|
|
554
|
+
return { success: false, message: errorMsg };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
363
557
|
}
|
|
364
558
|
|
|
365
559
|
let _apiBridge: IbGibApiBridge | undefined;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module identity/custodian-delegate-helper
|
|
3
|
+
*
|
|
4
|
+
* Client-side helper functions for deriving and storing local cryptographic delegate secrets
|
|
5
|
+
* for full-custodian / passwordless identities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getUUID, hash, HashAlgorithm, extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
9
|
+
|
|
10
|
+
const LOCAL_STORAGE_PREFIX = 'ibgib_custodian_delegate_secret_';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves or generates a high-entropy local delegate secret for a passwordless custodian identity.
|
|
14
|
+
* Stored locally in `localStorage` keyed by `primaryTjpAddr`.
|
|
15
|
+
*
|
|
16
|
+
* @param primaryTjpAddr The primary identity keystone TJP address (`ib^gib`).
|
|
17
|
+
*/
|
|
18
|
+
export async function deriveLocalCustodianDelegateSecret({
|
|
19
|
+
primaryTjpAddr,
|
|
20
|
+
}: {
|
|
21
|
+
primaryTjpAddr: string;
|
|
22
|
+
}): Promise<string> {
|
|
23
|
+
const lc = `[${deriveLocalCustodianDelegateSecret.name}]`;
|
|
24
|
+
try {
|
|
25
|
+
if (!primaryTjpAddr) {
|
|
26
|
+
throw new Error('primaryTjpAddr is required to derive local custodian delegate secret (E: a1b2c3d4e5f67890123456789a000101)');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const key = `${LOCAL_STORAGE_PREFIX}${primaryTjpAddr}`;
|
|
30
|
+
const existingSecret = localStorage.getItem(key);
|
|
31
|
+
if (existingSecret) {
|
|
32
|
+
return existingSecret;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Generate high-entropy 512-bit SHA hash secret from fresh UUIDs
|
|
36
|
+
const rawEntropy = `${await getUUID()}-${await getUUID()}-${await getUUID()}-${Date.now()}`;
|
|
37
|
+
const newSecret = await hash({
|
|
38
|
+
s: rawEntropy,
|
|
39
|
+
algorithm: HashAlgorithm.sha_512
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
localStorage.setItem(key, newSecret);
|
|
43
|
+
return newSecret;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Removes the stored local delegate secret for a custodian identity.
|
|
52
|
+
*/
|
|
53
|
+
export function clearLocalCustodianDelegateSecret({
|
|
54
|
+
primaryTjpAddr,
|
|
55
|
+
}: {
|
|
56
|
+
primaryTjpAddr: string;
|
|
57
|
+
}): void {
|
|
58
|
+
if (!primaryTjpAddr) { return; }
|
|
59
|
+
const key = `${LOCAL_STORAGE_PREFIX}${primaryTjpAddr}`;
|
|
60
|
+
localStorage.removeItem(key);
|
|
61
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module identity/sso-popup-helper
|
|
3
|
+
*
|
|
4
|
+
* DRY client helper for initiating OAuth2 SSO authentication via a popup window
|
|
5
|
+
* and capturing the returned authorization code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
9
|
+
import { getApiBridge } from "../api/ibgib-api-bridge.mjs";
|
|
10
|
+
|
|
11
|
+
export interface SsoPopupOpts {
|
|
12
|
+
providerId: 'google' | 'github';
|
|
13
|
+
parentTjpAddr?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SsoPopupResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
code?: string;
|
|
19
|
+
stateObj?: any;
|
|
20
|
+
redirectUri?: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Executes an OAuth2 SSO login/link authorization flow using a popup window.
|
|
26
|
+
*
|
|
27
|
+
* 1. Queries `GET /api/identity/sso/config` for safe public client configs.
|
|
28
|
+
* 2. Constructs the provider authorization URL with encoded state payload.
|
|
29
|
+
* 3. Opens a 600x700 popup window and listens for `sso-oauth-callback` postMessage.
|
|
30
|
+
* 4. Returns `{ success: true, code, stateObj, redirectUri }` upon authorization.
|
|
31
|
+
*/
|
|
32
|
+
export async function executeSsoOAuthPopup({
|
|
33
|
+
providerId,
|
|
34
|
+
parentTjpAddr,
|
|
35
|
+
}: SsoPopupOpts): Promise<SsoPopupResult> {
|
|
36
|
+
const lc = `[${executeSsoOAuthPopup.name}]`;
|
|
37
|
+
try {
|
|
38
|
+
// 1. Fetch public SSO configs from server
|
|
39
|
+
const apiBridge = getApiBridge();
|
|
40
|
+
const resConfig = await apiBridge.getSsoConfig();
|
|
41
|
+
if (!resConfig.success || !resConfig.providers) {
|
|
42
|
+
return { success: false, message: resConfig.message || 'SSO configuration retrieval failed.' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const providers = resConfig.providers as any;
|
|
46
|
+
const providerConfig = Array.isArray(providers)
|
|
47
|
+
? providers.find((p: any) => p.providerId === providerId)
|
|
48
|
+
: providers[providerId];
|
|
49
|
+
|
|
50
|
+
if (!providerConfig) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
message: `SSO provider '${providerId}' is not configured on this server environment.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Generate high-entropy user nonce & state
|
|
58
|
+
/**
|
|
59
|
+
* {@link getUUID} uses {@link globalThis.crypto.getRandomValues}
|
|
60
|
+
*/
|
|
61
|
+
const userNonce = await getUUID();
|
|
62
|
+
const stateObj = {
|
|
63
|
+
providerId,
|
|
64
|
+
parentTjpAddr,
|
|
65
|
+
userNonce
|
|
66
|
+
};
|
|
67
|
+
const state = btoa(JSON.stringify(stateObj));
|
|
68
|
+
|
|
69
|
+
// 3. Construct Auth URL if not pre-constructed
|
|
70
|
+
let authUrl = providerConfig.authUrl;
|
|
71
|
+
if (!authUrl.includes('client_id=')) {
|
|
72
|
+
authUrl = `${authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
|
|
73
|
+
} else if (!authUrl.includes('state=')) {
|
|
74
|
+
authUrl = `${authUrl}&state=${encodeURIComponent(state)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Open popup window
|
|
78
|
+
const popup = window.open(
|
|
79
|
+
authUrl,
|
|
80
|
+
'sso-oauth-popup',
|
|
81
|
+
'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!popup) {
|
|
85
|
+
return { success: false, message: 'Popup blocked by browser. Please enable popups for this site.' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 5. Promise wrapper listening for postMessage or window closure
|
|
89
|
+
return await new Promise<SsoPopupResult>((resolve) => {
|
|
90
|
+
let checkClosedInterval: any;
|
|
91
|
+
|
|
92
|
+
const messageListener = (event: MessageEvent) => {
|
|
93
|
+
if (event.origin !== window.location.origin) return;
|
|
94
|
+
const callbackData = event.data;
|
|
95
|
+
if (callbackData && callbackData.type === 'sso-oauth-callback') {
|
|
96
|
+
window.removeEventListener('message', messageListener);
|
|
97
|
+
clearInterval(checkClosedInterval);
|
|
98
|
+
resolve({
|
|
99
|
+
success: true,
|
|
100
|
+
code: callbackData.code,
|
|
101
|
+
stateObj: callbackData.stateObj || stateObj,
|
|
102
|
+
redirectUri: providerConfig.redirectUri
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
window.addEventListener('message', messageListener);
|
|
108
|
+
|
|
109
|
+
checkClosedInterval = setInterval(() => {
|
|
110
|
+
if (popup.closed) {
|
|
111
|
+
clearInterval(checkClosedInterval);
|
|
112
|
+
window.removeEventListener('message', messageListener);
|
|
113
|
+
resolve({ success: false, message: 'Authentication cancelled (window closed).' });
|
|
114
|
+
}
|
|
115
|
+
}, 500);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const emsg = extractErrorMsg(error);
|
|
120
|
+
console.error(`${lc} ${emsg}`);
|
|
121
|
+
return { success: false, message: emsg };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Checks if the current window was opened as an OAuth popup (`window.opener` exists)
|
|
127
|
+
* and has received an authorization code in the URL query string (`?code=...`).
|
|
128
|
+
* If so, posts `sso-oauth-callback` to the parent window and closes itself.
|
|
129
|
+
*/
|
|
130
|
+
export function checkAndNotifyPopupOpener(): boolean {
|
|
131
|
+
if (typeof window === 'undefined' || !window.opener) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const url = new URL(window.location.href);
|
|
136
|
+
const code = url.searchParams.get('code');
|
|
137
|
+
if (!code) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const rawState = url.searchParams.get('state');
|
|
142
|
+
let stateObj: any;
|
|
143
|
+
if (rawState) {
|
|
144
|
+
try { stateObj = JSON.parse(atob(rawState)); } catch { }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
window.opener.postMessage({
|
|
148
|
+
type: 'sso-oauth-callback',
|
|
149
|
+
code,
|
|
150
|
+
stateObj
|
|
151
|
+
}, window.location.origin);
|
|
152
|
+
|
|
153
|
+
window.close();
|
|
154
|
+
return true;
|
|
155
|
+
}
|
package/src/index.mts
CHANGED
|
@@ -18,6 +18,9 @@ export * from "./api/ibgib-api-bridge.mjs";
|
|
|
18
18
|
|
|
19
19
|
// Identity & Sync services
|
|
20
20
|
export * from "./identity/keystone-sync-service.mjs";
|
|
21
|
+
export * from "./identity/custodian-delegate-helper.mjs";
|
|
22
|
+
// export * from "./identity/sso-callback-helper.mjs";
|
|
23
|
+
export * from "./identity/sso-popup-helper.mjs";
|
|
21
24
|
|
|
22
25
|
// Identity UI components
|
|
23
26
|
export * from "./ui/component/identity/index.mjs";
|
|
@@ -277,7 +277,11 @@ export class AddDeviceComponentInstance
|
|
|
277
277
|
const primaryTjpAddr = getTjpAddr({ ibGib: primaryKeystone, defaultIfNone: 'incomingAddr' });
|
|
278
278
|
if (!primaryTjpAddr) { throw new Error(`(UNEXPECTED) primaryTjpAddr falsy? couldn't get tjpAddr from primaryKeystone? (E: bc8cc8678e1ce4194ab02646160fde26)`); }
|
|
279
279
|
const delegateSecret = await deriveDelegateSecret({ masterSecret });
|
|
280
|
-
const
|
|
280
|
+
const syncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
281
|
+
if (!syncProfileName || !syncProfileName.trim()) {
|
|
282
|
+
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 8f9e0a1b2c3d4e5f67890123456789ab)');
|
|
283
|
+
}
|
|
284
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(syncProfileName.trim() as any)
|
|
281
285
|
.withUsername(`${primaryUsername}-sync-${deviceTag}`)
|
|
282
286
|
.withDescription(`Sync Delegate for ${primaryUsername} on ${deviceTag}`)
|
|
283
287
|
.withDetails({
|
|
@@ -146,7 +146,8 @@ export class IdentityHeaderComponentInstance
|
|
|
146
146
|
};
|
|
147
147
|
this.keystoneCache.set(addr, details);
|
|
148
148
|
return details;
|
|
149
|
-
} catch {
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`${this.lc}[getKeystoneDetails] Registered keystone address ${addr} not found in local browser space: ${extractErrorMsg(error)} (E: a123456789abcdef0123456789c0002)`);
|
|
150
151
|
return { username: '', description: '', isPrimary: false };
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -10,17 +10,105 @@ h2 {
|
|
|
10
10
|
color: var(--clr-accent, #50fa7b);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
.mode-tabs {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 0.5rem;
|
|
16
|
+
background: rgba(0, 0, 0, 0.25);
|
|
17
|
+
padding: 0.25rem;
|
|
18
|
+
border-radius: 10px;
|
|
19
|
+
border: 1px solid var(--clr-border, rgba(255, 255, 255, 0.1));
|
|
20
|
+
margin-bottom: 1rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.mode-tab {
|
|
24
|
+
flex: 1;
|
|
25
|
+
padding: 0.6rem 0.75rem;
|
|
26
|
+
border: none;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
background: transparent;
|
|
29
|
+
color: var(--clr-text-secondary, #a6adc8);
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
font-size: 0.9rem;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition: all 0.2s ease;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.mode-tab:hover {
|
|
37
|
+
color: var(--clr-text, #cdd6f4);
|
|
38
|
+
background: rgba(255, 255, 255, 0.05);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.mode-tab.active {
|
|
42
|
+
background: #45475a;
|
|
43
|
+
color: #89b4fa;
|
|
44
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
.subtitle {
|
|
14
48
|
color: var(--clr-text-secondary, #a6adc8);
|
|
15
|
-
font-size: 0.
|
|
49
|
+
font-size: 0.92rem;
|
|
16
50
|
margin: 0;
|
|
51
|
+
line-height: 1.45;
|
|
17
52
|
}
|
|
18
53
|
|
|
19
54
|
.content {
|
|
20
|
-
margin-top: 1.
|
|
55
|
+
margin-top: 1.25rem;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
gap: 1.25rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tab-panel.hidden, .hidden {
|
|
62
|
+
display: none !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sso-buttons {
|
|
21
66
|
display: flex;
|
|
22
67
|
flex-direction: column;
|
|
23
|
-
gap:
|
|
68
|
+
gap: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sso-btn {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
gap: 0.75rem;
|
|
76
|
+
padding: 0.8rem 1.25rem;
|
|
77
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
background: #1e1e2e;
|
|
80
|
+
color: #cdd6f4;
|
|
81
|
+
font-size: 0.95rem;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: transform 0.15s ease, background 0.15s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sso-btn:hover {
|
|
88
|
+
transform: translateY(-1px);
|
|
89
|
+
background: #313244;
|
|
90
|
+
border-color: #89b4fa;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.divider {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
text-align: center;
|
|
97
|
+
margin: 0.5rem 0;
|
|
98
|
+
color: #a6adc8;
|
|
99
|
+
font-size: 0.75rem;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
letter-spacing: 1px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.divider::before, .divider::after {
|
|
105
|
+
content: '';
|
|
106
|
+
flex: 1;
|
|
107
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.divider span {
|
|
111
|
+
padding: 0 0.75rem;
|
|
24
112
|
}
|
|
25
113
|
|
|
26
114
|
.input-group {
|
|
@@ -34,8 +122,25 @@ h2 {
|
|
|
34
122
|
color: var(--clr-text-secondary, #a6adc8);
|
|
35
123
|
}
|
|
36
124
|
|
|
125
|
+
.text-input {
|
|
126
|
+
width: 100%;
|
|
127
|
+
padding: 0.75rem 1rem;
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
130
|
+
background: #11111b;
|
|
131
|
+
color: #cdd6f4;
|
|
132
|
+
font-size: 0.95rem;
|
|
133
|
+
box-sizing: border-box;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.text-input:focus {
|
|
137
|
+
outline: none;
|
|
138
|
+
border-color: #89b4fa;
|
|
139
|
+
box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.2);
|
|
140
|
+
}
|
|
141
|
+
|
|
37
142
|
.action-row {
|
|
38
|
-
margin-top:
|
|
143
|
+
margin-top: 0.5rem;
|
|
39
144
|
width: 100%;
|
|
40
145
|
}
|
|
41
146
|
|
|
@@ -65,10 +170,6 @@ h2 {
|
|
|
65
170
|
margin-top: 0.5rem;
|
|
66
171
|
}
|
|
67
172
|
|
|
68
|
-
.status-area.hidden {
|
|
69
|
-
display: none;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
173
|
.result-area {
|
|
73
174
|
padding: 1.5rem;
|
|
74
175
|
background: rgba(255, 255, 255, 0.03);
|
|
@@ -77,7 +178,6 @@ h2 {
|
|
|
77
178
|
display: flex;
|
|
78
179
|
flex-direction: column;
|
|
79
180
|
gap: 0.75rem;
|
|
80
|
-
transition: opacity var(--t-med, 0.2s ease);
|
|
81
181
|
}
|
|
82
182
|
|
|
83
183
|
.result-area.collapsed {
|
|
@@ -99,18 +199,4 @@ h2 {
|
|
|
99
199
|
input::placeholder {
|
|
100
200
|
color: rgba(255, 255, 255, 0.35);
|
|
101
201
|
opacity: 1;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.section-code-verification {
|
|
105
|
-
margin-top: 1.5rem;
|
|
106
|
-
padding: 1.25rem;
|
|
107
|
-
border: 2px dashed #f59e0b;
|
|
108
|
-
background: rgba(245, 158, 11, 0.05);
|
|
109
|
-
box-shadow: 0 0 15px rgba(245, 158, 11, 0.2);
|
|
110
|
-
border-radius: 12px;
|
|
111
|
-
transition: all 0.3s ease;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.section-code-verification.hidden {
|
|
115
|
-
display: none;
|
|
116
202
|
}
|