@idp.global/catalog 2.2.0 → 3.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.
@@ -34,4 +34,6 @@ export * from './idp-admin-shell.js';
34
34
  export * from './idp-landing-hero.js';
35
35
  export * from './idp-landing-page.js';
36
36
  export * from './idp-mobile-showcase.js';
37
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsaUNBQWlDLENBQUM7QUFDaEQsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxtQkFBbUIsQ0FBQztBQUNsQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLDBCQUEwQixDQUFDIn0=
37
+ export * from './idp-sso-connection-form.js';
38
+ export * from './idp-scim-token-panel.js';
39
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsaUNBQWlDLENBQUM7QUFDaEQsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxtQkFBbUIsQ0FBQztBQUNsQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYywyQkFBMkIsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idp.global/catalog",
3
- "version": "2.2.0",
3
+ "version": "3.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": {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@idp.global/catalog',
6
- version: '2.2.0',
6
+ version: '3.0.0',
7
7
  description: 'Web component catalog for idp.global, based on the v5 Apple-system design language.'
8
8
  }
@@ -8,8 +8,12 @@ import './idp-toggle.js';
8
8
  import './idp-delegation-section.js';
9
9
  import './idp-admin-sidebar.js';
10
10
  import './idp-admin-overview.js';
11
+ import './idp-sso-connection-form.js';
12
+ import './idp-scim-token-panel.js';
11
13
  import { idpAdminSharedStyles, renderAdminKpi, renderAdminStateCard, formatAdminTimeAgo, type TIdpAdminKpi } from './idp-admin-shared.js';
12
14
  import type { IIdpDelegation, IIdpDelegationApp } from './idp-delegation-section.js';
15
+ import type { IIdpSsoConnectionItem, IIdpSsoSpDetails } from './idp-sso-connection-form.js';
16
+ import type { IIdpScimTokenItem } from './idp-scim-token-panel.js';
13
17
 
