@idp.global/catalog 1.1.1 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idp.global/catalog",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "description": "Web component catalog for idp.global, based on the v3 product design language.",
6
6
  "exports": {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@idp.global/catalog',
6
- version: '1.1.1',
6
+ version: '1.2.0',
7
7
  description: 'Web component catalog for idp.global, based on the v3 product design language.'
8
8
  }
@@ -163,6 +163,24 @@ export interface IIdpAdminPassportEnrollment {
163
163
  expiresAt: number;
164
164
  }
165
165
 
166
+ export interface IIdpAdminPasskey {
167
+ id: string;
168
+ label: string;
169
+ credentialId?: string;
170
+ status: string;
171
+ backedUp?: boolean;
172
+ deviceType?: string;
173
+ transports?: string[];
174
+ createdAt: number;
175
+ lastUsedAt?: number | null;
176
+ }
177
+
178
+ export interface IIdpAdminTotpEnrollment {
179
+ credentialId: string;
180
+ secret: string;
181
+ otpauthUrl: string;
182
+ }
183
+
166
184
  export interface IIdpAdminSessionEventDetail {
167
185
  sessionId: string;
168
186
  }
@@ -237,6 +255,23 @@ export interface IIdpAdminPassportDeviceEventDetail {
237
255
  deviceId: string;
238
256
  }
239
257
 
258
+ export interface IIdpAdminTotpVerifyEventDetail {
259
+ credentialId: string;
260
+ code: string;
261
+ }
262
+
263
+ export interface IIdpAdminTotpCodeEventDetail {
264
+ code: string;
265
+ }
266
+
267
+ export interface IIdpAdminPasskeyRegistrationEventDetail {
268
+ label: string;
269
+ }
270
+
271
+ export interface IIdpAdminPasskeyEventDetail {
272
+ passkeyId: string;
273
+ }
274
+
240
275
  @customElement('idp-admin-shell')
241
276
  export class IdpAdminShell extends DeesElement {
242
277
  public static demo = () => html`<idp-admin-shell global-admin></idp-admin-shell>`;
@@ -1239,6 +1274,18 @@ export class IdpAdminShell extends DeesElement {
1239
1274
  @property({ type: Object })
1240
1275
  public accessor passportEnrollment: IIdpAdminPassportEnrollment | null = null;
1241
1276
 
1277
+ @property({ type: Boolean, attribute: 'totp-enabled' })
1278
+ public accessor totpEnabled = false;
1279
+
1280
+ @property({ type: Number, attribute: 'backup-codes-remaining' })
1281
+ public accessor backupCodesRemaining = 0;
1282
+
1283
+ @property({ type: Object })
1284
+ public accessor totpEnrollment: IIdpAdminTotpEnrollment | null = null;
1285
+
1286
+ @property({ type: Array })
1287
+ public accessor passkeys: IIdpAdminPasskey[] = [];
1288
+
1242
1289
  @property({ type: String, attribute: 'credential-message' })
1243
1290
  public accessor credentialMessage = '';
1244
1291
 
@@ -1523,6 +1570,51 @@ export class IdpAdminShell extends DeesElement {
1523
1570
  });
1524
1571
  }
1525
1572
 
