@ibgib/web-gib 0.0.61 → 0.0.62

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 (59) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/helpers.web.d.mts +3 -1
  4. package/dist/helpers.web.d.mts.map +1 -1
  5. package/dist/helpers.web.mjs +17 -1
  6. package/dist/helpers.web.mjs.map +1 -1
  7. package/dist/identity/device-delegate-helper.d.mts +48 -0
  8. package/dist/identity/device-delegate-helper.d.mts.map +1 -1
  9. package/dist/identity/device-delegate-helper.mjs +86 -2
  10. package/dist/identity/device-delegate-helper.mjs.map +1 -1
  11. package/dist/identity/device-delegate.respec.mjs +6 -6
  12. package/dist/identity/device-delegate.respec.mjs.map +1 -1
  13. package/dist/identity/passkey-helper.d.mts +72 -0
  14. package/dist/identity/passkey-helper.d.mts.map +1 -0
  15. package/dist/identity/passkey-helper.mjs +237 -0
  16. package/dist/identity/passkey-helper.mjs.map +1 -0
  17. package/dist/identity/passkey.respec.d.mts +2 -0
  18. package/dist/identity/passkey.respec.d.mts.map +1 -0
  19. package/dist/identity/passkey.respec.mjs +169 -0
  20. package/dist/identity/passkey.respec.mjs.map +1 -0
  21. package/dist/index.d.mts +1 -0
  22. package/dist/index.d.mts.map +1 -1
  23. package/dist/index.mjs +1 -0
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  26. package/dist/ui/component/identity/add-device/add-device.mjs +1 -0
  27. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  28. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +18 -1
  29. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  30. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +42 -3
  31. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +510 -74
  32. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  33. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +16 -1
  34. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  35. package/dist/ui/component/identity/keystone-details/keystone-details.html +38 -0
  36. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +946 -122
  37. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  38. package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
  39. package/dist/ui/component/identity/remove-device/remove-device.mjs +1 -0
  40. package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
  41. package/dist/ui/ui-helpers.d.mts +3 -0
  42. package/dist/ui/ui-helpers.d.mts.map +1 -1
  43. package/dist/ui/ui-helpers.mjs +110 -6
  44. package/dist/ui/ui-helpers.mjs.map +1 -1
  45. package/package.json +2 -2
  46. package/src/AUTO-GENERATED-version.mts +1 -1
  47. package/src/helpers.web.mts +22 -2
  48. package/src/identity/device-delegate-helper.mts +135 -2
  49. package/src/identity/device-delegate.respec.mts +6 -6
  50. package/src/identity/passkey-helper.mts +304 -0
  51. package/src/identity/passkey.respec.mts +194 -0
  52. package/src/index.mts +1 -0
  53. package/src/ui/component/identity/add-device/add-device.mts +1 -0
  54. package/src/ui/component/identity/keystone-creator/keystone-creator.html +42 -3
  55. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +551 -77
  56. package/src/ui/component/identity/keystone-details/keystone-details.html +38 -0
  57. package/src/ui/component/identity/keystone-details/keystone-details.mts +1011 -131
  58. package/src/ui/component/identity/remove-device/remove-device.mts +1 -0
  59. package/src/ui/ui-helpers.mts +118 -5
