@ibgib/web-gib 0.0.56 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/api/api-index.mjs +1 -1
- package/dist/api/api-index.mjs.map +1 -1
- package/dist/api/ibgib-api-bridge.d.mts +97 -0
- package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
- package/dist/api/ibgib-api-bridge.mjs +205 -3
- 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.html +6 -7
- package/dist/ui/component/identity/add-device/add-device.mjs +13 -10
- 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 +114 -9
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +23 -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 -50
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +529 -83
- 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 +19 -61
- 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/api-index.mts +1 -1
- package/src/api/ibgib-api-bridge.mts +288 -4
- package/src/identity/custodian-delegate-helper.mts +61 -0
- package/src/identity/sso/{sso-custodian-service.mts → sso-custodian-service.mts.bak} +65 -38
- package/src/identity/sso/{sso-custodian-service.respec.mts → sso-custodian-service.respec.mts.bak} +15 -14
- package/src/identity/sso-popup-helper.mts +155 -0
- package/src/index.mts +3 -0
- package/src/ui/component/identity/add-device/add-device.html +6 -7
- package/src/ui/component/identity/add-device/add-device.mts +17 -16
- package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +114 -9
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -50
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +599 -100
- package/src/ui/component/identity/keystone-details/keystone-details.mts +26 -77
- package/dist/identity/sso/sso-config-helper.d.mts +0 -7
- package/dist/identity/sso/sso-config-helper.d.mts.map +0 -1
- package/dist/identity/sso/sso-config-helper.mjs +0 -42
- package/dist/identity/sso/sso-config-helper.mjs.map +0 -1
- package/dist/identity/sso/sso-custodian-service.d.mts +0 -139
- package/dist/identity/sso/sso-custodian-service.d.mts.map +0 -1
- package/dist/identity/sso/sso-custodian-service.mjs +0 -443
- package/dist/identity/sso/sso-custodian-service.mjs.map +0 -1
- package/dist/identity/sso/sso-custodian-service.respec.d.mts +0 -2
- package/dist/identity/sso/sso-custodian-service.respec.d.mts.map +0 -1
- package/dist/identity/sso/sso-custodian-service.respec.mjs +0 -254
- package/dist/identity/sso/sso-custodian-service.respec.mjs.map +0 -1
- /package/src/identity/sso/{sso-config-helper.mts → sso-config-helper.mts.bak} +0 -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
|
|
|
@@ -27,7 +28,7 @@ export class IbGibApiBridge {
|
|
|
27
28
|
/**
|
|
28
29
|
* Posts a single genesis keystone ibgib to the server to create a new domain.
|
|
29
30
|
*/
|
|
30
|
-
public async postGenesisKeystone(keystoneIbGib: IbGib_V1): Promise<{ success: boolean; message?: string; addr?: string }> {
|
|
31
|
+
public async postGenesisKeystone(keystoneIbGib: IbGib_V1): Promise<{ success: boolean; pending?: boolean; statusCode?: number; message?: string; addr?: string }> {
|
|
31
32
|
const lc = `${this.lc}[${this.postGenesisKeystone.name}]`;
|
|
32
33
|
try {
|
|
33
34
|
if (logalot) { console.log(`${lc} starting... (I: eecd082fc4a4381c8857928e8b090826)`); }
|
|
@@ -46,11 +47,11 @@ export class IbGibApiBridge {
|
|
|
46
47
|
if (!response.ok) {
|
|
47
48
|
const errorMsg = data.message || data.error || 'Unknown error';
|
|
48
49
|
console.error(`${lc} server rejected genesis keystone: ${errorMsg}`);
|
|
49
|
-
return { success: false, message: errorMsg, addr: data.addr };
|
|
50
|
+
return { success: false, message: errorMsg, addr: data.addr, statusCode: response.status };
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
if (logalot) { console.log(`${lc} genesis keystone posted successfully
|
|
53
|
-
return { success: true, addr: data.addr };
|
|
53
|
+
if (logalot) { console.log(`${lc} genesis keystone posted successfully. pending: ${data.pending}`); }
|
|
54
|
+
return { success: true, pending: !!data.pending, statusCode: response.status, addr: data.addr, message: data.message };
|
|
54
55
|
|
|
55
56
|
} catch (error) {
|
|
56
57
|
const errorMsg = extractErrorMsg(error);
|
|
@@ -270,6 +271,289 @@ export class IbGibApiBridge {
|
|
|
270
271
|
return { success: false, message: errorMsg };
|
|
271
272
|
}
|
|
272
273
|
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Posts a request to generate a 6-digit verification code, save it to pending staging, and dispatch an email.
|
|
277
|
+
*/
|
|
278
|
+
public async postVerificationCode({
|
|
279
|
+
email,
|
|
280
|
+
primaryTjpAddr
|
|
281
|
+
}: {
|
|
282
|
+
email: string;
|
|
283
|
+
primaryTjpAddr?: string;
|
|
284
|
+
}): Promise<{ success: boolean; message?: string }> {
|
|
285
|
+
const lc = `${this.lc}[${this.postVerificationCode.name}]`;
|
|
286
|
+
try {
|
|
287
|
+
const url = '/api/email/verification-code';
|
|
288
|
+
const response = await fetch(url, {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-Type': 'application/json',
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({ email, primaryTjpAddr }),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const data = await this.parseResponseJson(response, url);
|
|
297
|
+
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
const errorMsg = data.message || data.error || 'Verification code request failed';
|
|
300
|
+
console.error(`${lc} server verification code post failed: ${errorMsg}`);
|
|
301
|
+
return { success: false, message: errorMsg };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { success: true, message: data.message };
|
|
305
|
+
} catch (error) {
|
|
306
|
+
const errorMsg = extractErrorMsg(error);
|
|
307
|
+
console.error(`${lc} post verification code error: ${errorMsg}`);
|
|
308
|
+
return { success: false, message: errorMsg };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Alias for postVerificationCode (deprecated name).
|
|
314
|
+
*
|
|
315
|
+
* @deprecated Use postVerificationCode instead.
|
|
316
|
+
*/
|
|
317
|
+
public async postMagicLink(opts: {
|
|
318
|
+
email: string;
|
|
319
|
+
primaryTjpAddr: string;
|
|
320
|
+
}): Promise<{ success: boolean; message?: string }> {
|
|
321
|
+
return this.postVerificationCode(opts);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Posts a 6-digit verification code to activate an identity and verify email.
|
|
326
|
+
*/
|
|
327
|
+
public async postVerifyCode({
|
|
328
|
+
email,
|
|
329
|
+
primaryTjpAddr,
|
|
330
|
+
code
|
|
331
|
+
}: {
|
|
332
|
+
email: string;
|
|
333
|
+
primaryTjpAddr: string;
|
|
334
|
+
code: string;
|
|
335
|
+
}): Promise<{ success: boolean; message?: string }> {
|
|
336
|
+
const lc = `${this.lc}[${this.postVerifyCode.name}]`;
|
|
337
|
+
try {
|
|
338
|
+
const url = '/api/email/verify-code';
|
|
339
|
+
const response = await fetch(url, {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: {
|
|
342
|
+
'Content-Type': 'application/json',
|
|
343
|
+
},
|
|
344
|
+
body: JSON.stringify({ email, primaryTjpAddr, code }),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const data = await this.parseResponseJson(response, url);
|
|
348
|
+
|
|
349
|
+
if (!response.ok) {
|
|
350
|
+
const errorMsg = data.message || data.error || 'Code verification failed';
|
|
351
|
+
console.error(`${lc} server verify-code failed: ${errorMsg}`);
|
|
352
|
+
return { success: false, message: errorMsg };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (logalot) { console.log(`${lc} verification code accepted.`); }
|
|
356
|
+
return { success: true, message: data.message };
|
|
357
|
+
|
|
358
|
+
} catch (error) {
|
|
359
|
+
const errorMsg = extractErrorMsg(error);
|
|
360
|
+
console.error(`${lc} fetch error: ${errorMsg}`);
|
|
361
|
+
return { success: false, message: errorMsg };
|
|
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
|
+
}
|
|
273
557
|
}
|
|
274
558
|
|
|
275
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
|
+
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { createPublicKey, verify } from 'node:crypto';
|
|
2
|
+
|
|
2
3
|
import { hash, HashAlgorithm, extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
3
4
|
import { kdf_recursiveSaltWrap } from '@ibgib/core-gib/dist/keystone/kdf/kdf-helpers.mjs';
|
|
4
5
|
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { POOL_ID_CUSTODIAN_MANAGE, KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE, POOL_ID_MANAGE, KEYSTONE_POOL_ID_REGEXP } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
8
|
-
import { createStandardPoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-config-builder.mjs';
|
|
6
|
+
import { KeystoneChallengePool, KeystonePoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
|
|
7
|
+
import { KEYSTONE_VERB_MANAGE, KEYSTONE_VERB_REVOKE, POOL_ID_MANAGE, KEYSTONE_POOL_ID_REGEXP } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
9
8
|
import { generateOpaqueChallengeId } from '@ibgib/core-gib/dist/keystone/keystone-helpers.mjs';
|
|
10
9
|
import { KeystoneStrategyFactory } from '@ibgib/core-gib/dist/keystone/strategy/keystone-strategy-factory.mjs';
|
|
11
10
|
import { MetaspaceService } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs';
|
|
12
11
|
import { IbGibSpaceAny } from '@ibgib/core-gib/dist/witness/space/space-base-v1.mjs';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
SsoProviderId,
|
|
16
|
-
OAuthUserInfo,
|
|
17
|
-
SsoProviderServerConfig,
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
SsoServerConfig, SsoProviderId, OAuthUserInfo, SsoProviderServerConfig,
|
|
18
15
|
SsoProviderClientConfig
|
|
19
16
|
} from './sso-types.mjs';
|
|
20
17
|
|
|
@@ -37,7 +34,7 @@ interface JwksResponse {
|
|
|
37
34
|
*/
|
|
38
35
|
export class SsoCustodianService {
|
|
39
36
|
private lc = `[${SsoCustodianService.name}]`;
|
|
40
|
-
|
|
37
|
+
|
|
41
38
|
// In-memory cache for JSON Web Key Sets (JWKS) per provider
|
|
42
39
|
private jwksCache: Map<SsoProviderId, { keys: JwkKey[]; expiresAt: number }> = new Map();
|
|
43
40
|
private jwksTtlMs = 24 * 60 * 60 * 1000; // Cache certs for 24 hours
|
|
@@ -47,19 +44,19 @@ export class SsoCustodianService {
|
|
|
47
44
|
* - Visibility: Private (Contains high-entropy secrets like `ssoServerKdfSecret` and `sessionSecret`).
|
|
48
45
|
* - Generator: Server (Bootstrapped from node environment variables on server initialization).
|
|
49
46
|
*/
|
|
50
|
-
constructor(private config: SsoServerConfig) {}
|
|
47
|
+
constructor(private config: SsoServerConfig) { }
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
50
|
* Exchanges an authorization code for an OAuth2 token and retrieves normalized user info.
|
|
54
|
-
*
|
|
51
|
+
*
|
|
55
52
|
* @param providerId The string identifier of the OAuth provider (e.g. 'google' or 'github').
|
|
56
53
|
* - Visibility: Public (Non-sensitive metadata string).
|
|
57
54
|
* - Generator: Client (Sent from browser UI selection).
|
|
58
|
-
*
|
|
55
|
+
*
|
|
59
56
|
* @param code The temporary authorization code returned by the OAuth provider.
|
|
60
57
|
* - Visibility: Private (Confidential short-lived bearer credential. Must be kept private).
|
|
61
58
|
* - Generator: Provider (Created by provider's OAuth page and sent to client redirect URI).
|
|
62
|
-
*
|
|
59
|
+
*
|
|
63
60
|
* @param redirectUri The redirect URI registered in the provider portal.
|
|
64
61
|
* - Visibility: Public (Configuration URL).
|
|
65
62
|
* - Generator: Client/Server (Derived from environmental configuration and sent by client).
|
|
@@ -97,7 +94,7 @@ export class SsoCustodianService {
|
|
|
97
94
|
/**
|
|
98
95
|
* Directly validates a Google ID Token (JWT) locally and returns normalized user info.
|
|
99
96
|
* Useful for client-side authentication handshakes.
|
|
100
|
-
*
|
|
97
|
+
*
|
|
101
98
|
* @param idToken The raw base64-encoded signed JSON Web Token (JWT) ID token returned by Google.
|
|
102
99
|
* - Visibility: Private (Sensitive bearer credential containing signature and claims. Must be kept private).
|
|
103
100
|
* - Generator: Provider (Generated and signed cryptographically by Google authentication servers).
|
|
@@ -125,6 +122,10 @@ export class SsoCustodianService {
|
|
|
125
122
|
throw new Error(`Google JWT is missing subject claim ('sub'). (E: s28900abfefd23e89cfa1f89)`);
|
|
126
123
|
}
|
|
127
124
|
|
|
125
|
+
if (payload.email && payload.email_verified !== true && (payload.email_verified as any) !== 'true') {
|
|
126
|
+
throw new Error(`Google email is not verified: ${payload.email}. (E: v28900abfefd23e89cfb2f90)`);
|
|
127
|
+
}
|
|
128
|
+
|
|
128
129
|
return {
|
|
129
130
|
providerKey: `google:${payload.sub}`,
|
|
130
131
|
providerId: 'google',
|
|
@@ -137,22 +138,22 @@ export class SsoCustodianService {
|
|
|
137
138
|
|
|
138
139
|
/**
|
|
139
140
|
* Internal: Exchange authorization code for access token / ID token.
|
|
140
|
-
*
|
|
141
|
+
*
|
|
141
142
|
* @param providerConfig Server configuration settings for the specific provider.
|
|
142
143
|
* - Visibility: Private (Contains provider client secrets. Must stay server-side).
|
|
143
144
|
* - Generator: Server (Configured from node environment variables).
|
|
144
|
-
*
|
|
145
|
+
*
|
|
145
146
|
* @param code The temporary authorization code to exchange.
|
|
146
147
|
* - Visibility: Private (Short-lived credential. Must be kept private).
|
|
147
148
|
* - Generator: Provider (Via OAuth flow).
|
|
148
|
-
*
|
|
149
|
+
*
|
|
149
150
|
* @param redirectUri The redirect URI registered for the exchange.
|
|
150
151
|
* - Visibility: Public (Configuration URL).
|
|
151
152
|
* - Generator: Client/Server (From app configuration).
|
|
152
153
|
*/
|
|
153
154
|
private async exchangeCodeForTokens(
|
|
154
|
-
providerConfig: SsoProviderServerConfig,
|
|
155
|
-
code: string,
|
|
155
|
+
providerConfig: SsoProviderServerConfig,
|
|
156
|
+
code: string,
|
|
156
157
|
redirectUri: string
|
|
157
158
|
): Promise<any> {
|
|
158
159
|
const body = new URLSearchParams({
|
|
@@ -188,17 +189,17 @@ export class SsoCustodianService {
|
|
|
188
189
|
|
|
189
190
|
/**
|
|
190
191
|
* Internal: Fetch GitHub user profile and normalize.
|
|
191
|
-
*
|
|
192
|
+
*
|
|
192
193
|
* @param providerConfig Server configuration settings for GitHub.
|
|
193
194
|
* - Visibility: Private (Contains GitHub client secret).
|
|
194
195
|
* - Generator: Server (From environment config).
|
|
195
|
-
*
|
|
196
|
+
*
|
|
196
197
|
* @param accessToken The active OAuth2 access token to authorize the API call.
|
|
197
198
|
* - Visibility: Private (Bearer token. Must be kept private).
|
|
198
199
|
* - Generator: Provider (GitHub token endpoint).
|
|
199
200
|
*/
|
|
200
201
|
private async fetchAndNormalizeGithubUser(
|
|
201
|
-
providerConfig: SsoProviderServerConfig,
|
|
202
|
+
providerConfig: SsoProviderServerConfig,
|
|
202
203
|
accessToken: string
|
|
203
204
|
): Promise<OAuthUserInfo> {
|
|
204
205
|
const res = await fetch(providerConfig.userInfoUrl, {
|
|
@@ -214,18 +215,44 @@ export class SsoCustodianService {
|
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
const rawUser = await res.json();
|
|
217
|
-
|
|
218
|
+
|
|
218
219
|
if (!rawUser.id) {
|
|
219
220
|
throw new Error(`GitHub user info response missing 'id' field.`);
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
const sub = String(rawUser.id);
|
|
224
|
+
let email = rawUser.email || undefined;
|
|
225
|
+
|
|
226
|
+
// If email is missing or private, fetch /user/emails to extract primary verified email
|
|
227
|
+
if (!email) {
|
|
228
|
+
try {
|
|
229
|
+
const emailsRes = await fetch('https://api.github.com/user/emails', {
|
|
230
|
+
headers: {
|
|
231
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
232
|
+
'User-Agent': 'space-gib-auth-agent'
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
if (emailsRes.ok) {
|
|
236
|
+
const emailsList: any[] = await emailsRes.json();
|
|
237
|
+
const primaryVerified = emailsList.find((e: any) => e.verified && e.primary) || emailsList.find((e: any) => e.verified);
|
|
238
|
+
if (primaryVerified?.email) {
|
|
239
|
+
email = primaryVerified.email;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.warn(`[${this.fetchAndNormalizeGithubUser.name}] Could not fetch GitHub /user/emails fallback: ${extractErrorMsg(err)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!email) {
|
|
248
|
+
throw new Error(`Your GitHub account does not have a verified primary email address. Please verify your primary email on GitHub and try again. (E: e28900abfefd23e89cfc3f01)`);
|
|
249
|
+
}
|
|
223
250
|
|
|
224
251
|
return {
|
|
225
252
|
providerKey: `github:${sub}`,
|
|
226
253
|
providerId: 'github',
|
|
227
254
|
sub,
|
|
228
|
-
email
|
|
255
|
+
email,
|
|
229
256
|
name: rawUser.name || rawUser.login || undefined,
|
|
230
257
|
picture: rawUser.avatar_url || undefined
|
|
231
258
|
};
|
|
@@ -233,11 +260,11 @@ export class SsoCustodianService {
|
|
|
233
260
|
|
|
234
261
|
/**
|
|
235
262
|
* Internal: Decodes and cryptographically verifies a JWT signature using JWKS public keys.
|
|
236
|
-
*
|
|
263
|
+
*
|
|
237
264
|
* @param token The raw base64-encoded JWT token to verify.
|
|
238
265
|
* - Visibility: Private (Sensitive token. Must be kept private).
|
|
239
266
|
* - Generator: Provider (Google / OIDC server).
|
|
240
|
-
*
|
|
267
|
+
*
|
|
241
268
|
* @param providerConfig The target provider server settings containing `jwksUrl`.
|
|
242
269
|
* - Visibility: Private (Contains secrets).
|
|
243
270
|
* - Generator: Server (From environment config).
|
|
@@ -299,7 +326,7 @@ export class SsoCustodianService {
|
|
|
299
326
|
|
|
300
327
|
/**
|
|
301
328
|
* Internal: Retrieves JWKS keys from cache or fetches them from the provider's jwksUrl.
|
|
302
|
-
*
|
|
329
|
+
*
|
|
303
330
|
* @param providerConfig The provider config containing the target `jwksUrl`.
|
|
304
331
|
* - Visibility: Private (Contains client secrets).
|
|
305
332
|
* - Generator: Server (From environment config).
|
|
@@ -307,7 +334,7 @@ export class SsoCustodianService {
|
|
|
307
334
|
private async getJwksKeys(providerConfig: SsoProviderServerConfig): Promise<JwkKey[]> {
|
|
308
335
|
const providerId = providerConfig.providerId;
|
|
309
336
|
const cached = this.jwksCache.get(providerId);
|
|
310
|
-
|
|
337
|
+
|
|
311
338
|
if (cached && cached.expiresAt > Date.now()) {
|
|
312
339
|
return cached.keys;
|
|
313
340
|
}
|
|
@@ -337,11 +364,11 @@ export class SsoCustodianService {
|
|
|
337
364
|
/**
|
|
338
365
|
* Derives a server-delegate master secret deterministically using the server's private KDF secret,
|
|
339
366
|
* the providerKey, and the user's public nonce.
|
|
340
|
-
*
|
|
367
|
+
*
|
|
341
368
|
* @param providerKey The unique key identifying the provider and user sub ID (e.g. `google:123...`).
|
|
342
369
|
* - Visibility: Public (Identifier saved on parent's delegate claim, safe for log/ledger storage).
|
|
343
370
|
* - Generator: Provider/Server (Derived by mapping provider user sub ID after authentication).
|
|
344
|
-
*
|
|
371
|
+
*
|
|
345
372
|
* @param userNonce The public, high-entropy nonce generated by the client to salt the KDF derivation.
|
|
346
373
|
* - Visibility: Public (Salt value stored on parent's delegate claim, safe for ledger storage).
|
|
347
374
|
* - Generator: Client (Generated offline on user's device).
|
|
@@ -362,11 +389,11 @@ export class SsoCustodianService {
|
|
|
362
389
|
/**
|
|
363
390
|
* Creates a public custodian challenge pool (custodian-manage) derived deterministically
|
|
364
391
|
* from the server's private KDF secret, providerKey, and userNonce.
|
|
365
|
-
*
|
|
392
|
+
*
|
|
366
393
|
* @param providerKey The unique key identifying the provider and user sub ID (e.g. `google:123...`).
|
|
367
394
|
* - Visibility: Public (Mapped identifier, safe for ledger).
|
|
368
395
|
* - Generator: Provider/Server (Derived after successful token exchange).
|
|
369
|
-
*
|
|
396
|
+
*
|
|
370
397
|
* @param userNonce The public, high-entropy nonce.
|
|
371
398
|
* - Visibility: Public (Derivation salt, safe for ledger).
|
|
372
399
|
* - Generator: Client (Generated on user's device).
|
|
@@ -412,7 +439,7 @@ export class SsoCustodianService {
|
|
|
412
439
|
|
|
413
440
|
// 4. Derive delegate secrets
|
|
414
441
|
const custodianSecret = await this.deriveServerDelegateSecret(providerKey, userNonce);
|
|
415
|
-
|
|
442
|
+
|
|
416
443
|
const poolSalt = await hash({
|
|
417
444
|
s: `${providerKey}:${userNonce}:salt`,
|
|
418
445
|
algorithm: HashAlgorithm.sha_256
|
|
@@ -460,14 +487,14 @@ export class SsoCustodianService {
|
|
|
460
487
|
|
|
461
488
|
/**
|
|
462
489
|
* Maps private server configuration to safe public client configurations.
|
|
463
|
-
*
|
|
490
|
+
*
|
|
464
491
|
* @param hostUrl The root URL host address of the server (e.g. `https://space-gib.localhost` or `https://ibgib.space`).
|
|
465
492
|
* - Visibility: Public (Non-sensitive hostname configuration).
|
|
466
493
|
* - Generator: Server (Extracted from the HTTP request context headers/protocol).
|
|
467
494
|
*/
|
|
468
495
|
getSsoClientConfigs(hostUrl: string): SsoProviderClientConfig[] {
|
|
469
496
|
const configs: SsoProviderClientConfig[] = [];
|
|
470
|
-
|
|
497
|
+
|
|
471
498
|
if (this.config.providers.google) {
|
|
472
499
|
configs.push({
|
|
473
500
|
providerId: 'google',
|
|
@@ -477,7 +504,7 @@ export class SsoCustodianService {
|
|
|
477
504
|
scope: 'openid email profile'
|
|
478
505
|
});
|
|
479
506
|
}
|
|
480
|
-
|
|
507
|
+
|
|
481
508
|
if (this.config.providers.github) {
|
|
482
509
|
configs.push({
|
|
483
510
|
providerId: 'github',
|
|
@@ -487,7 +514,7 @@ export class SsoCustodianService {
|
|
|
487
514
|
scope: 'read:user user:email'
|
|
488
515
|
});
|
|
489
516
|
}
|
|
490
|
-
|
|
517
|
+
|
|
491
518
|
return configs;
|
|
492
519
|
}
|
|
493
520
|
|