@idp.global/catalog 3.6.0 → 5.0.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": "3.6.0",
3
+ "version": "5.0.0",
4
4
  "private": false,
5
5
  "description": "Web component catalog for idp.global, based on the v5 Apple-system design language.",
6
6
  "exports": {
@@ -12,25 +12,24 @@
12
12
  "author": "Task Venture Capital GmbH",
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@design.estate/dees-catalog": "^3.90.1",
15
+ "@design.estate/dees-catalog": "^3.96.3",
16
16
  "@design.estate/dees-domtools": "^2.5.6",
17
- "@design.estate/dees-element": "^2.2.4",
18
- "@design.estate/dees-wcctools": "^3.9.0",
19
- "lucide": "^1.16.0"
17
+ "@design.estate/dees-element": "^2.4.0",
18
+ "@design.estate/dees-wcctools": "^3.9.2",
19
+ "@idp.global/interfaces": "^13.0.0",
20
+ "lucide": "^1.24.0"
20
21
  },
21
22
  "devDependencies": {
22
23
  "@git.zone/tsbuild": "^4.4.2",
23
- "@git.zone/tsbundle": "^2.11.0",
24
+ "@git.zone/tsbundle": "^2.11.2",
24
25
  "@git.zone/tswatch": "^3.3.5",
25
26
  "@push.rocks/projectinfo": "^5.1.0",
26
- "@types/node": "^25.9.4"
27
+ "@types/node": "^26.1.1"
27
28
  },