@@ -0,0 +1,194 @@
1
+ import { respecfully, ifWe, ifWeMight, iReckon } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
2
+ import { getIbGibGlobalThis_IbGib } from '../helpers.mjs';
3
+ import {
4
+ bufferToHex,
5
+ hexToUint8Array,
6
+ normalizeSaltTo32Bytes,
7
+ isPasskeyPrfSupported,
8
+ registerPasskeyCredential,
9
+ evaluatePasskeyPrf,
10
+ createPasskeyDeviceSyncDelegate
11
+ } from './passkey-helper.mjs';
12
+ import { getDeviceDelegateSecret } from './device-delegate-helper.mjs';
13
+
14
+ const sir = `[${import.meta.url}]`;
15
+
16
+ // Initialize global db & store names for tests
17
+ const globalThisIbGib = getIbGibGlobalThis_IbGib();
18
+ globalThisIbGib.dbName_hack = 'space_gib_db';
19
+ globalThisIbGib.storeName_hack = 'space_gib_store';
20
+
21
+ // Mock storage substrate for Node test environment
22
+ const inMemoryStore = new Map<string, string>();
23
+
24
+ if (typeof globalThis.indexedDB === 'undefined') {
25
+ (globalThis as any).indexedDB = {
26
+ open: (dbName: string) => {
27
+ const req: any = {};
28
+ setTimeout(() => {
29
+ const db = {
30
+ objectStoreNames: { contains: () => true },
31
+ transaction: (storeName: string) => ({
32
+ objectStore: (sName: string) => ({
33
+ get: (key: string) => {
34
+ const getReq: any = {};
35
+ setTimeout(() => {
36
+ const val = inMemoryStore.get(key);
37
+ getReq.result = val !== undefined ? { value: val } : undefined;
38
+ if (getReq.onsuccess) getReq.onsuccess({ target: getReq });
39
+ }, 0);
40
+ return getReq;
41
+ },
42
+ put: (datum: any) => {
43
+ const putReq: any = {};
44
+ setTimeout(() => {
45
+ inMemoryStore.set(datum.key, datum.value);
46
+ if (putReq.onsuccess) putReq.onsuccess({ target: putReq });
47
+ }, 0);
48
+ return putReq;
49
+ },
50
+ delete: (key: string) => {
51
+ const delReq: any = {};
52
+ setTimeout(() => {
53
+ inMemoryStore.delete(key);
54
+ if (delReq.onsuccess) delReq.onsuccess({ target: delReq });
55
+ }, 0);
56
+ return delReq;
57
+ }
58
+ })
59
+ }),
60
+ close: () => {}
61
+ };
62
+ req.result = db;
63
+ if (req.onsuccess) req.onsuccess({ target: req });
64
+ }, 0);
65
+ return req;
66
+ }
67
+ };
68
+ }
69
+
70
+ // Setup environment profile
71
+ process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC = 'sync-profile.dev';
72
+
73
+ class MockIbGibSpace {
74
+ store = new Map<string, any>();
75
+ ib: string = 'witness space mock-space mock_space_id user';
76
+
77
+ constructor(public name: string = "mock_space") { }
78
+
79
+ async put({ ibGib }: { ibGib: any }): Promise<void> {
80
+ const addr = `${ibGib.ib}^${ibGib.gib}`;
81
+ this.store.set(addr, JSON.parse(JSON.stringify(ibGib)));
82
+ }
83
+
84
+ async get({ addr }: { addr: string }): Promise<any | null> {
85
+ const data = this.store.get(addr);
86
+ return data ? JSON.parse(JSON.stringify(data)) : null;
87
+ }
88
+
89
+ async argy({ argData, ibGibs }: { argData: any; ibGibs?: any[] }): Promise<any> {
90
+ return {
91
+ ib: 'arg^gib',
92
+ gib: 'arg_gib',
93
+ data: argData,
94
+ ibGibs,
95
+ };
96
+ }
97
+
98
+ async witness(arg: any): Promise<any> {
99
+ return { data: { success: true } };
100
+ }
101
+ }
102
+
103
+ class MockMetaspaceService {
104
+ constructor(public space: MockIbGibSpace) { }
105
+
106
+ async getLocalUserSpace({ lock }: { lock?: boolean } = {}): Promise<MockIbGibSpace> {
107
+ return this.space;
108
+ }
109
+
110
+ async put(args: any): Promise<void> {
111
+ const target = args.space || this.space;
112
+ return target.put(args);
113
+ }
114
+
115
+ async registerNewIbGib({ ibGib }: { ibGib: any }): Promise<void> {
116
+ return this.put({ ibGib });
117
+ }
118
+
119
+ async get(args: { addr?: string; addrs?: string[]; space?: any }): Promise<{ success: boolean; ibGibs?: any[] }> {
120
+ const target = args.space || this.space;
121
+ if (args.addr) {
122
+ const x = await target.get({ addr: args.addr });
123
+ return { success: !!x, ibGibs: x ? [x] : [] };
124
+ }
125
+ return { success: true, ibGibs: [] };
126
+ }
127
+ }
128
+
129
+ const mockSpace = new MockIbGibSpace();
130
+ const mockMetaspace = new MockMetaspaceService(mockSpace);
131
+
132
+ await respecfully(sir, 'Passkey WebAuthn PRF Helper Service', async () => {
133
+
134
+ await ifWe(sir, 'bufferToHex and hexToUint8Array roundtrip correctly', async () => {
135
+ const originalBytes = new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
136
+ const hex = bufferToHex(originalBytes.buffer);
137
+ iReckon(sir, hex).willEqual('deadbeef0123456789abcdef');
138
+
139
+ const roundtripBytes = hexToUint8Array(hex);
140
+ iReckon(sir, roundtripBytes.length).willEqual(originalBytes.length);
141
+ for (let i = 0; i < originalBytes.length; i++) {
142
+ iReckon(sir, roundtripBytes[i]).willEqual(originalBytes[i]);
143
+ }
144
+ });
145
+
146
+ await ifWe(sir, 'normalizeSaltTo32Bytes normalizes various salt inputs to 32 bytes', async () => {
147
+ // 1. Hex string of 64 chars (32 bytes)
148
+ const hex64 = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
149
+ const normHex = normalizeSaltTo32Bytes(hex64);
150
+ iReckon(sir, normHex.length).willEqual(32);
151
+ iReckon(sir, bufferToHex(normHex.buffer)).willEqual(hex64);
152
+
153
+ // 2. Text string
154
+ const textSalt = 'ibgib_passkey_salt_domain_123';
155
+ const normText = normalizeSaltTo32Bytes(textSalt);
156
+ iReckon(sir, normText.length).willEqual(32);
157
+
158
+ // 3. Uint8Array
159
+ const rawBytes = new Uint8Array(32);
160
+ rawBytes.fill(0x42);
161
+ const normRaw = normalizeSaltTo32Bytes(rawBytes);
162
+ iReckon(sir, normRaw.length).willEqual(32);
163
+ iReckon(sir, normRaw[0]).willEqual(0x42);
164
+ });
165
+
166
+ await ifWe(sir, 'isPasskeyPrfSupported evaluates capabilities cleanly', async () => {
167
+ // In Node environment without window
168
+ const supported = await isPasskeyPrfSupported();
169
+ iReckon(sir, typeof supported).willEqual('boolean');
170
+ });
171
+
172
+ await ifWe(sir, 'createPasskeyDeviceSyncDelegate creates standard passkey sync delegate', async () => {
173
+ const username = 'alice';
174
+ const primaryTjpAddr = 'keystone^alice_primary_tjp.addr';
175
+ const passkeySecret = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
176
+
177
+ const { delegateKeystone, deviceTag, delegateSecret } = await createPasskeyDeviceSyncDelegate({
178
+ username,
179
+ primaryTjpAddr,
180
+ passkeySecret,
181
+ transports: ['internal'],
182
+ metaspace: mockMetaspace as any,
183
+ space: mockSpace as any,
184
+ });
185
+
186
+ iReckon(sir, delegateKeystone).asTo('delegateKeystone is generated').isGonnaBeTruthy();
187
+ iReckon(sir, deviceTag.startsWith('passkey-alice-')).asTo('deviceTag follows passkey naming format').isGonnaBeTrue();
188
+ iReckon(sir, delegateSecret).asTo('delegateSecret is derived').isGonnaBeTruthy();
189
+
190
+ // Verify stored in IndexedDB
191
+ const storedSecret = await getDeviceDelegateSecret({ primaryTjpAddr });
192
+ iReckon(sir, storedSecret).willEqual(delegateSecret);
193
+ });
194
+ });
package/src/index.mts CHANGED
@@ -20,6 +20,7 @@ export * from "./api/ibgib-api-bridge.mjs";
20
20
  export * from "./identity/keystone-sync-service.mjs";
