@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
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',
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module identity/sso-popup-helper
|
|
3
|
+
*
|
|
4
|
+
* DRY client helper for initiating OAuth2 SSO authentication via a popup window
|
|
5
|
+
* and capturing the returned authorization code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
9
|
+
import { getApiBridge } from "../api/ibgib-api-bridge.mjs";
|
|
10
|
+
|
|
11
|
+
export interface SsoPopupOpts {
|
|
12
|
+
providerId: 'google' | 'github';
|
|
13
|
+
parentTjpAddr?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SsoPopupResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
code?: string;
|
|
19
|
+
stateObj?: any;
|
|
20
|
+
redirectUri?: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Executes an OAuth2 SSO login/link authorization flow using a popup window.
|
|
26
|
+
*
|
|
27
|
+
* 1. Queries `GET /api/identity/sso/config` for safe public client configs.
|
|
28
|
+
* 2. Constructs the provider authorization URL with encoded state payload.
|
|
29
|
+
* 3. Opens a 600x700 popup window and listens for `sso-oauth-callback` postMessage.
|
|
30
|
+
* 4. Returns `{ success: true, code, stateObj, redirectUri }` upon authorization.
|
|
31
|
+
*/
|
|
32
|
+
export async function executeSsoOAuthPopup({
|
|
33
|
+
providerId,
|
|
34
|
+
parentTjpAddr,
|
|
35
|
+
}: SsoPopupOpts): Promise<SsoPopupResult> {
|
|
36
|
+
const lc = `[${executeSsoOAuthPopup.name}]`;
|
|
37
|
+
try {
|
|
38
|
+
// 1. Fetch public SSO configs from server
|
|
39
|
+
const apiBridge = getApiBridge();
|
|
40
|
+
const resConfig = await apiBridge.getSsoConfig();
|
|
41
|
+
if (!resConfig.success || !resConfig.providers) {
|
|
42
|
+
return { success: false, message: resConfig.message || 'SSO configuration retrieval failed.' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const providers = resConfig.providers as any;
|
|
46
|
+
const providerConfig = Array.isArray(providers)
|
|
47
|
+
? providers.find((p: any) => p.providerId === providerId)
|
|
48
|
+
: providers[providerId];
|
|
49
|
+
|
|
50
|
+
if (!providerConfig) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
message: `SSO provider '${providerId}' is not configured on this server environment.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Generate high-entropy user nonce & state
|
|
58
|
+
/**
|
|
59
|
+
* {@link getUUID} uses {@link globalThis.crypto.getRandomValues}
|
|
60
|
+
*/
|
|
61
|
+
const userNonce = await getUUID();
|
|
62
|
+
const stateObj = {
|
|
63
|
+
providerId,
|
|
64
|
+
parentTjpAddr,
|
|
65
|
+
userNonce
|
|
66
|
+
};
|
|
67
|
+
const state = btoa(JSON.stringify(stateObj));
|
|
68
|
+
|
|
69
|
+
// 3. Construct Auth URL if not pre-constructed
|
|
70
|
+
let authUrl = providerConfig.authUrl;
|
|
71
|
+
if (!authUrl.includes('client_id=')) {
|
|
72
|
+
authUrl = `${authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
|
|
73
|
+
} else if (!authUrl.includes('state=')) {
|
|
74
|
+
authUrl = `${authUrl}&state=${encodeURIComponent(state)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Open popup window
|
|
78
|
+
const popup = window.open(
|
|
79
|
+
authUrl,
|
|
80
|
+
'sso-oauth-popup',
|
|
81
|
+
'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!popup) {
|
|
85
|
+
return { success: false, message: 'Popup blocked by browser. Please enable popups for this site.' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 5. Promise wrapper listening for postMessage or window closure
|
|
89
|
+
return await new Promise<SsoPopupResult>((resolve) => {
|
|
90
|
+
let checkClosedInterval: any;
|
|
91
|
+
|
|
92
|
+
const messageListener = (event: MessageEvent) => {
|
|
93
|
+
if (event.origin !== window.location.origin) return;
|
|
94
|
+
const callbackData = event.data;
|
|
95
|
+
if (callbackData && callbackData.type === 'sso-oauth-callback') {
|
|
96
|
+
window.removeEventListener('message', messageListener);
|
|
97
|
+
clearInterval(checkClosedInterval);
|
|
98
|
+
resolve({
|
|
99
|
+
success: true,
|
|
100
|
+
code: callbackData.code,
|
|
101
|
+
stateObj: callbackData.stateObj || stateObj,
|
|
102
|
+
redirectUri: providerConfig.redirectUri
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
window.addEventListener('message', messageListener);
|
|
108
|
+
|
|
109
|
+
checkClosedInterval = setInterval(() => {
|
|
110
|
+
if (popup.closed) {
|
|
111
|
+
clearInterval(checkClosedInterval);
|
|
112
|
+
window.removeEventListener('message', messageListener);
|
|
113
|
+
resolve({ success: false, message: 'Authentication cancelled (window closed).' });
|
|
114
|
+
}
|
|
115
|
+
}, 500);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const emsg = extractErrorMsg(error);
|
|
120
|
+
console.error(`${lc} ${emsg}`);
|
|
121
|
+
return { success: false, message: emsg };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Checks if the current window was opened as an OAuth popup (`window.opener` exists)
|
|
127
|
+
* and has received an authorization code in the URL query string (`?code=...`).
|
|
128
|
+
* If so, posts `sso-oauth-callback` to the parent window and closes itself.
|
|
129
|
+
*/
|
|
130
|
+
export function checkAndNotifyPopupOpener(): boolean {
|
|
131
|
+
if (typeof window === 'undefined' || !window.opener) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const url = new URL(window.location.href);
|
|
136
|
+
const code = url.searchParams.get('code');
|
|
137
|
+
if (!code) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const rawState = url.searchParams.get('state');
|
|
142
|
+
let stateObj: any;
|
|
143
|
+
if (rawState) {
|
|
144
|
+
try { stateObj = JSON.parse(atob(rawState)); } catch { }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
window.opener.postMessage({
|
|
148
|
+
type: 'sso-oauth-callback',
|
|
149
|
+
code,
|
|
150
|
+
stateObj
|
|
151
|
+
}, window.location.origin);
|
|
152
|
+
|
|
153
|
+
window.close();
|
|
154
|
+
return true;
|
|
155
|
+
}
|
package/src/index.mts
CHANGED
|
@@ -18,6 +18,9 @@ export * from "./api/ibgib-api-bridge.mjs";
|
|
|
18
18
|
|
|
19
19
|
// Identity & Sync services
|
|
20
20
|
export * from "./identity/keystone-sync-service.mjs";
|
|
21
|
+
export * from "./identity/custodian-delegate-helper.mjs";
|
|
22
|
+
// export * from "./identity/sso-callback-helper.mjs";
|
|
23
|
+
export * from "./identity/sso-popup-helper.mjs";
|
|
21
24
|
|
|
22
25
|
// Identity UI components
|
|
23
26
|
export * from "./ui/component/identity/index.mjs";
|
|
@@ -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
|
});
|
|
@@ -146,7 +146,8 @@ export class IdentityHeaderComponentInstance
|
|
|
146
146
|
};
|
|
147
147
|
this.keystoneCache.set(addr, details);
|
|
148
148
|
return details;
|
|
149
|
-
} catch {
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`${this.lc}[getKeystoneDetails] Registered keystone address ${addr} not found in local browser space: ${extractErrorMsg(error)} (E: a123456789abcdef0123456789c0002)`);
|
|
150
151
|
return { username: '', description: '', isPrimary: false };
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -10,17 +10,105 @@ h2 {
|
|
|
10
10
|
color: var(--clr-accent, #50fa7b);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
.mode-tabs {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 0.5rem;
|
|
16
|
+
background: rgba(0, 0, 0, 0.25);
|
|
17
|
+
padding: 0.25rem;
|
|
18
|
+
border-radius: 10px;
|
|
19
|
+
border: 1px solid var(--clr-border, rgba(255, 255, 255, 0.1));
|
|
20
|
+
margin-bottom: 1rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.mode-tab {
|
|
24
|
+
flex: 1;
|
|
25
|
+
padding: 0.6rem 0.75rem;
|
|
26
|
+
border: none;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
background: transparent;
|
|
29
|
+
color: var(--clr-text-secondary, #a6adc8);
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
font-size: 0.9rem;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition: all 0.2s ease;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.mode-tab:hover {
|
|
37
|
+
color: var(--clr-text, #cdd6f4);
|
|
38
|
+
background: rgba(255, 255, 255, 0.05);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.mode-tab.active {
|
|
42
|
+
background: #45475a;
|
|
43
|
+
color: #89b4fa;
|
|
44
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
.subtitle {
|
|
14
48
|
color: var(--clr-text-secondary, #a6adc8);
|
|
15
|
-
font-size: 0.
|
|
49
|
+
font-size: 0.92rem;
|
|
16
50
|
margin: 0;
|
|
51
|
+
line-height: 1.45;
|
|
17
52
|
}
|
|
18
53
|
|
|
19
54
|
.content {
|
|
20
|
-
margin-top: 1.
|
|
55
|
+
margin-top: 1.25rem;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
gap: 1.25rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tab-panel.hidden, .hidden {
|
|
62
|
+
display: none !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sso-buttons {
|
|
21
66
|
display: flex;
|
|
22
67
|
flex-direction: column;
|
|
23
|
-
gap:
|
|
68
|
+
gap: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sso-btn {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
gap: 0.75rem;
|
|
76
|
+
padding: 0.8rem 1.25rem;
|
|
77
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
background: #1e1e2e;
|
|
80
|
+
color: #cdd6f4;
|
|
81
|
+
font-size: 0.95rem;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: transform 0.15s ease, background 0.15s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sso-btn:hover {
|
|
88
|
+
transform: translateY(-1px);
|
|
89
|
+
background: #313244;
|
|
90
|
+
border-color: #89b4fa;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.divider {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
text-align: center;
|
|
97
|
+
margin: 0.5rem 0;
|
|
98
|
+
color: #a6adc8;
|
|
99
|
+
font-size: 0.75rem;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
letter-spacing: 1px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.divider::before, .divider::after {
|
|
105
|
+
content: '';
|
|
106
|
+
flex: 1;
|
|
107
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.divider span {
|
|
111
|
+
padding: 0 0.75rem;
|
|
24
112
|
}
|
|
25
113
|
|
|
26
114
|
.input-group {
|
|
@@ -34,8 +122,25 @@ h2 {
|
|
|
34
122
|
color: var(--clr-text-secondary, #a6adc8);
|
|
35
123
|
}
|
|
36
124
|
|
|
125
|
+
.text-input {
|
|
126
|
+
width: 100%;
|
|
127
|
+
padding: 0.75rem 1rem;
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
130
|
+
background: #11111b;
|
|
131
|
+
color: #cdd6f4;
|
|
132
|
+
font-size: 0.95rem;
|
|
133
|
+
box-sizing: border-box;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.text-input:focus {
|
|
137
|
+
outline: none;
|
|
138
|
+
border-color: #89b4fa;
|
|
139
|
+
box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.2);
|
|
140
|
+
}
|
|
141
|
+
|
|
37
142
|
.action-row {
|
|
38
|
-
margin-top:
|
|
143
|
+
margin-top: 0.5rem;
|
|
39
144
|
width: 100%;
|
|
40
145
|
}
|
|
41
146
|
|
|
@@ -65,10 +170,6 @@ h2 {
|
|
|
65
170
|
margin-top: 0.5rem;
|
|
66
171
|
}
|
|
67
172
|
|
|
68
|
-
.status-area.hidden {
|
|
69
|
-
display: none;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
173
|
.result-area {
|
|
73
174
|
padding: 1.5rem;
|
|
74
175
|
background: rgba(255, 255, 255, 0.03);
|
|
@@ -77,7 +178,6 @@ h2 {
|
|
|
77
178
|
display: flex;
|
|
78
179
|
flex-direction: column;
|
|
79
180
|
gap: 0.75rem;
|
|
80
|
-
transition: opacity var(--t-med, 0.2s ease);
|
|
81
181
|
}
|
|
82
182
|
|
|
83
183
|
.result-area.collapsed {
|
|
@@ -94,4 +194,9 @@ h2 {
|
|
|
94
194
|
font-size: 0.8rem;
|
|
95
195
|
color: var(--clr-warn, #f38ba8);
|
|
96
196
|
margin: 0.5rem 0 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
input::placeholder {
|
|
200
|
+
color: rgba(255, 255, 255, 0.35);
|
|
201
|
+
opacity: 1;
|
|
97
202
|
}
|