@ibgib/web-gib 0.0.56 → 0.0.57

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.
Files changed (46) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/api/api-index.mjs +1 -1
  4. package/dist/api/api-index.mjs.map +1 -1
  5. package/dist/api/ibgib-api-bridge.d.mts +35 -0
  6. package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
  7. package/dist/api/ibgib-api-bridge.mjs +70 -3
  8. package/dist/api/ibgib-api-bridge.mjs.map +1 -1
  9. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  10. package/dist/ui/component/identity/add-device/add-device.html +6 -7
  11. package/dist/ui/component/identity/add-device/add-device.mjs +13 -10
  12. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  13. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +19 -0
  14. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +1 -0
  15. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  16. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +10 -0
  17. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +164 -41
  18. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  19. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  20. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +9 -8
  21. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  22. package/package.json +2 -2
  23. package/src/AUTO-GENERATED-version.mts +1 -1
  24. package/src/api/api-index.mts +1 -1
  25. package/src/api/ibgib-api-bridge.mts +94 -4
  26. package/src/identity/sso/{sso-custodian-service.mts → sso-custodian-service.mts.bak} +65 -38
  27. package/src/identity/sso/{sso-custodian-service.respec.mts → sso-custodian-service.respec.mts.bak} +15 -14
  28. package/src/ui/component/identity/add-device/add-device.html +6 -7
  29. package/src/ui/component/identity/add-device/add-device.mts +17 -16
  30. package/src/ui/component/identity/keystone-creator/keystone-creator.css +19 -0
  31. package/src/ui/component/identity/keystone-creator/keystone-creator.html +10 -0
  32. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +180 -55
  33. package/src/ui/component/identity/keystone-details/keystone-details.mts +16 -15
  34. package/dist/identity/sso/sso-config-helper.d.mts +0 -7
  35. package/dist/identity/sso/sso-config-helper.d.mts.map +0 -1
  36. package/dist/identity/sso/sso-config-helper.mjs +0 -42
  37. package/dist/identity/sso/sso-config-helper.mjs.map +0 -1
  38. package/dist/identity/sso/sso-custodian-service.d.mts +0 -139
  39. package/dist/identity/sso/sso-custodian-service.d.mts.map +0 -1
  40. package/dist/identity/sso/sso-custodian-service.mjs +0 -443
  41. package/dist/identity/sso/sso-custodian-service.mjs.map +0 -1
  42. package/dist/identity/sso/sso-custodian-service.respec.d.mts +0 -2
  43. package/dist/identity/sso/sso-custodian-service.respec.d.mts.map +0 -1
  44. package/dist/identity/sso/sso-custodian-service.respec.mjs +0 -254
  45. package/dist/identity/sso/sso-custodian-service.respec.mjs.map +0 -1
  46. /package/src/identity/sso/{sso-config-helper.mts → sso-config-helper.mts.bak} +0 -0
@@ -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 { KeystoneProfileBuilder } from '@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs';
6
- import { KeystoneIbGib_V1, KeystoneChallengePool, KeystonePoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
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
- import {
14
- SsoServerConfig,
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: rawUser.email || undefined,
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
 
@@ -1,10 +1,11 @@
1
1
  import { generateKeyPairSync, createSign } from 'node:crypto';
2
- import {
3
- respecfully,
4
- ifWeMight,
5
- iReckon,
6
- firstOfAll,
7
- lastOfAll
2
+ import {
3
+ respecfully,
4
+ ifWeMight,
5
+ iReckon,
6
+ firstOfAll,
7
+ lastOfAll,
8
+ ifWe
8
9
  } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
9
10
  import { SsoCustodianService } from './sso-custodian-service.mjs';
10
11
  import { SsoServerConfig } from './sso-types.mjs';
@@ -24,7 +25,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
24
25
  modulusLength: 2048
25
26
  });
26
27
  mockPrivateKey = privateKey;
27
-
28
+
28
29
  // Export public key as JWK
29
30
  mockPublicKeyJwk = publicKey.export({ format: 'jwk' });
30
31
  mockPublicKeyJwk.kid = 'mock-kid-123';
@@ -58,7 +59,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
58
59
  }
