@ibgib/web-gib 0.0.55 → 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.
- 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 +35 -0
- package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
- package/dist/api/ibgib-api-bridge.mjs +70 -3
- package/dist/api/ibgib-api-bridge.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/keystone-creator/keystone-creator.css +19 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +1 -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 +10 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +164 -41
- 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 +9 -8
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/package.json +2 -2
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/api/api-index.mts +1 -1
- package/src/api/ibgib-api-bridge.mts +94 -4
- 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/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/keystone-creator/keystone-creator.css +19 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +10 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +180 -55
- package/src/ui/component/identity/keystone-details/keystone-details.mts +16 -15
- 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
|
@@ -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
|
|
package/src/identity/sso/{sso-custodian-service.respec.mts → sso-custodian-service.respec.mts.bak}
RENAMED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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">
|
|
2
|
+
<h2 id="title">Sign In New Device</h2>
|
|
3
3
|
<div class="content">
|
|
4
|
-
<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">
|
|
8
|
-
<input type="text" id="input-identifier" placeholder="Enter registered
|
|
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
|
|
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">
|
|
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,
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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>
|