1573
+ private requestTotpEnrollment() {
1574
+ this.dispatchShellEvent('idp-admin-totp-start', {});
1575
+ }
1576
+
1577
+ private verifyTotpEnrollment() {
1578
+ if (!this.totpEnrollment) {
1579
+ return;
1580
+ }
1581
+ const code = globalThis.prompt?.('Authenticator code')?.trim();
1582
+ if (!code) {
1583
+ return;
1584
+ }
1585
+ this.dispatchShellEvent<IIdpAdminTotpVerifyEventDetail>('idp-admin-totp-verify', {
1586
+ credentialId: this.totpEnrollment.credentialId,
1587
+ code,
1588
+ });
1589
+ }
1590
+
1591
+ private disableTotp() {
1592
+ const code = globalThis.prompt?.('Enter your current authenticator code to disable TOTP')?.trim();
1593
+ if (!code) {
1594
+ return;
1595
+ }
1596
+ this.dispatchShellEvent<IIdpAdminTotpCodeEventDetail>('idp-admin-totp-disable', { code });
1597
+ }
1598
+
1599
+ private regenerateBackupCodes() {
1600
+ const code = globalThis.prompt?.('Enter your current authenticator code to regenerate backup codes')?.trim();
1601
+ if (!code) {
1602
+ return;
1603
+ }
1604
+ this.dispatchShellEvent<IIdpAdminTotpCodeEventDetail>('idp-admin-backup-codes-regenerate', { code });
1605
+ }
1606
+
1607
+ private requestPasskeyRegistration() {
1608
+ const fallbackLabel = typeof navigator !== 'undefined'
1609
+ ? navigator.userAgent.includes('Mobile') ? 'Mobile passkey' : 'Desktop passkey'
1610
+ : 'Passkey';
1611
+ const label = globalThis.prompt?.('Passkey label', fallbackLabel)?.trim();
1612
+ if (!label) {
1613
+ return;
1614
+ }
1615
+ this.dispatchShellEvent<IIdpAdminPasskeyRegistrationEventDetail>('idp-admin-passkey-register', { label });
1616
+ }
1617
+
1526
1618
  private setPage(pageArg: TIdpAdminPage) {
1527
1619
  this.page = pageArg;
1528
1620
  this.orgMenuOpen = false;
@@ -2172,6 +2264,23 @@ export class IdpAdminShell extends DeesElement {
2172
2264
  html`<div class="cell-actions"><button class="table-action destructive" @click=${() => this.dispatchShellEvent<IIdpAdminPassportDeviceEventDetail>('idp-admin-passport-revoke', { deviceId: deviceArg.id })}>Revoke</button></div>`,
2173
2265
  ],
2174
2266
  }));
2267
+ const passkeyRows = this.passkeys.map((passkeyArg) => ({
2268
+ cells: [
2269
+ html`
2270
+ <div class="identity-cell">
2271
+ <span class="identity-avatar">${this.getInitials(passkeyArg.label)}</span>
2272
+ <div>
2273
+ <div class="identity-primary">${passkeyArg.label}</div>
2274
+ <div class="identity-secondary">${passkeyArg.deviceType || 'passkey'}${passkeyArg.backedUp ? ' - backed up' : ''}</div>
2275
+ </div>
2276
+ </div>
2277
+ `,
2278
+ html`<idp-badge variant=${passkeyArg.status === 'active' ? 'ok' : 'error'}>${passkeyArg.status}</idp-badge>`,
2279
+ (passkeyArg.transports || []).join(', ') || '-',
2280
+ passkeyArg.lastUsedAt ? this.formatTimeAgo(passkeyArg.lastUsedAt) : 'never',
2281
+ html`<div class="cell-actions"><button class="table-action destructive" @click=${() => this.dispatchShellEvent<IIdpAdminPasskeyEventDetail>('idp-admin-passkey-revoke', { passkeyId: passkeyArg.id })}>Revoke</button></div>`,
2282
+ ],
2283
+ }));
2175
2284
 
2176
2285
  return html`
2177
2286
  ${this.renderPageHeader('Security', 'Manage how you authenticate and protect your account.')}
@@ -2190,6 +2299,45 @@ export class IdpAdminShell extends DeesElement {
2190
2299
  <div class="divider"></div>
2191
2300
  <button class="plain-button primary" @click=${() => this.submitPasswordChange()}>Update password</button>
2192
2301
  `)}
