@ibgib/web-gib 0.0.62 → 0.0.63
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/helpers.web.d.mts +17 -2
- package/dist/helpers.web.d.mts.map +1 -1
- package/dist/helpers.web.mjs +67 -2
- package/dist/helpers.web.mjs.map +1 -1
- package/dist/identity/passkey-helper.d.mts +7 -2
- package/dist/identity/passkey-helper.d.mts.map +1 -1
- package/dist/identity/passkey-helper.mjs +33 -12
- package/dist/identity/passkey-helper.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +26 -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 +18 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +10 -2
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +12 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.html +8 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +419 -305
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/dist/ui/ui-helpers.d.mts.map +1 -1
- package/dist/ui/ui-helpers.mjs +7 -2
- package/dist/ui/ui-helpers.mjs.map +1 -1
- package/package.json +2 -2
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/helpers.web.mts +95 -5
- package/src/identity/passkey-helper.mts +43 -10
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +18 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +12 -2
- package/src/ui/component/identity/keystone-details/keystone-details.html +8 -0
- package/src/ui/component/identity/keystone-details/keystone-details.mts +474 -339
- package/src/ui/ui-helpers.mts +6 -2
- package/tools/auto-generated-agent-skills/skills/ibgib-create-app-server/SKILL.md +50 -0
- package/src/identity/custodian-delegate-helper.mts.bak +0 -7
- package/src/identity/sso/sso-config-helper.mts.bak +0 -52
- package/src/identity/sso/sso-custodian-service.mts.bak +0 -566
- package/src/identity/sso/sso-custodian-service.respec.mts.bak +0 -298
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import { generateKeyPairSync, createSign } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
respecfully,
|
|
4
|
-
ifWeMight,
|
|
5
|
-
iReckon,
|
|
6
|
-
firstOfAll,
|
|
7
|
-
lastOfAll,
|
|
8
|
-
ifWe
|
|
9
|
-
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
10
|
-
import { SsoCustodianService } from './sso-custodian-service.mjs';
|
|
11
|
-
import { SsoServerConfig } from './sso-types.mjs';
|
|
12
|
-
import { POOL_ID_CUSTODIAN_MANAGE, POOL_ID_MANAGE } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
13
|
-
import { createStandardPoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-config-builder.mjs';
|
|
14
|
-
|
|
15
|
-
const maam = `[${import.meta.url}]`, sir = maam;
|
|
16
|
-
|
|
17
|
-
await respecfully(sir, 'SsoCustodianService', async () => {
|
|
18
|
-
let mockPrivateKey: any;
|
|
19
|
-
let mockPublicKeyJwk: any;
|
|
20
|
-
let originalFetch: any;
|
|
21
|
-
|
|
22
|
-
firstOfAll(sir, async () => {
|
|
23
|
-
// Generate mock RSA key pair
|
|
24
|
-
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
|
|
25
|
-
modulusLength: 2048
|
|
26
|
-
});
|
|
27
|
-
mockPrivateKey = privateKey;
|
|
28
|
-
|
|
29
|
-
// Export public key as JWK
|
|
30
|
-
mockPublicKeyJwk = publicKey.export({ format: 'jwk' });
|
|
31
|
-
mockPublicKeyJwk.kid = 'mock-kid-123';
|
|
32
|
-
mockPublicKeyJwk.alg = 'RS256';
|
|
33
|
-
mockPublicKeyJwk.use = 'sig';
|
|
34
|
-
|
|
35
|
-
// Mock global fetch to return JWKS containing the mock public key
|
|
36
|
-
originalFetch = (globalThis as any).fetch;
|
|
37
|
-
(globalThis as any).fetch = async (url: string, options?: any) => {
|
|
38
|
-
if (url === 'https://www.googleapis.com/oauth2/v3/certs') {
|
|
39
|
-
return {
|
|
40
|
-
ok: true,
|
|
41
|
-
status: 200,
|
|
42
|
-
json: async () => ({
|
|
43
|
-
keys: [mockPublicKeyJwk]
|
|
44
|
-
})
|
|
45
|
-
} as any;
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
ok: false,
|
|
49
|
-
status: 404,
|
|
50
|
-
text: async () => 'Not Found'
|
|
51
|
-
} as any;
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
lastOfAll(sir, async () => {
|
|
56
|
-
// Restore global fetch
|
|
57
|
-
if (originalFetch) {
|
|
58
|
-
(globalThis as any).fetch = originalFetch;
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await ifWe(sir, 'verify dynamic RSA RS256 JWT signature successfully', async () => {
|
|
63
|
-
const config: SsoServerConfig = {
|
|
64
|
-
ssoServerKdfSecret: 'mock-kdf-secret',
|
|
65
|
-
sessionSecret: 'mock-session-secret',
|
|
66
|
-
providers: {
|
|
67
|
-
google: {
|
|
68
|
-
providerId: 'google',
|
|
69
|
-
clientId: 'google-client-id-123',
|
|
70
|
-
clientSecret: 'google-client-secret-123',
|
|
71
|
-
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
72
|
-
userInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',
|
|
73
|
-
jwksUrl: 'https://www.googleapis.com/oauth2/v3/certs'
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const service = new SsoCustodianService(config);
|
|
79
|
-
|
|
80
|
-
// 1. Build JWT Header & Payload
|
|
81
|
-
const header = { alg: 'RS256', kid: 'mock-kid-123', typ: 'JWT' };
|
|
82
|
-
const payload = {
|
|
83
|
-
sub: 'user-sub-999',
|
|
84
|
-
email: 'user@example.com',
|
|
85
|
-
name: 'John Doe',
|
|
86
|
-
aud: 'google-client-id-123',
|
|
87
|
-
iss: 'https://accounts.google.com',
|
|
88
|
-
exp: Math.floor(Date.now() / 1000) + 3600 // expires in 1 hour
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
92
|
-
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
93
|
-
const data = `${headerB64}.${payloadB64}`;
|
|
94
|
-
|
|
95
|
-
// 2. Sign JWT using private key
|
|
96
|
-
const sign = createSign('RSA-SHA256');
|
|
97
|
-
sign.update(data);
|
|
98
|
-
const signatureB64 = sign.sign(mockPrivateKey, 'base64url');
|
|
99
|
-
|
|
100
|
-
const mockJwtToken = `${data}.${signatureB64}`;
|
|
101
|
-
|
|
102
|
-
// 3. Verify signature via service
|
|
103
|
-
const decodedPayload = await service.verifyJwt(mockJwtToken, config.providers.google!);
|
|
104
|
-
|
|
105
|
-
iReckon(sir, decodedPayload.sub).isGonnaBe('user-sub-999');
|
|
106
|
-
iReckon(sir, decodedPayload.email).isGonnaBe('user@example.com');
|
|
107
|
-
iReckon(sir, decodedPayload.name).isGonnaBe('John Doe');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
await ifWe(sir, 'fail validation if JWT has invalid signature', async () => {
|
|
111
|
-
const config: SsoServerConfig = {
|
|
112
|
-
ssoServerKdfSecret: 'mock-kdf-secret',
|
|
113
|
-
sessionSecret: 'mock-session-secret',
|
|
114
|
-
providers: {
|
|
115
|
-
google: {
|
|
116
|
-
providerId: 'google',
|
|
117
|
-
clientId: 'google-client-id-123',
|
|
118
|
-
clientSecret: 'google-client-secret-123',
|
|
119
|
-
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
120
|
-
userInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',
|
|
121
|
-
jwksUrl: 'https://www.googleapis.com/oauth2/v3/certs'
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const service = new SsoCustodianService(config);
|
|
127
|
-
|
|
128
|
-
const header = { alg: 'RS256', kid: 'mock-kid-123', typ: 'JWT' };
|
|
129
|
-
const payload = {
|
|
130
|
-
sub: 'user-sub-999',
|
|
131
|
-
aud: 'google-client-id-123',
|
|
132
|
-
exp: Math.floor(Date.now() / 1000) + 3600
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
136
|
-
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
137
|
-
const data = `${headerB64}.${payloadB64}`;
|
|
138
|
-
|
|
139
|
-
// Create signature using a different/unregistered RSA key
|
|
140
|
-
const { privateKey: badPrivateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
141
|
-
const sign = createSign('RSA-SHA256');
|
|
142
|
-
sign.update(data);
|
|
143
|
-
const badSignatureB64 = sign.sign(badPrivateKey, 'base64url');
|
|
144
|
-
|
|
145
|
-
const badJwtToken = `${data}.${badSignatureB64}`;
|
|
146
|
-
|
|
147
|
-
let verifyError: any;
|
|
148
|
-
try {
|
|
149
|
-
await service.verifyJwt(badJwtToken, config.providers.google!);
|
|
150
|
-
} catch (error: any) {
|
|
151
|
-
verifyError = error;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
iReckon(sir, verifyError).not.isGonnaBeUndefined();
|
|
155
|
-
iReckon(sir, verifyError.message).includes('Cryptographic signature verification failed');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
await ifWe(sir, 'derive server-delegate secret deterministically', async () => {
|
|
159
|
-
const config: SsoServerConfig = {
|
|
160
|
-
ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
|
|
161
|
-
sessionSecret: 'session-secret-123',
|
|
162
|
-
providers: {}
|
|
163
|
-
};
|
|
164
|
-
const service = new SsoCustodianService(config);
|
|
165
|
-
|
|
166
|
-
const key = 'google:123456789';
|
|
167
|
-
const nonce1 = 'nonce-abc-123';
|
|
168
|
-
const nonce2 = 'nonce-def-456';
|
|
169
|
-
|
|
170
|
-
const secret1_a = await service.deriveServerDelegateSecret(key, nonce1);
|
|
171
|
-
const secret1_b = await service.deriveServerDelegateSecret(key, nonce1);
|
|
172
|
-
const secret2 = await service.deriveServerDelegateSecret(key, nonce2);
|
|
173
|
-
|
|
174
|
-
// Identical inputs must yield identical outputs
|
|
175
|
-
iReckon(sir, secret1_a).isGonnaBe(secret1_b);
|
|
176
|
-
// Different nonces must yield different outputs
|
|
177
|
-
iReckon(sir, secret1_a).not.isGonnaBe(secret2);
|
|
178
|
-
// Derived secrets must be string type with high entropy
|
|
179
|
-
iReckon(sir, typeof secret1_a).isGonnaBe('string');
|
|
180
|
-
iReckon(sir, secret1_a.length).isGonnaBe(128); // sha512 output in hex/base64 wrap
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
await ifWe(sir, 'create custodian challenge pool successfully', async () => {
|
|
184
|
-
const config: SsoServerConfig = {
|
|
185
|
-
ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
|
|
186
|
-
sessionSecret: 'session-secret-123',
|
|
187
|
-
providers: {}
|
|
188
|
-
};
|
|
189
|
-
const service = new SsoCustodianService(config);
|
|
190
|
-
|
|
191
|
-
const key = 'google:123456789';
|
|
192
|
-
const nonce = 'nonce-abc-123';
|
|
193
|
-
|
|
194
|
-
// 1. Create a mock target keystone containing a 'manage' pool
|
|
195
|
-
const userManageConfig = createStandardPoolConfig({
|
|
196
|
-
id: POOL_ID_MANAGE,
|
|
197
|
-
salt: 'user-manage-salt'
|
|
198
|
-
});
|
|
199
|
-
userManageConfig.behavior.size = 12; // custom size for verification
|
|
200
|
-
const mockTargetKeystone = {
|
|
201
|
-
ib: 'keystone^some-tjp-addr',
|
|
202
|
-
gib: 'some-gib-value',
|
|
203
|
-
data: {
|
|
204
|
-
challengePools: [
|
|
205
|
-
{
|
|
206
|
-
id: 'manage',
|
|
207
|
-
config: userManageConfig,
|
|
208
|
-
challenges: { 'chal1': 'val1' }
|
|
209
|
-
}
|
|
210
|
-
],
|
|
211
|
-
n: 0
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// 2. Mock metaspace and space calls
|
|
216
|
-
const mockSpace = {};
|
|
217
|
-
const mockMetaspace = {
|
|
218
|
-
getLatestAddr: async () => 'latest-keystone-addr',
|
|
219
|
-
get: async () => ({
|
|
220
|
-
success: true,
|
|
221
|
-
ibGibs: [mockTargetKeystone]
|
|
222
|
-
})
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const custodianPool = await service.createCustodianChallengePool({
|
|
226
|
-
providerId: 'google',
|
|
227
|
-
providerKey: key,
|
|
228
|
-
userNonce: nonce,
|
|
229
|
-
keystoneAddr: 'some-tjp-addr',
|
|
230
|
-
metaspace: mockMetaspace as any,
|
|
231
|
-
space: mockSpace as any
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
iReckon(sir, custodianPool).not.isGonnaBeUndefined();
|
|
235
|
-
iReckon(sir, custodianPool.id).willEqual('custodian-manage-google');
|
|
236
|
-
iReckon(sir, custodianPool.isForeign).isGonnaBeTrue();
|
|
237
|
-
iReckon(sir, custodianPool.metadata?.providerKey).willEqual(key);
|
|
238
|
-
|
|
239
|
-
// Replicated behavior size from parent manage pool config
|
|
240
|
-
iReckon(sir, custodianPool.config.behavior.size).willEqual(userManageConfig.behavior.size);
|
|
241
|
-
|
|
242
|
-
// Structural validation of challenges
|
|
243
|
-
const challengesCount = Object.keys(custodianPool.challenges).length;
|
|
244
|
-
iReckon(sir, challengesCount).willEqual(custodianPool.config.behavior.size);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
await ifWe(sir, 'revoke tokens successfully, making the correct http fetch calls', async () => {
|
|
248
|
-
const config: SsoServerConfig = {
|
|
249
|
-
ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
|
|
250
|
-
sessionSecret: 'session-secret-123',
|
|
251
|
-
providers: {
|
|
252
|
-
google: {
|
|
253
|
-
providerId: 'google',
|
|
254
|
-
clientId: 'google-client-id-abc',
|
|
255
|
-
clientSecret: 'google-client-secret-abc',
|
|
256
|
-
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
257
|
-
userInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo'
|
|
258
|
-
},
|
|
259
|
-
github: {
|
|
260
|
-
providerId: 'github',
|
|
261
|
-
clientId: 'github-client-id-xyz',
|
|
262
|
-
clientSecret: 'github-client-secret-xyz',
|
|
263
|
-
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
264
|
-
userInfoUrl: 'https://api.github.com/user'
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
const service = new SsoCustodianService(config);
|
|
269
|
-
|
|
270
|
-
const originalFetch = globalThis.fetch;
|
|
271
|
-
let fetchCalls: { url: string, options: any }[] = [];
|
|
272
|
-
globalThis.fetch = (async (url: any, options: any) => {
|
|
273
|
-
fetchCalls.push({ url, options });
|
|
274
|
-
return {
|
|
275
|
-
ok: true,
|
|
276
|
-
status: 200,
|
|
277
|
-
text: async () => 'ok'
|
|
278
|
-
} as any;
|
|
279
|
-
}) as any;
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
// Revoke Google token
|
|
283
|
-
await service.revokeToken('google', 'google-test-token-123');
|
|
284
|
-
iReckon(sir, fetchCalls.length).willEqual(1);
|
|
285
|
-
iReckon(sir, fetchCalls[0].url).includes('google-test-token-123');
|
|
286
|
-
|
|
287
|
-
// Revoke GitHub token
|
|
288
|
-
await service.revokeToken('github', 'github-test-token-456');
|
|
289
|
-
iReckon(sir, fetchCalls.length).willEqual(2);
|
|
290
|
-
iReckon(sir, fetchCalls[1].url).includes('/applications/github-client-id-xyz/grant');
|
|
291
|
-
iReckon(sir, fetchCalls[1].options.method).willEqual('DELETE');
|
|
292
|
-
iReckon(sir, fetchCalls[1].options.headers['Authorization']).includes('Basic');
|
|
293
|
-
} finally {
|
|
294
|
-
globalThis.fetch = originalFetch;
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
|