59
60
  });
60
61
 
61
- await ifWeMight(sir, 'verify dynamic RSA RS256 JWT signature successfully', async () => {
62
+ await ifWe(sir, 'verify dynamic RSA RS256 JWT signature successfully', async () => {
62
63
  const config: SsoServerConfig = {
63
64
  ssoServerKdfSecret: 'mock-kdf-secret',
64
65
  sessionSecret: 'mock-session-secret',
@@ -106,7 +107,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
106
107
  iReckon(sir, decodedPayload.name).isGonnaBe('John Doe');
107
108
  });
108
109
 
109
- await ifWeMight(sir, 'fail validation if JWT has invalid signature', async () => {
110
+ await ifWe(sir, 'fail validation if JWT has invalid signature', async () => {
110
111
  const config: SsoServerConfig = {
111
112
  ssoServerKdfSecret: 'mock-kdf-secret',
112
113
  sessionSecret: 'mock-session-secret',
@@ -154,7 +155,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
154
155
  iReckon(sir, verifyError.message).includes('Cryptographic signature verification failed');
155
156
  });
156
157
 
157
- await ifWeMight(sir, 'derive server-delegate secret deterministically', async () => {
158
+ await ifWe(sir, 'derive server-delegate secret deterministically', async () => {
158
159
  const config: SsoServerConfig = {
159
160
  ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
160
161
  sessionSecret: 'session-secret-123',
@@ -179,7 +180,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
179
180
  iReckon(sir, secret1_a.length).isGonnaBe(128); // sha512 output in hex/base64 wrap
180
181
  });
181
182
 
182
- await ifWeMight(sir, 'create custodian challenge pool successfully', async () => {
183
+ await ifWe(sir, 'create custodian challenge pool successfully', async () => {
183
184
  const config: SsoServerConfig = {
184
185
  ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
185
186
  sessionSecret: 'session-secret-123',
@@ -234,16 +235,16 @@ await respecfully(sir, 'SsoCustodianService', async () => {
234
235
  iReckon(sir, custodianPool.id).willEqual('custodian-manage-google');
235
236
  iReckon(sir, custodianPool.isForeign).isGonnaBeTrue();
236
237
  iReckon(sir, custodianPool.metadata?.providerKey).willEqual(key);
237
-
238
+
238
239
  // Replicated behavior size from parent manage pool config
239
240
  iReckon(sir, custodianPool.config.behavior.size).willEqual(userManageConfig.behavior.size);
240
-
241
+
241
242
  // Structural validation of challenges
242
243
  const challengesCount = Object.keys(custodianPool.challenges).length;
243
244
  iReckon(sir, challengesCount).willEqual(custodianPool.config.behavior.size);
244
245
  });
245
246
 
246
- await ifWeMight(sir, 'revoke tokens successfully, making the correct http fetch calls', async () => {
247
+ await ifWe(sir, 'revoke tokens successfully, making the correct http fetch calls', async () => {
247
248
  const config: SsoServerConfig = {
248
249
  ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
249
250
  sessionSecret: 'session-secret-123',
@@ -1,16 +1,15 @@
1
1
  <div id="container" class="component-container">
2
- <h2 id="title">Add New Device (Pairing)</h2>
2
+ <h2 id="title">Sign In New Device</h2>
3
3
  <div class="content">
4
- <p>Pair this device to your existing primary identity timeline.</p>
4
+ <p>Sign in on this device to add it to an existing account.</p>
5
5
 
6
6
  <div class="input-group">
7
- <label for="input-identifier">Username or Email</label>
8
- <input type="text" id="input-identifier" placeholder="Enter registered username or email..."
9
- class="text-input">
7
+ <label for="input-identifier">Email</label>
8
+ <input type="text" id="input-identifier" placeholder="Enter registered email..." class="text-input">
10
9
  </div>
11
10
 
12
11
  <div class="input-group">
13
- <label for="input-device-tag">Device Tag / Name *</label>
12
+ <label for="input-device-tag">Device Name</label>
14
13
  <input type="text" id="input-device-tag" placeholder="e.g. laptop, phone, living-room..."
15
14
  class="text-input">
16
15
  </div>
@@ -29,7 +28,7 @@
29
28
  </div>
30
29
 
31
30
  <div class="action-row">
32
- <button id="btn-add-device" class="action-btn">Pair & Add Device</button>
31
+ <button id="btn-add-device" class="action-btn">Sign In</button>
33
32
  </div>
34
33
 
35
34
  <div class="switch-workflow" style="margin-top: 1.2rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
@@ -2,30 +2,29 @@ import thisCss from "./add-device.css";
2
2
  import thisHtml from "./add-device.html";
3
3
  import commonComponentStylesCss from "../../common-component-styles.css";
4
4
 
5
- import { delay, extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
+ import { delay, extractErrorMsg, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
6
  import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
7
7
  import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
8
8
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
9
- import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
10
9
  import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
10
  import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
12
11
  import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
13
12
  import { KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_USERNAME_REGEXP } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
14
13
  import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
15
- import { getTimelinesGroupedByTjp, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
14
+ import { getTimelinesGroupedByTjp, getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
16
15
  import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
17
- import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
18
16
 
17
+ import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
19
18
  import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
20
- import {
21
- IbGibDynamicComponentMetaBase, IbGibFormInstanceBase,
22
- } from "../../ibgib-dynamic-component-bases.mjs";
19
+ import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
23
20
  import {
24
21
  ElementsBase, IbGibDynamicComponentInstance,
25
22
  IbGibDynamicComponentInstanceInitOpts,
26
23
  } from "../../component-types.mjs";
27
24
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
28
- import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
25
+ import {
26
+ EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE
27
+ } from "../../../ui-constants.mjs";
29
28
 
30
29
  export const ADD_DEVICE_COMPONENT_NAME = 'ibgib-add-device';
31
30
 
@@ -162,7 +161,7 @@ export class AddDeviceComponentInstance
162
161
  this.elements!.inputIdentifier.value = identifier;
163
162
  if (!identifier) {
164
163
  this.elements!.inputIdentifier.classList.add('invalid');
165
- this.setStatus("Please enter a registered username or email.", "error");
164
+ this.setStatus("Please enter a registered email.", "error");
166
165
  return;
167
166
  }
168
167
 
@@ -170,7 +169,7 @@ export class AddDeviceComponentInstance
170
169
  this.elements!.inputDeviceTag.value = deviceTag;
171
170
  if (!deviceTag || !KEYSTONE_USERNAME_REGEXP.test(deviceTag)) {
172
171
  this.elements!.inputDeviceTag.classList.add('invalid');
173
- this.setStatus("Please enter a valid Device Tag / Name (e.g. 'work-laptop').", "error");
172
+ this.setStatus("Please enter a valid Device Name (e.g. 'work-laptop').", "error");
174
173
  return;
175
174
  }
176
175
 
@@ -194,7 +193,6 @@ export class AddDeviceComponentInstance
194
193
  const space = await metaspace.getLocalUserSpace({});
195
194
  if (!space) { throw new Error("No default space found in metaspace."); }
196
195
 
197
- const keystoneService = new KeystoneService_V1();
198
196
 
199
197
  // 1a. First attempt to locate primary identity keystone locally in space
200
198
  const keystonesIndex = await metaspace.getSpecialIbGib({
@@ -225,7 +223,7 @@ export class AddDeviceComponentInstance
225
223
  if (resLookup.success && resLookup.primaryKeystoneIbGib) {
226
224
  const fetchedKeystone = resLookup.primaryKeystoneIbGib as KeystoneIbGib_V1;
227
225
  const domainAddr = resLookup.tjpAddr || getIbGibAddr({ ibGib: fetchedKeystone });
228
-
226
+
229
227
  // Fetch full domain graph from server so local space has all historical keystone frames
230
228
  const resGraph = await getApiBridge().getKeystoneGraph(domainAddr);
231
229
  if (resGraph.success && resGraph.graph) {
@@ -276,6 +274,8 @@ export class AddDeviceComponentInstance
276
274
  animationEmoji: '🔑'
277
275
  });
278
276
 
277
+ const primaryTjpAddr = getTjpAddr({ ibGib: primaryKeystone, defaultIfNone: 'incomingAddr' });
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
280
  const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
281
281
  .withUsername(`${primaryUsername}-sync-${deviceTag}`)
@@ -283,10 +283,12 @@ export class AddDeviceComponentInstance
283
283
  .withDetails({
284
284
  client: 'space-gib-web',
285
285
  role: 'sync-delegate',
286
- deviceTag
286
+ deviceTag,
287
+ primaryTjpAddr,
287
288
  });
288
289
  const delegateConfigs = await delegateBuilder.compileConfigs();
289
290
 
291
+ const keystoneService = new KeystoneService_V1();
290
292
  const delegateKeystone = await keystoneService.genesis({
291
293
  masterSecret: delegateSecret,
292
294
  configs: delegateConfigs,
@@ -331,11 +333,10 @@ export class AddDeviceComponentInstance
331
333
  animationEmoji: '🔄'
332
334
  });
333
335
 
334
- const primaryTjpAddr = getGibInfo({ ibGibAddr: getIbGibAddr({ ibGib: primaryKeystone }) }).tjpGib;
335
- const domainAddr = primaryTjpAddr ? getIbGibAddr({ ibGib: primaryKeystone }) : getIbGibAddr({ ibGib: primaryKeystone });
336
+ // const domainAddr = primaryTjpAddr ? getIbGibAddr({ ibGib: primaryKeystone }) : getIbGibAddr({ ibGib: primaryKeystone });
336
337
 
337
338
  const resEvolve = await getApiBridge().putEvolveKeystone({
338
- domainAddr,
339
+ domainAddr: primaryTjpAddr,
339
340
  keystoneIbGib: evolvedPrimaryKeystone,
340
341
  relatedKeystoneIbGibs: [delegateKeystone]
341
342
  });
@@ -94,4 +94,23 @@ h2 {
94
94
  font-size: 0.8rem;
95
95
  color: var(--clr-warn, #f38ba8);
96
96
  margin: 0.5rem 0 0;
97
+ }
98
+
99
+ input::placeholder {
100
+ color: rgba(255, 255, 255, 0.35);
101
+ 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;
97
116
  }
@@ -41,6 +41,16 @@
41
41
  <p id="status-text" class="status-msg"></p>
42
42
  </div>
43
43
 
44
+ <div id="section-code-verification" class="input-group hidden" style="margin-top: 1.5rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
45
+ <label for="input-verify-code" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification Code</label>
46
+ <p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">A 6-digit verification code was sent to your email. Enter it below to activate your identity:</p>
47
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
48
+ <input type="text" id="input-verify-code" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
49
+ <button id="btn-submit-code" class="action-btn" style="white-space: nowrap;">Verify Code</button>
50
+ <button id="btn-resend-code" class="action-btn" style="white-space: nowrap; background: #45475a; color: #cdd6f4;">Resend Code</button>
51
+ </div>
52
+ </div>
53
+
44
54
  <div id="result-area" class="result-area collapsed">
45
55
  <h3>New Keystone Generated!</h3>
46
56
  <p class="address-label">Address:</p>