21
21
  export * from "./identity/device-delegate-helper.mjs";
22
22
  export * from "./identity/sso-popup-helper.mjs";
23
+ export * from "./identity/passkey-helper.mjs";
23
24
 
24
25
  // Identity UI components
25
26
  export * from "./ui/component/identity/index.mjs";
@@ -615,6 +615,7 @@ export class AddDeviceComponentInstance
615
615
  parentKeystone: primaryKeystone,
616
616
  delegateKeystone,
617
617
  masterSecret,
618
+ signingPoolId: 'manage',
618
619
  allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
619
620
  metaspace,
620
621
  space,
@@ -4,11 +4,13 @@
4
4
  <!-- Mode Selector Tabs -->
5
5
  <div class="mode-tabs">
6
6
  <button id="tab-quick" class="mode-tab active">⚡ Quick (Passwordless)</button>
7
+ <button id="tab-passkey" class="mode-tab">🛡️ Passkey / Hardware</button>
7
8
  <button id="tab-password" class="mode-tab">🔑 Password-based</button>
8
9
  </div>
9
10
 
10
11
  <!-- Mode Subtitles -->
11
12
  <p id="subtitle-quick" class="subtitle">⚡ Skip passwords — sign in with Google, GitHub, or an Email code. You can always set up a password later.</p>