28
29
  "files": [
29
30
  "ts_web/**/*",
30
31
  "dist_ts_web/**/*",
31
- "dist_bundle/**/*",
32
32
  "assets/**/*",
33
- "html/**/*",
34
33
  "license",
35
34
  "readme.md",
36
35
  "changelog.md"
package/readme.md CHANGED
@@ -78,6 +78,36 @@ The catalog uses shared tokens from `ts_web/elements/tokens.ts` for color, type,
78
78
 
79
79
  This package is component-only. It does not perform authentication, store sessions, call the idp.global backend, or implement business logic. Use it together with `@idp.global/sdk/browser` and `@idp.global/interfaces` when you need live identity behavior.
80
80
 
81
+ Admin activity surfaces accept `TIdpAdminActivity`, an alias of the structured
82
+ `@idp.global/interfaces` activity DTO. Activity rendering uses finite actions,
83
+ outcomes, target types, and reason codes; it does not accept record IDs,
84
+ free-text descriptions, IP addresses, user agents, or generic metadata.
85
+
86
+ ```ts
87
+ import {
88
+ formatAdminActivityDetail,
89
+ formatAdminActivityTitle,
90
+ getAdminActivityTone,
91
+ type TIdpAdminActivity,
92
+ } from '@idp.global/catalog';
93
+
94
+ const activity: TIdpAdminActivity = {
95
+ action: 'passkey_login',
96
+ timestamp: Date.now(),
97
+ outcome: 'succeeded',
98
+ targetType: 'passkey',
99
+ facts: null,
100
+ };
101
+
102
+ const title = formatAdminActivityTitle(activity);
103
+ const detail = formatAdminActivityDetail(activity);
104
+ const statusColor = getAdminActivityTone(activity);
105
+ ```
106
+
107
+ The npm package publishes ESM component modules, declarations, and assets. The
108
+ local showcase HTML and its browser bundle are development artifacts and are
109
+ not part of the npm tarball.
110
+
81
111
  ## Development
82
112
 
83
113
  ```bash
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@idp.global/catalog',
6
- version: '3.6.0',
6
+ version: '5.0.0',
7
7
  description: 'Web component catalog for idp.global, based on the v5 Apple-system design language.'
8
8
  }
@@ -1,6 +1,16 @@
1
1
  import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
2
2
  import { idpElementStyles } from './tokens.js';
3
- import { idpAdminSharedStyles, renderAdminKpi, renderAdminStateCard, formatAdminTimeAgo, type TIdpAdminKpi } from './idp-admin-shared.js';
3
+ import {
4
+ idpAdminSharedStyles,
5
+ renderAdminKpi,
6
+ renderAdminStateCard,
7
+ formatAdminTimeAgo,
8
+ formatAdminActivityTitle,
9
+ formatAdminActivityDetail,
10
+ getAdminActivityTone,
11
+ type TIdpAdminActivity,
12
+ type TIdpAdminKpi,
13
+ } from './idp-admin-shared.js';
4
14
  import './idp-icon.js';
5
15
  import './idp-badge.js';
6
16
  import '@design.estate/dees-catalog/dist_ts_web/elements/00group-chart/dees-chart-area/index.js';
@@ -9,7 +19,6 @@ import type {
9
19
  IIdpAdminUser,
10
20
  IIdpAdminOrg,
11
21
  IIdpAdminSession,
12
- IIdpAdminActivity,
13
22
  IIdpAdminPassportDevice,
14
23
  IIdpAdminOverviewData,
15
24
  IIdpAdminNavigateEventDetail,
@@ -38,8 +47,8 @@ export class IdpAdminOverview extends DeesElement {
38
47
  { id: 's2', deviceName: 'iPhone 15 Pro', browser: 'Safari', os: 'iOS 18', ip: '84.132.10.4', lastActive: now - 3600000, createdAt: now - 86400000 * 10, isCurrent: false },
39
48
  ]}
40
49
  .activities=${[
41
- { id: 'a1', action: 'login_success', description: 'Signed in with passkey', timestamp: now - 300000 },
42
- { id: 'a2', action: 'app_authorized', description: 'Granted access to foss.global', timestamp: now - 7200000 },
50
+ { action: 'passkey_login', timestamp: now - 300000, outcome: 'succeeded', targetType: 'passkey', facts: null },
51
+ { action: 'oidc_authorization_granted', timestamp: now - 7200000, outcome: 'succeeded', targetType: 'oidc_authorization', facts: { grantType: 'authorization_code' } },
43
52
  ]}
44
53
  .overviewData=${{
45
54
  chartLabel: 'Approval activity',
@@ -190,7 +199,7 @@ export class IdpAdminOverview extends DeesElement {
190
199
  public accessor sessions: IIdpAdminSession[] = [];
191
200
 
192
201
  @property({ type: Array })
193
- public accessor activities: IIdpAdminActivity[] = [];
202
+ public accessor activities: TIdpAdminActivity[] = [];
194
203
 
195
204
  @property({ type: Array })
196
205
  public accessor passportDevices: IIdpAdminPassportDevice[] = [];
@@ -371,7 +380,7 @@ export class IdpAdminOverview extends DeesElement {
371
380
  <div class="card">
372
381
  <div class="card-head"><span class="card-title">Recent activity</span><idp-badge>${this.activities.length} events</idp-badge></div>
373
382
  ${this.activities.length ? this.activities.slice(0, 8).map((activityArg) => html`
374
- <div class="feed-item"><span class="feed-dot" style="--dot-color:var(--idp-info)"></span><div class="feed-text"><strong>${activityArg.action.replace(/_/g, ' ')}</strong> - ${activityArg.description}</div><span class="feed-meta">${formatAdminTimeAgo(activityArg.timestamp)}</span></div>
383
+ <div class="feed-item"><span class="feed-dot" style="--dot-color:${getAdminActivityTone(activityArg)}"></span><div class="feed-text"><strong>${formatAdminActivityTitle(activityArg)}</strong> - ${formatAdminActivityDetail(activityArg)}</div><span class="feed-meta">${formatAdminTimeAgo(activityArg.timestamp)}</span></div>
375
384
  `) : renderAdminStateCard('No activity yet', 'Activity events will appear here after logins, app changes, and organisation updates.', 'activity')}
376
385
  </div>
377
386
  <div class="card">
@@ -1,6 +1,9 @@
1
1
  import { css, html, type TemplateResult } from '@design.estate/dees-element';
2
+ import type { dto } from '@idp.global/interfaces';
2
3
  import './idp-icon.js';
3
4
 
5
+ export type TIdpAdminActivity = dto.TActivityEventDto;
6
+
4
7
  export type TIdpAdminKpi = {
5
8
  label: string;
6
9
  value: string;
@@ -417,3 +420,31 @@ export const formatAdminTimeAgo = (timestampArg?: number): string => {
417
420
  if (days < 7) return `${days}d ago`;
418
421
  return new Date(timestampArg).toLocaleDateString();
419
422
  };
423
+
424
+ export const formatAdminActivityTitle = (activityArg: TIdpAdminActivity): string =>
425
+ activityArg.action
426
+ .split('_')
427
+ .map((segmentArg) => `${segmentArg.slice(0, 1).toUpperCase()}${segmentArg.slice(1)}`)
428
+ .join(' ');
429
+
430
+ export const formatAdminActivityDetail = (activityArg: TIdpAdminActivity): string => {
431
+ const classifications = [
432
+ activityArg.outcome,
433
+ activityArg.targetType.replace(/_/g, ' '),
434
+ activityArg.reasonCode?.replace(/_/g, ' '),
435
+ ];
436
+ return classifications.filter(Boolean).join(' · ');
437
+ };
438
+
439
+ export const getAdminActivityTone = (activityArg: TIdpAdminActivity): string => {
440
+ switch (activityArg.outcome) {
441
+ case 'succeeded':
442
+ return 'var(--idp-ok)';
443
+ case 'denied':
444
+ return 'var(--idp-warn)';
445
+ case 'failed':
446
+ return 'var(--idp-error)';
447
+ case 'attempted':
448
+ return 'var(--idp-info)';
449
+ }
450
+ };
@@ -10,7 +10,17 @@ import './idp-admin-sidebar.js';
10
10
  import './idp-admin-overview.js';
11
11
  import './idp-sso-connection-form.js';
12
12
  import './idp-scim-token-panel.js';
13
- import { idpAdminSharedStyles, renderAdminKpi, renderAdminStateCard, formatAdminTimeAgo, type TIdpAdminKpi } from './idp-admin-shared.js';
13
+ import {
14
+ idpAdminSharedStyles,
15
+ renderAdminKpi,
16
+ renderAdminStateCard,
17
+ formatAdminTimeAgo,
18
+ formatAdminActivityTitle,
19
+ formatAdminActivityDetail,
20
+ getAdminActivityTone,
21
+ type TIdpAdminActivity,
22
+ type TIdpAdminKpi,
23
+ } from './idp-admin-shared.js';
14
24
  import type { IIdpDelegation, IIdpDelegationApp } from './idp-delegation-section.js';
15
25
  import type { IIdpSsoConnectionItem, IIdpSsoSpDetails } from './idp-sso-connection-form.js';
16
26
  import type { IIdpScimTokenItem } from './idp-scim-token-panel.js';
@@ -130,7 +140,7 @@ export interface IIdpAdminGaAppConfigureEventDetail {
130
140
  redirectUris: string[];
131
141
  postLogoutRedirectUris: string[];
132
142
  allowedScopes: string[];
133
- grantTypes: Array<'authorization_code' | 'client_credentials' | 'refresh_token'>;
143
+ grantTypes: Array<'authorization_code' | 'refresh_token'>;
134
144
  oidcSettings: {
135
145
  accessTokenLifetime: number;
136
146
  idTokenLifetime: number;
@@ -199,15 +209,6 @@ export interface IIdpAdminSession {
199
209
  isCurrent: boolean;
200
210
  }
201
211
 
202
- export interface IIdpAdminActivity {
203
- id: string;
204
- action: string;
205
- description: string;
206
- timestamp: number;
207
- ip?: string;
208
- targetType?: string;
209
- }
210
-
211
212
  export interface IIdpAdminApp {
212
213
  id: string;
213
214
  name: string;
@@ -1007,7 +1008,7 @@ export class IdpAdminShell extends DeesElement {
1007
1008
  public accessor sessions: IIdpAdminSession[] = [];
1008
1009
 
1009
1010
  @property({ type: Array })
1010
- public accessor activities: IIdpAdminActivity[] = [];
1011
+ public accessor activities: TIdpAdminActivity[] = [];
1011
1012
 
1012
1013
  @property({ type: Array })
1013
1014
  public accessor orgMembers: IIdpAdminMember[] = [];
@@ -1734,10 +1735,10 @@ export class IdpAdminShell extends DeesElement {
1734
1735
  const postLogoutRedirectUris = this.parseLines(this.dialogPostLogoutRedirectUris);
1735
1736
  const allowedScopes = this.parseCsv(this.dialogAllowedScopes);
1736
1737
  const configuredGrantTypes = this.parseCsv(this.dialogGrantTypes);
1737
- const supportedGrantTypes = ['authorization_code', 'client_credentials', 'refresh_token'];
1738
+ const supportedGrantTypes = ['authorization_code', 'refresh_token'];
1738
1739
  const invalidGrantTypes = configuredGrantTypes.filter((grantArg) => !supportedGrantTypes.includes(grantArg));
1739
1740
  const grantTypes = configuredGrantTypes
1740
- .filter((grantArg): grantArg is 'authorization_code' | 'client_credentials' | 'refresh_token' => supportedGrantTypes.includes(grantArg));
1741
+ .filter((grantArg): grantArg is 'authorization_code' | 'refresh_token' => supportedGrantTypes.includes(grantArg));
1741
1742
  const invalidUris = [...redirectUris, ...postLogoutRedirectUris].filter((uriArg) => !this.isValidExactUri(uriArg));
1742
1743
  const lifetimesAreValid = [this.dialogAccessTokenLifetime, this.dialogIdTokenLifetime]
1743
1744
  .every((lifetimeArg) => Number.isInteger(lifetimeArg) && lifetimeArg >= 300 && lifetimeArg <= 86400)
@@ -1764,10 +1765,6 @@ export class IdpAdminShell extends DeesElement {
1764
1765
  this.dialogError = 'The refresh_token grant requires the authorization_code grant.';
1765
1766
  return;
1766
1767
  }
1767
- if (this.dialogClientType === 'public' && grantTypes.includes('client_credentials')) {
1768
- this.dialogError = 'Public clients cannot use the client_credentials grant.';
1769
- return;
1770
- }
1771
1768
  if (!lifetimesAreValid) {
1772
1769
  this.dialogError = 'Access and ID token lifetimes must be 300 to 86400 seconds; refresh family lifetime must be 3600 to 7776000 seconds.';
1773
1770
  return;
@@ -2034,7 +2031,7 @@ export class IdpAdminShell extends DeesElement {
2034
2031
  ${this.renderFormRow('Callback URIs', 'One exact absolute URI per line; required for authorization_code.', html`<textarea class="textarea" .value=${this.dialogRedirectUris} @input=${(eventArg: Event) => { this.dialogRedirectUris = (eventArg.target as HTMLTextAreaElement).value; }}></textarea>`)}
2035
2032
  ${this.renderFormRow('Post-logout URIs', 'One exact RP-Initiated Logout destination per line.', html`<textarea class="textarea" .value=${this.dialogPostLogoutRedirectUris} @input=${(eventArg: Event) => { this.dialogPostLogoutRedirectUris = (eventArg.target as HTMLTextAreaElement).value; }}></textarea>`)}
2036
2033
  ${this.renderFormRow('Allowed scopes', 'Comma-separated scopes; authorization_code requires openid.', html`<input class="input" .value=${this.dialogAllowedScopes} @input=${(eventArg: Event) => { this.dialogAllowedScopes = (eventArg.target as HTMLInputElement).value; }} />`, true)}
2037
- ${this.renderFormRow('Grant types', 'authorization_code, client_credentials, and refresh_token are supported.', html`<input class="input" .value=${this.dialogGrantTypes} @input=${(eventArg: Event) => { this.dialogGrantTypes = (eventArg.target as HTMLInputElement).value; }} />`, true)}
2034
+ ${this.renderFormRow('Grant types', 'authorization_code and refresh_token are supported.', html`<input class="input" .value=${this.dialogGrantTypes} @input=${(eventArg: Event) => { this.dialogGrantTypes = (eventArg.target as HTMLInputElement).value; }} />`, true)}
2038
2035
  <div class="mapping-row">
2039
2036
  <div>${this.renderFormRow('Access token seconds', '300 to 86400.', html`<input class="input" type="number" .value=${String(this.dialogAccessTokenLifetime)} @input=${(eventArg: Event) => this.setDialogNumber('access', eventArg)} />`, true)}</div>
2040
2037
  <div>${this.renderFormRow('ID token seconds', '300 to 86400.', html`<input class="input" type="number" .value=${String(this.dialogIdTokenLifetime)} @input=${(eventArg: Event) => this.setDialogNumber('id', eventArg)} />`, true)}</div>
@@ -2250,10 +2247,10 @@ export class IdpAdminShell extends DeesElement {
2250
2247
  ${this.deleteAccountOpen
2251
2248
  ? html`
2252
2249
  ${this.renderFormRow('Confirm your email', 'Type your account email to confirm', html`<input class="input" .value=${this.deleteAccountConfirmEmail} ?disabled=${this.accountDeleting} @input=${(eventArg: Event) => { this.deleteAccountConfirmEmail = (eventArg.target as HTMLInputElement).value; }} />`, true)}
2253
- ${this.renderFormRow('Password', 'Required when your account has one', html`<input class="input" type="password" autocomplete="current-password" .value=${this.deleteAccountPassword} ?disabled=${this.accountDeleting} @input=${(eventArg: Event) => { this.deleteAccountPassword = (eventArg.target as HTMLInputElement).value; }} />`)}
2250
+ ${this.renderFormRow('Password', 'Required. Passwordless account deletion needs a future fresh-auth step-up.', html`<input class="input" type="password" autocomplete="current-password" .value=${this.deleteAccountPassword} ?disabled=${this.accountDeleting} @input=${(eventArg: Event) => { this.deleteAccountPassword = (eventArg.target as HTMLInputElement).value; }} />`)}
2254
2251
  <div class="split-row" style="justify-content:flex-end;gap:10px;margin-top:14px">
2255
2252
  <idp-button variant="outline" shape="rect" ?disabled=${this.accountDeleting} @click=${() => { this.deleteAccountOpen = false; this.deleteAccountPassword = ''; this.deleteAccountConfirmEmail = ''; }}>Cancel</idp-button>
2256
- <idp-button variant="accent" shape="rect" ?loading=${this.accountDeleting} ?disabled=${this.deleteAccountConfirmEmail.trim().toLowerCase() !== (this.user?.email || '').trim().toLowerCase()} @click=${() => this.dispatchShellEvent<IIdpAdminAccountDeleteEventDetail>('idp-admin-account-delete', { password: this.deleteAccountPassword, confirmEmail: this.deleteAccountConfirmEmail.trim() })}>Delete my account</idp-button>
2253
+ <idp-button variant="accent" shape="rect" ?loading=${this.accountDeleting} ?disabled=${!this.deleteAccountPassword || this.deleteAccountConfirmEmail.trim().toLowerCase() !== (this.user?.email || '').trim().toLowerCase()} @click=${() => this.dispatchShellEvent<IIdpAdminAccountDeleteEventDetail>('idp-admin-account-delete', { password: this.deleteAccountPassword, confirmEmail: this.deleteAccountConfirmEmail.trim() })}>Delete my account</idp-button>
2257
2254
  </div>
2258
2255
  `
2259
2256
  : html`<div class="split-row"><div><div class="section-title">Delete this account</div><div class="muted">Organisations you own must be transferred or deleted first.</div></div><idp-button variant="outline" shape="rect" @click=${() => { this.deleteAccountOpen = true; }}>Delete account…</idp-button></div>`}
@@ -2426,14 +2423,10 @@ export class IdpAdminShell extends DeesElement {
2426
2423
  const org = this.currentOrg;
2427
2424
  const connectedAppCount = this.orgApps.filter((appArg) => appArg.isConnected).length;
2428
2425
  const orgActivities = this.activities
2429
- .filter((activityArg) => {
2430
- const searchableText = `${activityArg.targetType || ''} ${activityArg.description || ''}`.toLowerCase();
2431
- return Boolean(org.slug && searchableText.includes(org.slug.toLowerCase()))
2432
- || Boolean(org.name && searchableText.includes(org.name.toLowerCase()))
2433
- || searchableText.includes('org')
2434
- || searchableText.includes('organization')
2435
- || searchableText.includes('organisation');
2436
- })
2426
+ .filter((activityArg) =>
2427
+ activityArg.targetType === 'organization'
2428
+ || activityArg.targetType.startsWith('organization_')
2429
+ )
2437
2430
  .slice(0, 5);
2438
2431
  const kpis: TIdpAdminKpi[] = [
2439
2432
  {
@@ -2500,10 +2493,10 @@ export class IdpAdminShell extends DeesElement {
2500
2493
  </div>
2501
2494
  </div>
2502
2495
  <div class="card">
2503
- <div class="card-head"><span class="card-title">Recent organisation activity</span><idp-badge>${orgActivities.length} events</idp-badge></div>
2496
+ <div class="card-head"><span class="card-title">Organisation-classified account activity</span><idp-badge>${orgActivities.length} events</idp-badge></div>
2504
2497
  ${orgActivities.length ? orgActivities.map((activityArg) => html`
2505
- <div class="feed-item"><span class="feed-dot" style="--dot-color:var(--idp-info)"></span><div class="feed-text"><strong>${activityArg.action.replace(/_/g, ' ')}</strong> - ${activityArg.description}</div><span class="feed-meta">${this.formatTimeAgo(activityArg.timestamp)}</span></div>
2506
- `) : this.renderStateCard('No org activity yet', 'Organisation-related activity will appear here when the backend reports matching activity events.', 'activity')}
2498
+ <div class="feed-item"><span class="feed-dot" style="--dot-color:${getAdminActivityTone(activityArg)}"></span><div class="feed-text"><strong>${formatAdminActivityTitle(activityArg)}</strong> - ${formatAdminActivityDetail(activityArg)}</div><span class="feed-meta">${this.formatTimeAgo(activityArg.timestamp)}</span></div>
2499
+ `) : this.renderStateCard('No organisation activity yet', 'Account events with an organisation target classification will appear here without exposing target identities.', 'activity')}
2507
2500
  </div>
2508
2501
  </div>
2509
2502
  </div>