@ibgib/web-gib 0.0.61 → 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 +20 -3
- package/dist/helpers.web.d.mts.map +1 -1
- package/dist/helpers.web.mjs +84 -3
- package/dist/helpers.web.mjs.map +1 -1
- package/dist/identity/device-delegate-helper.d.mts +48 -0
- package/dist/identity/device-delegate-helper.d.mts.map +1 -1
- package/dist/identity/device-delegate-helper.mjs +86 -2
- package/dist/identity/device-delegate-helper.mjs.map +1 -1
- package/dist/identity/device-delegate.respec.mjs +6 -6
- package/dist/identity/device-delegate.respec.mjs.map +1 -1
- package/dist/identity/passkey-helper.d.mts +77 -0
- package/dist/identity/passkey-helper.d.mts.map +1 -0
- package/dist/identity/passkey-helper.mjs +258 -0
- package/dist/identity/passkey-helper.mjs.map +1 -0
- package/dist/identity/passkey.respec.d.mts +2 -0
- package/dist/identity/passkey.respec.d.mts.map +1 -0
- package/dist/identity/passkey.respec.mjs +169 -0
- package/dist/identity/passkey.respec.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -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.mjs +1 -0
- package/dist/ui/component/identity/add-device/add-device.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 +18 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.html +60 -4
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +518 -74
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +28 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.html +46 -0
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +1146 -208
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.mjs +1 -0
- package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
- package/dist/ui/ui-helpers.d.mts +3 -0
- package/dist/ui/ui-helpers.d.mts.map +1 -1
- package/dist/ui/ui-helpers.mjs +116 -7
- 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 +117 -7
- package/src/identity/device-delegate-helper.mts +135 -2
- package/src/identity/device-delegate.respec.mts +6 -6
- package/src/identity/passkey-helper.mts +337 -0
- package/src/identity/passkey.respec.mts +194 -0
- package/src/index.mts +1 -0
- package/src/ui/component/identity/add-device/add-device.mts +1 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +26 -0
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +60 -4
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +561 -77
- package/src/ui/component/identity/keystone-details/keystone-details.html +46 -0
- package/src/ui/component/identity/keystone-details/keystone-details.mts +1239 -224
- package/src/ui/component/identity/remove-device/remove-device.mts +1 -0
- package/src/ui/ui-helpers.mts +123 -6
- 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
package/src/ui/ui-helpers.mts
CHANGED
|
@@ -49,6 +49,9 @@ export interface ShowFullscreenDialogOpts {
|
|
|
49
49
|
isPassword?: boolean;
|
|
50
50
|
isBusy?: boolean;
|
|
51
51
|
animationEmoji?: string;
|
|
52
|
+
passkeys?: Record<string, any>;
|
|
53
|
+
passkeyButtonTitle?: string;
|
|
54
|
+
onPasskeyAuth?: (passkeyEntry?: any) => Promise<string | undefined>;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export async function showFullscreenDialog(opts: ShowFullscreenDialogOpts & { isBusy: true }): Promise<FullscreenDialogController>;
|
|
@@ -64,6 +67,9 @@ export async function showFullscreenDialog({
|
|
|
64
67
|
isPassword,
|
|
65
68
|
isBusy,
|
|
66
69
|
animationEmoji,
|
|
70
|
+
passkeys,
|
|
71
|
+
passkeyButtonTitle,
|
|
72
|
+
onPasskeyAuth,
|
|
67
73
|
}: ShowFullscreenDialogOpts): Promise<string | undefined | FullscreenDialogController> {
|
|
68
74
|
const lc = `[${showFullscreenDialog.name}]`;
|
|
69
75
|
try {
|
|
@@ -112,6 +118,75 @@ export async function showFullscreenDialog({
|
|
|
112
118
|
messageElement.appendChild(animDiv);
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
// Clean up any previous passkey container
|
|
122
|
+
let prevPasskeyContainer = document.getElementById('fullscreen-dialog-passkey-container');
|
|
123
|
+
if (prevPasskeyContainer) { prevPasskeyContainer.remove(); }
|
|
124
|
+
|
|
125
|
+
let passkeyContainer: HTMLDivElement | null = null;
|
|
126
|
+
let btnPasskey: HTMLButtonElement | null = null;
|
|
127
|
+
let selectPasskey: HTMLSelectElement | null = null;
|
|
128
|
+
const validPasskeys = Object.values(passkeys || {}).filter(p => p !== null && p !== undefined);
|
|
129
|
+
|
|
130
|
+
if (validPasskeys.length > 0 && onPasskeyAuth && !isBusy) {
|
|
131
|
+
passkeyContainer = document.createElement('div');
|
|
132
|
+
passkeyContainer.id = 'fullscreen-dialog-passkey-container';
|
|
133
|
+
passkeyContainer.style.margin = '1rem 0';
|
|
134
|
+
passkeyContainer.style.textAlign = 'center';
|
|
135
|
+
|
|
136
|
+
btnPasskey = document.createElement('button');
|
|
137
|
+
btnPasskey.id = 'fullscreen-dialog-btn-passkey';
|
|
138
|
+
btnPasskey.type = 'button';
|
|
139
|
+
btnPasskey.className = 'action-btn';
|
|
140
|
+
btnPasskey.style.width = '100%';
|
|
141
|
+
btnPasskey.style.padding = '0.75rem 1rem';
|
|
142
|
+
btnPasskey.style.background = 'linear-gradient(135deg, #1b5e20, #2e7d32)';
|
|
143
|
+
btnPasskey.style.color = '#ffffff';
|
|
144
|
+
btnPasskey.style.fontWeight = 'bold';
|
|
145
|
+
btnPasskey.style.borderRadius = '8px';
|
|
146
|
+
btnPasskey.style.border = '1px solid rgba(255,255,255,0.2)';
|
|
147
|
+
btnPasskey.style.cursor = 'pointer';
|
|
148
|
+
btnPasskey.style.fontSize = '0.95rem';
|
|
149
|
+
btnPasskey.style.marginBottom = '0.75rem';
|
|
150
|
+
btnPasskey.textContent = passkeyButtonTitle || '🛡️ Touch ID / Security Key';
|
|
151
|
+
|
|
152
|
+
if (validPasskeys.length > 1) {
|
|
153
|
+
selectPasskey = document.createElement('select');
|
|
154
|
+
selectPasskey.style.width = '100%';
|
|
155
|
+
selectPasskey.style.padding = '0.4rem';
|
|
156
|
+
selectPasskey.style.marginBottom = '0.5rem';
|
|
157
|
+
selectPasskey.style.borderRadius = '4px';
|
|
158
|
+
selectPasskey.style.background = 'rgba(0,0,0,0.3)';
|
|
159
|
+
selectPasskey.style.color = 'inherit';
|
|
160
|
+
selectPasskey.style.border = '1px solid rgba(255,255,255,0.2)';
|
|
161
|
+
validPasskeys.forEach((p: any, idx: number) => {
|
|
162
|
+
const opt = document.createElement('option');
|
|
163
|
+
opt.value = idx.toString();
|
|
164
|
+
opt.textContent = `${p.name || 'Key'} (${p.credentialId?.substring(0, 8)}...)`;
|
|
165
|
+
selectPasskey!.appendChild(opt);
|
|
166
|
+
});
|
|
167
|
+
passkeyContainer.appendChild(selectPasskey);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
passkeyContainer.appendChild(btnPasskey);
|
|
171
|
+
|
|
172
|
+
const divider = document.createElement('div');
|
|
173
|
+
divider.style.display = 'flex';
|
|
174
|
+
divider.style.alignItems = 'center';
|
|
175
|
+
divider.style.justifyContent = 'center';
|
|
176
|
+
divider.style.gap = '0.5rem';
|
|
177
|
+
divider.style.fontSize = '0.8rem';
|
|
178
|
+
divider.style.color = 'var(--clr-text-secondary, #b3b3b3)';
|
|
179
|
+
divider.style.margin = '0.5rem 0';
|
|
180
|
+
divider.innerHTML = `
|
|
181
|
+
<span style="flex: 1; height: 1px; background: rgba(255,255,255,0.15);"></span>
|
|
182
|
+
<span>OR enter Password below</span>
|
|
183
|
+
<span style="flex: 1; height: 1px; background: rgba(255,255,255,0.15);"></span>
|
|
184
|
+
`;
|
|
185
|
+
passkeyContainer.appendChild(divider);
|
|
186
|
+
|
|
187
|
+
promptInput.parentElement?.insertBefore(passkeyContainer, promptInput);
|
|
188
|
+
}
|
|
189
|
+
|
|
115
190
|
// reset the input classList and button displays
|
|
116
191
|
promptInput.classList.remove('collapsed');
|
|
117
192
|
cancelButton.classList.remove('collapsed');
|
|
@@ -241,6 +316,12 @@ export async function showFullscreenDialog({
|
|
|
241
316
|
if (prompt || showInput) {
|
|
242
317
|
promptInput.removeEventListener('keypress', onKeypress);
|
|
243
318
|
}
|
|
319
|
+
if (btnPasskey) {
|
|
320
|
+
btnPasskey.removeEventListener('click', onPasskeyClick);
|
|
321
|
+
}
|
|
322
|
+
if (passkeyContainer) {
|
|
323
|
+
passkeyContainer.remove();
|
|
324
|
+
}
|
|
244
325
|
okButton.removeEventListener('click', onOK);
|
|
245
326
|
cancelButton.removeEventListener('click', onCancel);
|
|
246
327
|
dialog.removeEventListener('close', onClose); // Remove dialog-level listener too
|
|
@@ -253,9 +334,45 @@ export async function showFullscreenDialog({
|
|
|
253
334
|
true.toString();
|
|
254
335
|
if (promptInput) { promptInput.value = ''; } // Clear input for next time
|
|
255
336
|
dialog.close(result);
|
|
256
|
-
|
|
337
|
+
if (validPasskeys.length > 0) {
|
|
338
|
+
resolve({ secret: result, poolId: 'manage' });
|
|
339
|
+
} else {
|
|
340
|
+
resolve(result);
|
|
341
|
+
}
|
|
257
342
|
};
|
|
258
343
|
|
|
344
|
+
/** user clicks Passkey button */
|
|
345
|
+
const onPasskeyClick = async () => {
|
|
346
|
+
try {
|
|
347
|
+
if (!onPasskeyAuth) return;
|
|
348
|
+
btnPasskey!.disabled = true;
|
|
349
|
+
btnPasskey!.textContent = 'Waiting for biometrics / security key...';
|
|
350
|
+
let selectedPasskey = validPasskeys[0];
|
|
351
|
+
if (selectPasskey) {
|
|
352
|
+
const selectedIdx = parseInt(selectPasskey.value, 10);
|
|
353
|
+
selectedPasskey = validPasskeys[selectedIdx] || validPasskeys[0];
|
|
354
|
+
}
|
|
355
|
+
const passkeySecret = await onPasskeyAuth(selectedPasskey);
|
|
356
|
+
if (passkeySecret) {
|
|
357
|
+
removeEventListeners();
|
|
358
|
+
if (promptInput) { promptInput.value = ''; }
|
|
359
|
+
dialog.close(passkeySecret);
|
|
360
|
+
resolve({ secret: passkeySecret, poolId: selectedPasskey.poolId || 'passkey-manage' });
|
|
361
|
+
} else {
|
|
362
|
+
btnPasskey!.disabled = false;
|
|
363
|
+
btnPasskey!.textContent = passkeyButtonTitle || '🛡️ Touch ID / Security Key';
|
|
364
|
+
}
|
|
365
|
+
} catch (err) {
|
|
366
|
+
console.warn('Passkey assertion in dialog failed/cancelled:', err);
|
|
367
|
+
btnPasskey!.disabled = false;
|
|
368
|
+
btnPasskey!.textContent = passkeyButtonTitle || '🛡️ Touch ID / Security Key';
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
if (btnPasskey) {
|
|
373
|
+
btnPasskey.addEventListener('click', onPasskeyClick);
|
|
374
|
+
}
|
|
375
|
+
|
|
259
376
|
/** user actively clicks Cancel */
|
|
260
377
|
const onCancel = () => {
|
|
261
378
|
removeEventListeners();
|
|
@@ -264,11 +381,11 @@ export async function showFullscreenDialog({
|
|
|
264
381
|
resolve(undefined);
|
|
265
382
|
};
|
|
266
383
|
/**
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
384
|
+
* Handle dialog.close() without button click (e.g., Esc key)
|
|
385
|
+
*
|
|
386
|
+
* NOTE: this does not trigger when dialog.close() is executed. This
|
|
387
|
+
* is only firing when esc is pressed.
|
|
388
|
+
*/
|
|
272
389
|
const onClose = () => {
|
|
273
390
|
removeEventListeners();
|
|
274
391
|
if (promptInput) { promptInput.value = ''; } // Clear input for next time
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ibgib-create-app-server
|
|
3
|
+
description: Scaffolds the backend Node.js server for an ibgib full-stack application using serve-gib from @ibgib/node-gib, including WebSocket sync upgrade, Traefik Docker Compose, and server TypeScript configuration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: ibgib-create-app-server
|
|
7
|
+
|
|
8
|
+
## What This Skill Does
|
|
9
|
+
|
|
10
|
+
Generates the backend server infrastructure for a full-stack ibgib application. This includes:
|
|
11
|
+
- **`src/server/server.mts`**: Node.js HTTP/WebSocket server utilizing `serve-gib`, `getStandardServeGibHandlers()`, and `SyncUpgradeHandler`.
|
|
12
|
+
- **`tsconfig.server.json`**: TypeScript compiler configuration targeted for Node.js ESNext.
|
|
13
|
+
- **`Dockerfile`**: Lightweight Alpine container packaging the bundled server and static client SPA.
|
|
14
|
+
- **`docker-compose.yml`**: Local multi-container orchestration behind the Traefik reverse proxy with automatic local HTTPS.
|
|
15
|
+
|
|
16
|
+
This skill is invoked during the scaffolding of **Full-Stack (Archetype A)** applications.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Token Reference
|
|
21
|
+
|
|
22
|
+
| Token | Example value | How it's computed |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `{{APP_NAME}}` | `my-app` | kebab-case application name |
|
|
25
|
+
| `{{APP_DIR_NAME}}` | `my-app` | Folder name |
|
|
26
|
+
| `{{APP_HUMAN_NAME}}` | `My App` | Natural language name |
|
|
27
|
+
| `{{PORT}}` | `3000` | Server port (default `3000`) |
|
|
28
|
+
| `{{DATA_DIR_REL}}` | `../../.data/my-app-space` | Local storage directory relative to server |
|
|
29
|
+
| `{{LOCAL_DOMAIN}}` | `my-app.localhost` | Local HTTPS subdomain |
|
|
30
|
+
| `{{APP_UUID}}` | `4b810a9c...` | Unique 32-char hex string from `generate-id.js` |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Generation Steps
|
|
35
|
+
|
|
36
|
+
### 1. Confirm token values
|
|
37
|
+
Derive tokens from the developer's inputs and generate fresh UUIDs using `node .agents/skills/ibgib-create-app-core/generate-id.js`.
|
|
38
|
+
|
|
39
|
+
### 2. Generate files
|
|
40
|
+
Apply token substitutions to templates in `templates/` and write to the target application directory:
|
|
41
|
+
|
|
42
|
+
| Template file | Output file |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `templates/server.mts.template` | `src/server/server.mts` |
|
|
45
|
+
| `templates/tsconfig.server.json.template` | `tsconfig.server.json` |
|
|
46
|
+
| `templates/Dockerfile.template` | `Dockerfile` |
|
|
47
|
+
| `templates/docker-compose.yml.template` | `docker-compose.yml` (or app service block) |
|
|
48
|
+
|
|
49
|
+
### 3. Update Build Script
|
|
50
|
+
Ensure the application's `build-[app-name].mts` script configures `tsconfigConfigs` for both client (`tsconfig.json`) and server (`tsconfig.server.json`), and builds the server entry point via `buildServer()`.
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { SsoServerConfig, SsoProviderServerConfig, SsoProviderId } from './sso-types.mjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Loads the SSO configuration from environment variables.
|
|
5
|
-
* Parses active OAuth2 providers dynamically based on their environment keys.
|
|
6
|
-
*/
|
|
7
|
-
export function loadSsoServerConfig(): SsoServerConfig {
|
|
8
|
-
const ssoServerKdfSecret = process.env.SSO_SERVER_KDF_SECRET;
|
|
9
|
-
const sessionSecret = process.env.SESSION_SECRET;
|
|
10
|
-
|
|
11
|
-
if (!ssoServerKdfSecret) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
`SSO_SERVER_KDF_SECRET is missing from environment. (E: 840899ab4fdfd23e89cfae1889cfae12)`
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
if (!sessionSecret) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`SESSION_SECRET is missing from environment. (E: bfa899ab4fdfd23e89cfae23)`
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const providers: Partial<Record<SsoProviderId, SsoProviderServerConfig>> = {};
|
|
23
|
-
|
|
24
|
-
// Load Google Provider if configured
|
|
25
|
-
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
|
|
26
|
-
providers.google = {
|
|
27
|
-
providerId: 'google',
|
|
28
|
-
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
29
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
30
|
-
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
31
|
-
userInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',
|
|
32
|
-
jwksUrl: 'https://www.googleapis.com/oauth2/v3/certs'
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Load GitHub Provider if configured
|
|
37
|
-
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
|
|
38
|
-
providers.github = {
|
|
39
|
-
providerId: 'github',
|
|
40
|
-
clientId: process.env.GITHUB_CLIENT_ID,
|
|
41
|
-
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
42
|
-
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
43
|
-
userInfoUrl: 'https://api.github.com/user'
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
ssoServerKdfSecret,
|
|
49
|
-
sessionSecret,
|
|
50
|
-
providers
|
|
51
|
-
};
|
|
52
|
-
}
|