13
+ <p id="subtitle-passkey" class="subtitle hidden">🛡️ Sovereign & Passwordless — secure your identity with Touch ID, Windows Hello, or a YubiKey.</p>
12
14
  <p id="subtitle-password" class="subtitle hidden">🔑 Full local ownership — your password never leaves your device. You can link Google or GitHub later.</p>
13
15
 
14
16
  <div class="content">
@@ -49,12 +51,49 @@
49
51
  <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
50
52
  <input type="text" id="input-quick-otp" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
51
53
  <button id="btn-quick-verify-code" class="action-btn" style="white-space: nowrap; width: auto;">Verify Code</button>
54
+ <button id="btn-quick-resend-code" class="action-btn secondary" type="button" style="white-space: nowrap; width: auto; font-size: 0.85rem; padding: 0.4rem 0.8rem; background: #45475a; color: #cdd6f4;">Resend Code</button>
52
55
  </div>
53
56
  </div>
54
57
  </div>
55
58
 
56
59
  <!-- ----------------------------------------------------------------- -->
57
- <!-- Tab 2: Password-based Content -->
60
+ <!-- Tab 2: Passkey / Hardware Biometrics Content -->
61
+ <!-- ----------------------------------------------------------------- -->
62
+ <div id="panel-passkey" class="tab-panel hidden">
63
+ <div class="input-group">
64
+ <label for="input-passkey-username">Username</label>
65
+ <input type="text" id="input-passkey-username" placeholder="Enter your username..." class="text-input">
66
+ </div>
67
+
68
+ <div class="input-group">
69
+ <label for="input-passkey-description">Description (Optional)</label>
70
+ <input type="text" id="input-passkey-description" placeholder="Enter an optional description..." class="text-input">
71
+ </div>
72
+
73
+ <div class="input-group">
74
+ <label for="input-passkey-email">Primary Email (Optional)</label>
75
+ <input type="email" id="input-passkey-email" placeholder="Enter your primary email address..." class="text-input">
76
+ </div>
77
+
78
+ <div class="input-group">
79
+ <label for="input-passkey-name">Passkey / Device Label</label>
80
+ <input type="text" id="input-passkey-name" placeholder="e.g. MacBook Touch ID, YubiKey 5Ci, Android Phone..." class="text-input">
81
+ </div>
82
+
83
+ <div class="checkbox-group" style="margin-top: 0.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
84
+ <input type="checkbox" id="checkbox-passkey-make-active" checked style="cursor: pointer; width: 1rem; height: 1rem;">
85
+ <label for="checkbox-passkey-make-active" style="cursor: pointer; font-size: 0.9rem; user-select: none;">Make this the active identity</label>
86
+ </div>
87
+
88
+ <div class="action-row">
89
+ <button id="btn-generate-passkey" class="action-btn">
90
+ 🛡️ Create Identity with Passkey / Biometrics
91
+ </button>
92
+ </div>
93
+ </div>
94
+
95
+ <!-- ----------------------------------------------------------------- -->
96
+ <!-- Tab 3: Password-based Content -->
58
97
  <!-- ----------------------------------------------------------------- -->
59
98
  <div id="panel-password" class="tab-panel hidden">
60
99
  <div class="input-group">
@@ -99,8 +138,8 @@
99
138
  <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>
100
139
  <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
101
140
  <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;">
102
- <button id="btn-submit-code" class="action-btn" style="white-space: nowrap;">Verify Code</button>
103
- <button id="btn-resend-code" class="action-btn" style="white-space: nowrap; background: #45475a; color: #cdd6f4;">Resend Code</button>
141
+ <button id="btn-submit-code" class="action-btn" style="white-space: nowrap; width: auto;">Verify Code</button>
142
+ <button id="btn-resend-code" class="action-btn secondary" type="button" style="white-space: nowrap; width: auto; font-size: 0.85rem; padding: 0.4rem 0.8rem; background: #45475a; color: #cdd6f4;">Resend Code</button>
104
143
  </div>
105
144
  </div>
106
145