2302
+ ${this.renderSectionCard('Authenticator app', 'TOTP protects password and magic-link sign-ins with a six-digit code from an authenticator app.', html`
2303
+ <div class="split-row"><div><div class="section-title">Status</div><div class="muted">${this.totpEnabled ? `${this.backupCodesRemaining} backup codes remaining.` : 'No authenticator app is enrolled.'}</div></div><idp-badge variant=${this.totpEnabled ? 'ok' : 'warn'}>${this.totpEnabled ? 'Enabled' : 'Disabled'}</idp-badge></div>
2304
+ ${this.totpEnrollment ? html`
2305
+ <div class="divider"></div>
2306
+ <div class="notice-card"><idp-icon name="qr" size="16"></idp-icon><div><div class="section-title">TOTP setup pending</div><div class="muted">Scan the otpauth URL or enter the manual secret, then verify the current code.</div></div></div>
2307
+ ${this.renderFormRow('Manual secret', '', this.renderCodeBlock(this.totpEnrollment.secret))}
2308
+ ${this.renderFormRow('otpauth URL', '', this.renderCodeBlock(this.totpEnrollment.otpauthUrl))}
2309
+ <button class="plain-button primary" @click=${() => this.verifyTotpEnrollment()}>Verify setup code</button>
2310
+ ` : html`
2311
+ <div class="divider"></div>
2312
+ ${this.totpEnabled ? html`
2313
+ <div class="cell-actions" style="justify-content:flex-start">
2314
+ <button class="plain-button outline" @click=${() => this.regenerateBackupCodes()}>Regenerate backup codes</button>
2315
+ <button class="plain-button ghost" style="color:var(--idp-error)" @click=${() => this.disableTotp()}>Disable TOTP</button>
2316
+ </div>
2317
+ ` : html`<button class="plain-button primary" @click=${() => this.requestTotpEnrollment()}>Enable authenticator app</button>`}
2318
+ `}
2319
+ `)}
2320
+ <idp-data-table
2321
+ title="Passkeys"
2322
+ subtitle="WebAuthn credentials for passwordless login and MFA step-up."
2323
+ badge=${`${this.passkeys.length} total`}
2324
+ empty-title="No passkeys"
2325
+ empty-description="Register a passkey to sign in with platform authenticators or security keys."
2326
+ .columns=${[
2327
+ { label: 'Passkey' },
2328
+ { label: 'Status' },
2329
+ { label: 'Transports', hideBelow: 'mobile' },
2330
+ { label: 'Last used', mono: true, hideBelow: 'mobile' },
2331
+ { label: 'Action', align: 'right' },
2332
+ ]}
2333
+ .rows=${passkeyRows}
2334
+ ></idp-data-table>
2335
+ <div class="section-card">
2336
+ <div class="section-headline">
2337
+ <div><div class="section-title">Register passkey</div><div class="section-description">Creates a WebAuthn registration challenge in this browser.</div></div>
2338
+ <button class="plain-button outline" @click=${() => this.requestPasskeyRegistration()}>Register passkey</button>
2339
+ </div>
2340
+ </div>
2193
2341
  <idp-data-table
2194
2342
  title="Passport devices"
2195
2343
  subtitle="Cryptographic IDP Passport devices registered for this account."
@@ -2217,10 +2365,6 @@ export class IdpAdminShell extends DeesElement {
2217
2365
  ${this.renderFormRow('Signing payload', '', this.renderCodeBlock(this.passportEnrollment.signingPayload))}
2218
2366
  ` : html`<div class="muted">No active enrollment challenge.</div>`}
2219
2367
  </div>
2220
- <div class="primary-grid">
2221
- ${this.renderStateCard('TOTP controls not connected', 'No TOTP secret, enrollment, or verification endpoints exist in this backend yet, so no fake TOTP toggle is shown.', 'lock')}
2222
- ${this.renderStateCard('WebAuthn passkeys not connected', 'No WebAuthn passkey credential model or assertion endpoints exist yet. Passport devices are the available cryptographic credential path.', 'key')}
2223
- </div>
2224
2368
  </div>
2225
2369
  `;
2226
2370
  }