14
18
  declare global {
15
19
  interface HTMLElementTagNameMap {
@@ -27,6 +31,7 @@ export type TIdpAdminPage =
27
31
  | 'org-settings'
28
32
  | 'org-members'
29
33
  | 'org-apps'
34
+ | 'org-sso'
30
35
  | 'support'
31
36
  | 'ga-users'
32
37
  | 'ga-orgs'
@@ -917,6 +922,18 @@ export class IdpAdminShell extends DeesElement {
917
922
  @property({ type: Array })
918
923
  public accessor gaUsers: IIdpAdminGaUser[] = [];
919
924
 
925
+ @property({ type: Array })
926
+ public accessor ssoConnections: IIdpSsoConnectionItem[] = [];
927
+
928
+ @property({ type: Object })
929
+ public accessor ssoSpDetails: IIdpSsoSpDetails | null = null;
930
+
931
+ @property({ type: Array })
932
+ public accessor scimTokens: IIdpScimTokenItem[] = [];
933
+
934
+ @property({ type: Object })
935
+ public accessor scimCreatedToken: { label: string; plaintextToken: string } | null = null;
936
+
920
937
  @property({ type: Object })
921
938
  public accessor overviewData: IIdpAdminOverviewData | null = null;
922
939
 
@@ -2220,6 +2237,25 @@ export class IdpAdminShell extends DeesElement {
2220
2237
  `;
2221
2238
  }
2222
2239
 
2240
+ private renderOrgSso(): TemplateResult {
2241
+ return html`
2242
+ ${this.renderPageHeader('SSO & Provisioning', 'Enterprise single sign-on and automated user provisioning for this organisation.')}
2243
+ <div class="body narrow-body">
2244
+ <div class="list-stack">
2245
+ <idp-sso-connection-form
2246
+ .connections=${this.ssoConnections}
2247
+ .spDetails=${this.ssoSpDetails}
2248
+ ></idp-sso-connection-form>
2249
+ <idp-scim-token-panel
2250
+ .tokens=${this.scimTokens}
2251
+ .createdToken=${this.scimCreatedToken}
2252
+ scimBaseUrl=${this.ssoSpDetails?.scimBaseUrl || ''}
2253
+ ></idp-scim-token-panel>
2254
+ </div>
2255
+ </div>
2256
+ `;
2257
+ }
2258
+
2223
2259
  private renderSupport(): TemplateResult {
2224
2260
  const services = [
2225
2261
  ['Account Recovery', 'Lost access to your account or locked out of your organisation? Our team will verify your identity and restore access securely.', 'EUR149', 'per incident', 'key', '1-2 business days'],
@@ -2409,6 +2445,7 @@ export class IdpAdminShell extends DeesElement {
2409
2445
  'org-settings': () => this.renderOrgSettings(),
2410
2446
  'org-members': () => this.renderOrgMembers(),
2411
2447
  'org-apps': () => this.renderOrgApps(),
2448
+ 'org-sso': () => this.renderOrgSso(),
2412
2449
  support: () => this.renderSupport(),
2413
2450
  'ga-users': () => this.renderGAUsers(),
2414
2451
  'ga-orgs': () => this.renderGAOrgs(),
@@ -444,6 +444,7 @@ export class IdpAdminSidebar extends DeesElement {
444
444
  { id: 'org-settings', label: 'Settings', icon: 'settings' },
445
445
  { id: 'org-members', label: 'Members', icon: 'users' },
446
446
  { id: 'org-apps', label: 'OAuth Apps', icon: 'box' },
447
+ { id: 'org-sso', label: 'SSO & Provisioning', icon: 'key' },
447
448
  ];
448
449
 
449
450
  private supportNav: TNavItem[] = [
@@ -0,0 +1,311 @@
1
+ import { DeesElement, html, property, state, customElement, css, type TemplateResult } from '@design.estate/dees-element';
2
+ import { idpElementStyles } from './tokens.js';
3
+ import './idp-badge.js';
4
+ import './idp-button.js';
5
+ import './idp-icon.js';
6
+ import './idp-input.js';
7
+
8
+ export interface IIdpScimTokenItem {
9
+ id: string;
10
+ label: string;
11
+ tokenPrefix: string;
12
+ createdAt: number;
13
+ expiresAt?: number;
14
+ lastUsedAt?: number;
15
+ revoked: boolean;
16
+ }
17
+
18
+ export interface IIdpScimTokenCreateEventDetail {
19
+ label: string;
20
+ }
21
+
22
+ export interface IIdpScimTokenRevokeEventDetail {
23
+ tokenId: string;
24
+ }
25
+
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ 'idp-scim-token-panel': IdpScimTokenPanel;
29
+ }
30
+ }
31
+
32
+ @customElement('idp-scim-token-panel')
33
+ export class IdpScimTokenPanel extends DeesElement {
34
+ public static demo = () => html`
35
+ <div style="max-width: 720px;">
36
+ <idp-scim-token-panel
37
+ scimBaseUrl="https://idp.global/scim/v2"
38
+ .tokens=${[
39
+ { id: 't1', label: 'Okta production', tokenPrefix: 'idp_scim', createdAt: Date.now() - 86400000 * 30, lastUsedAt: Date.now() - 3600000, revoked: false },
40
+ { id: 't2', label: 'Entra staging', tokenPrefix: 'idp_scim', createdAt: Date.now() - 86400000 * 90, expiresAt: Date.now() + 86400000 * 30, revoked: true },
41
+ ] as IIdpScimTokenItem[]}
42
+ .createdToken=${{ label: 'Okta production', plaintextToken: 'idp_scim_2f8Qb1x0demoTokenValueOnlyShownOnce' }}
43
+ ></idp-scim-token-panel>
44
+ </div>
45
+ `;
46
+ public static demoGroups = ['idp.global v5 admin console'];
47
+
48
+ @property({ type: Array })
49
+ public accessor tokens: IIdpScimTokenItem[] = [];
50
+
51
+ /** One-time reveal payload after a createScimToken round trip */
52
+ @property({ type: Object })
53
+ public accessor createdToken: { label: string; plaintextToken: string } | null = null;
54
+
55
+ @property({ type: String })
56
+ public accessor scimBaseUrl = '';
57
+
58
+ @state()
59
+ private accessor newLabel = '';
60
+
61
+ @state()
62
+ private accessor copied = false;
63
+
64
+ public static styles = [
65
+ ...idpElementStyles,
66
+ css`
67
+ :host {
68
+ display: block;
69
+ font-family: var(--idp-font);
70
+ color: var(--idp-fg);
71
+ }
72
+ .section {
73
+ background: var(--idp-card);
74
+ border: 1px solid var(--idp-border);
75
+ border-radius: var(--idp-radius);
76
+ padding: 20px;
77
+ }
78
+ .title {
79
+ font-size: 14px;
80
+ font-weight: 600;
81
+ }
82
+ .muted {
83
+ font-size: 12.5px;
84
+ color: var(--idp-muted-fg);
85
+ line-height: 1.5;
86
+ margin-top: 3px;
87
+ }
88
+ .endpoint {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 8px;
92
+ margin-top: 12px;
93
+ padding: 8px 10px;
94
+ background: var(--idp-muted);
95
+ border: 1px solid var(--idp-border);
96
+ border-radius: 8px;
97
+ font-family: var(--idp-mono, ui-monospace, monospace);
98
+ font-size: 12px;
99
+ overflow-wrap: anywhere;
100
+ }
101
+ .reveal {
102
+ margin-top: 14px;
103
+ padding: 12px;
104
+ border: 1px solid var(--idp-info-border, var(--idp-border));
105
+ border-radius: 10px;
106
+ background: var(--idp-accent-soft);
107
+ }
108
+ .reveal-title {
109
+ font-size: 12.5px;
110
+ font-weight: 600;
111
+ color: var(--idp-accent);
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 6px;
115
+ }
116
+ .reveal-token {
117
+ margin-top: 8px;
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ font-family: var(--idp-mono, ui-monospace, monospace);
122
+ font-size: 12.5px;
123
+ overflow-wrap: anywhere;
124
+ }
125
+ .copy-button {
126
+ flex: 0 0 auto;
127
+ border: 1px solid var(--idp-border);
128
+ background: var(--idp-card);
129
+ border-radius: 7px;
130
+ font-family: inherit;
131
+ font-size: 11px;
132
+ padding: 4px 10px;
133
+ cursor: pointer;
134
+ }
135
+ .copy-button:hover {
136
+ background: var(--idp-muted);
137
+ }
138
+ .row {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 12px;
142
+ padding: 12px 0;
143
+ border-bottom: 1px solid var(--idp-border);
144
+ }
145
+ .row:last-of-type {
146
+ border-bottom: none;
147
+ }
148
+ .row-main {
149
+ flex: 1;
150
+ min-width: 0;
151
+ }
152
+ .row-name {
153
+ font-size: 13px;
154
+ font-weight: 500;
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ }
159
+ .row-meta {
160
+ font-size: 11px;
161
+ color: var(--idp-muted-fg);
162
+ margin-top: 2px;
163
+ }
164
+ .revoke {
165
+ background: none;
166
+ border: none;
167
+ cursor: pointer;
168
+ font-family: inherit;
169
+ font-size: 11px;
170
+ font-weight: 500;
171
+ color: var(--idp-destructive);
172
+ padding: 4px 8px;
173
+ border-radius: 6px;
174
+ }
175
+ .revoke:hover {
176
+ background: var(--idp-error-bg);
177
+ }
178
+ .create-row {
179
+ display: flex;
180
+ gap: 10px;
181
+ align-items: flex-end;
182
+ margin-top: 16px;
183
+ padding-top: 16px;
184
+ border-top: 1px solid var(--idp-border);
185
+ }
186
+ .create-row idp-input {
187
+ flex: 1;
188
+ }
189
+ .empty {
190
+ padding: 18px 0;
191
+ font-size: 12.5px;
192
+ color: var(--idp-muted-fg);
193
+ }
194
+ `,
195
+ ];
196
+
197
+ private formatTimeAgo(timestampArg: number): string {
198
+ const deltaMs = Date.now() - timestampArg;
199
+ const days = Math.floor(deltaMs / 86400000);
200
+ if (days > 0) return `${days}d ago`;
201
+ const hours = Math.floor(deltaMs / 3600000);
202
+ if (hours > 0) return `${hours}h ago`;
203
+ return 'just now';
204
+ }
205
+
206
+ private async copyToken() {
207
+ if (!this.createdToken) return;
208
+ try {
209
+ await navigator.clipboard.writeText(this.createdToken.plaintextToken);
210
+ this.copied = true;
211
+ setTimeout(() => (this.copied = false), 2000);
212
+ } catch {
213
+ // clipboard unavailable; the token stays selectable
214
+ }
215
+ }
216
+
217
+ private submitCreate() {
218
+ const label = this.newLabel.trim();
219
+ if (!label) return;
220
+ this.dispatchEvent(
221
+ new CustomEvent<IIdpScimTokenCreateEventDetail>('idp-scim-token-create', {
222
+ detail: { label },
223
+ bubbles: true,
224
+ composed: true,
225
+ }),
226
+ );
227
+ this.newLabel = '';
228
+ }
229
+
230
+ public render(): TemplateResult {
231
+ return html`
232
+ <div class="section">
233
+ <div class="title">SCIM provisioning</div>
234
+ <div class="muted">
235
+ Let your identity provider create, update and deactivate accounts automatically.
236
+ Configure the endpoint below with a bearer token in Okta or Microsoft Entra.
237
+ </div>
238
+ ${this.scimBaseUrl
239
+ ? html`<div class="endpoint"><idp-icon name="globe" size="14"></idp-icon>${this.scimBaseUrl}</div>`
240
+ : html``}
241
+ ${this.createdToken
242
+ ? html`
243
+ <div class="reveal">
244
+ <div class="reveal-title">
245
+ <idp-icon name="key" size="14"></idp-icon>
246
+ Token for "${this.createdToken.label}" — copy it now, it won't be shown again
247
+ </div>
248
+ <div class="reveal-token">
249
+ <span>${this.createdToken.plaintextToken}</span>
250
+ <button class="copy-button" @click=${() => this.copyToken()}>
251
+ ${this.copied ? 'Copied' : 'Copy'}
252
+ </button>
253
+ </div>
254
+ </div>
255
+ `
256
+ : html``}
257
+ ${this.tokens.length
258
+ ? this.tokens.map(
259
+ (tokenArg) => html`
260
+ <div class="row">
261
+ <div class="row-main">
262
+ <div class="row-name">
263
+ ${tokenArg.label}
264
+ ${tokenArg.revoked
265
+ ? html`<idp-badge variant="error">revoked</idp-badge>`
266
+ : tokenArg.expiresAt && tokenArg.expiresAt < Date.now()
267
+ ? html`<idp-badge variant="warn">expired</idp-badge>`
268
+ : html`<idp-badge variant="ok">active</idp-badge>`}
269
+ </div>
270
+ <div class="row-meta">
271
+ ${tokenArg.tokenPrefix}… · created ${this.formatTimeAgo(tokenArg.createdAt)}
272
+ ${tokenArg.lastUsedAt ? ` · last used ${this.formatTimeAgo(tokenArg.lastUsedAt)}` : ' · never used'}
273
+ </div>
274
+ </div>
275
+ ${!tokenArg.revoked
276
+ ? html`
277
+ <button
278
+ class="revoke"
279
+ @click=${() =>
280
+ this.dispatchEvent(
281
+ new CustomEvent<IIdpScimTokenRevokeEventDetail>('idp-scim-token-revoke', {
282
+ detail: { tokenId: tokenArg.id },
283
+ bubbles: true,
284
+ composed: true,
285
+ }),
286
+ )}
287
+ >
288
+ Revoke
289
+ </button>
290
+ `
291
+ : html``}
292
+ </div>
293
+ `,
294
+ )
295
+ : html`<div class="empty">No SCIM tokens yet. Create one to connect your IdP's provisioning.</div>`}
296
+ <div class="create-row">
297
+ <idp-input
298
+ label="New token label"
299
+ placeholder="e.g. Okta production"
300
+ .value=${this.newLabel}
301
+ @input=${(eventArg: Event) => {
302
+ const target = eventArg.composedPath()[0] as HTMLInputElement;
303
+ this.newLabel = target?.value ?? '';
304
+ }}
305
+ ></idp-input>
306
+ <idp-button variant="accent" size="sm" @click=${() => this.submitCreate()}>Create token</idp-button>
307
+ </div>
308
+ </div>
309
+ `;
310
+ }
311
+ }