@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.
- package/changelog.md +10 -0
- package/dist_bundle/bundle.js +1175 -643
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/idp-admin-shell.d.ts +13 -1
- package/dist_ts_web/elements/idp-admin-shell.js +55 -2
- package/dist_ts_web/elements/idp-admin-sidebar.js +2 -1
- package/dist_ts_web/elements/idp-scim-token-panel.d.ts +43 -0
- package/dist_ts_web/elements/idp-scim-token-panel.js +361 -0
- package/dist_ts_web/elements/idp-sso-connection-form.d.ts +81 -0
- package/dist_ts_web/elements/idp-sso-connection-form.js +637 -0
- package/dist_ts_web/elements/index.d.ts +2 -0
- package/dist_ts_web/elements/index.js +3 -1
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/idp-admin-shell.ts +37 -0
- package/ts_web/elements/idp-admin-sidebar.ts +1 -0
- package/ts_web/elements/idp-scim-token-panel.ts +311 -0
- package/ts_web/elements/idp-sso-connection-form.ts +574 -0
- package/ts_web/elements/index.ts +2 -0
|
@@ -0,0 +1,574 @@
|
|
|
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 IIdpSsoConnectionItem {
|
|
9
|
+
id: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
status: 'active' | 'disabled';
|
|
12
|
+
emailDomains: string[];
|
|
13
|
+
entityId: string;
|
|
14
|
+
ssoUrl: string;
|
|
15
|
+
certificateCount: number;
|
|
16
|
+
provisioningMode: 'jit' | 'scim-only' | 'jit-and-scim';
|
|
17
|
+
defaultOrgRoles: string[];
|
|
18
|
+
groupMappings: { idpGroup: string; orgRoles: string[] }[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IIdpSsoSpDetails {
|
|
22
|
+
spEntityId: string;
|
|
23
|
+
acsUrl: string;
|
|
24
|
+
metadataUrl: string;
|
|
25
|
+
scimBaseUrl: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IIdpSsoCreateEventDetail {
|
|
29
|
+
displayName: string;
|
|
30
|
+
emailDomains: string[];
|
|
31
|
+
metadataXml?: string;
|
|
32
|
+
idp?: { entityId: string; ssoUrl: string; signingCertificates: string[] };
|
|
33
|
+
provisioningMode: 'jit' | 'scim-only' | 'jit-and-scim';
|
|
34
|
+
defaultOrgRoles: string[];
|
|
35
|
+
groupMappings: { idpGroup: string; orgRoles: string[] }[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IIdpSsoStatusEventDetail {
|
|
39
|
+
connectionId: string;
|
|
40
|
+
status: 'active' | 'disabled';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IIdpSsoDeleteEventDetail {
|
|
44
|
+
connectionId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare global {
|
|
48
|
+
interface HTMLElementTagNameMap {
|
|
49
|
+
'idp-sso-connection-form': IdpSsoConnectionForm;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@customElement('idp-sso-connection-form')
|
|
54
|
+
export class IdpSsoConnectionForm extends DeesElement {
|
|
55
|
+
public static demo = () => html`
|
|
56
|
+
<div style="max-width: 720px; display: flex; flex-direction: column; gap: 16px;">
|
|
57
|
+
<idp-sso-connection-form
|
|
58
|
+
.spDetails=${{
|
|
59
|
+
spEntityId: 'https://idp.global/saml/sp/metadata/conn-1',
|
|
60
|
+
acsUrl: 'https://idp.global/saml/acs/conn-1',
|
|
61
|
+
metadataUrl: 'https://idp.global/saml/sp/metadata/conn-1',
|
|
62
|
+
scimBaseUrl: 'https://idp.global/scim/v2',
|
|
63
|
+
} as IIdpSsoSpDetails}
|
|
64
|
+
.connections=${[
|
|
65
|
+
{
|
|
66
|
+
id: 'conn-1',
|
|
67
|
+
displayName: 'Okta (acme.com)',
|
|
68
|
+
status: 'active',
|
|
69
|
+
emailDomains: ['acme.com', 'acme.io'],
|
|
70
|
+
entityId: 'http://www.okta.com/exk9examples',
|
|
71
|
+
ssoUrl: 'https://acme.okta.com/app/xyz/sso/saml',
|
|
72
|
+
certificateCount: 1,
|
|
73
|
+
provisioningMode: 'jit-and-scim',
|
|
74
|
+
defaultOrgRoles: ['member'],
|
|
75
|
+
groupMappings: [{ idpGroup: 'Engineering', orgRoles: ['developer'] }],
|
|
76
|
+
},
|
|
77
|
+
] as IIdpSsoConnectionItem[]}
|
|
78
|
+
></idp-sso-connection-form>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
public static demoGroups = ['idp.global v5 admin console'];
|
|
82
|
+
|
|
83
|
+
@property({ type: Array })
|
|
84
|
+
public accessor connections: IIdpSsoConnectionItem[] = [];
|
|
85
|
+
|
|
86
|
+
@property({ type: Object })
|
|
87
|
+
public accessor spDetails: IIdpSsoSpDetails | null = null;
|
|
88
|
+
|
|
89
|
+
@state()
|
|
90
|
+
private accessor formOpen = false;
|
|
91
|
+
|
|
92
|
+
@state()
|
|
93
|
+
private accessor manualIdp = false;
|
|
94
|
+
|
|
95
|
+
@state()
|
|
96
|
+
private accessor formDisplayName = '';
|
|
97
|
+
|
|
98
|
+
@state()
|
|
99
|
+
private accessor formEmailDomains = '';
|
|
100
|
+
|
|
101
|
+
@state()
|
|
102
|
+
private accessor formMetadataXml = '';
|
|
103
|
+
|
|
104
|
+
@state()
|
|
105
|
+
private accessor formEntityId = '';
|
|
106
|
+
|
|
107
|
+
@state()
|
|
108
|
+
private accessor formSsoUrl = '';
|
|
109
|
+
|
|
110
|
+
@state()
|
|
111
|
+
private accessor formCertificate = '';
|
|
112
|
+
|
|
113
|
+
@state()
|
|
114
|
+
private accessor formProvisioningMode: 'jit' | 'scim-only' | 'jit-and-scim' = 'jit';
|
|
115
|
+
|
|
116
|
+
@state()
|
|
117
|
+
private accessor formDefaultRoles = 'member';
|
|
118
|
+
|
|
119
|
+
@state()
|
|
120
|
+
private accessor formGroupMappings = '';
|
|
121
|
+
|
|
122
|
+
@state()
|
|
123
|
+
private accessor formError = '';
|
|
124
|
+
|
|
125
|
+
public static styles = [
|
|
126
|
+
...idpElementStyles,
|
|
127
|
+
css`
|
|
128
|
+
:host {
|
|
129
|
+
display: block;
|
|
130
|
+
font-family: var(--idp-font);
|
|
131
|
+
color: var(--idp-fg);
|
|
132
|
+
}
|
|
133
|
+
.section {
|
|
134
|
+
background: var(--idp-card);
|
|
135
|
+
border: 1px solid var(--idp-border);
|
|
136
|
+
border-radius: var(--idp-radius);
|
|
137
|
+
padding: 20px;
|
|
138
|
+
}
|
|
139
|
+
.headline {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: flex-start;
|
|
142
|
+
justify-content: space-between;
|
|
143
|
+
gap: 16px;
|
|
144
|
+
}
|
|
145
|
+
.title {
|
|
146
|
+
font-size: 14px;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
}
|
|
149
|
+
.muted {
|
|
150
|
+
font-size: 12.5px;
|
|
151
|
+
color: var(--idp-muted-fg);
|
|
152
|
+
line-height: 1.5;
|
|
153
|
+
margin-top: 3px;
|
|
154
|
+
}
|
|
155
|
+
.sp-grid {
|
|
156
|
+
display: grid;
|
|
157
|
+
grid-template-columns: minmax(90px, auto) 1fr;
|
|
158
|
+
gap: 6px 14px;
|
|
159
|
+
margin-top: 14px;
|
|
160
|
+
padding: 12px;
|
|
161
|
+
background: var(--idp-muted);
|
|
162
|
+
border: 1px solid var(--idp-border);
|
|
163
|
+
border-radius: 10px;
|
|
164
|
+
font-size: 12px;
|
|
165
|
+
}
|
|
166
|
+
.sp-key {
|
|
167
|
+
color: var(--idp-muted-fg);
|
|
168
|
+
white-space: nowrap;
|
|
169
|
+
}
|
|
170
|
+
.sp-value {
|
|
171
|
+
font-family: var(--idp-mono, ui-monospace, monospace);
|
|
172
|
+
overflow-wrap: anywhere;
|
|
173
|
+
}
|
|
174
|
+
.row {
|
|
175
|
+
padding: 14px 0;
|
|
176
|
+
border-bottom: 1px solid var(--idp-border);
|
|
177
|
+
}
|
|
178
|
+
.row:last-of-type {
|
|
179
|
+
border-bottom: none;
|
|
180
|
+
}
|
|
181
|
+
.row-head {
|
|
182
|
+
display: flex;
|
|
183
|
+
align-items: center;
|
|
184
|
+
gap: 10px;
|
|
185
|
+
}
|
|
186
|
+
.row-name {
|
|
187
|
+
font-size: 13px;
|
|
188
|
+
font-weight: 500;
|
|
189
|
+
flex: 1;
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: 8px;
|
|
193
|
+
min-width: 0;
|
|
194
|
+
}
|
|
195
|
+
.row-meta {
|
|
196
|
+
font-size: 11px;
|
|
197
|
+
color: var(--idp-muted-fg);
|
|
198
|
+
margin-top: 4px;
|
|
199
|
+
overflow-wrap: anywhere;
|
|
200
|
+
}
|
|
201
|
+
.chip-row {
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-wrap: wrap;
|
|
204
|
+
gap: 6px;
|
|
205
|
+
margin-top: 8px;
|
|
206
|
+
}
|
|
207
|
+
.row-actions {
|
|
208
|
+
display: flex;
|
|
209
|
+
gap: 6px;
|
|
210
|
+
flex: 0 0 auto;
|
|
211
|
+
}
|
|
212
|
+
.table-action {
|
|
213
|
+
background: none;
|
|
214
|
+
border: 1px solid var(--idp-border);
|
|
215
|
+
border-radius: 7px;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
font-family: inherit;
|
|
218
|
+
font-size: 11px;
|
|
219
|
+
font-weight: 500;
|
|
220
|
+
padding: 4px 10px;
|
|
221
|
+
color: var(--idp-fg);
|
|
222
|
+
}
|
|
223
|
+
.table-action:hover {
|
|
224
|
+
background: var(--idp-muted);
|
|
225
|
+
}
|
|
226
|
+
.table-action.destructive {
|
|
227
|
+
color: var(--idp-destructive);
|
|
228
|
+
border-color: transparent;
|
|
229
|
+
}
|
|
230
|
+
.table-action.destructive:hover {
|
|
231
|
+
background: var(--idp-error-bg);
|
|
232
|
+
}
|
|
233
|
+
.empty {
|
|
234
|
+
padding: 18px 0;
|
|
235
|
+
font-size: 12.5px;
|
|
236
|
+
color: var(--idp-muted-fg);
|
|
237
|
+
}
|
|
238
|
+
.form {
|
|
239
|
+
margin-top: 16px;
|
|
240
|
+
padding-top: 16px;
|
|
241
|
+
border-top: 1px solid var(--idp-border);
|
|
242
|
+
display: flex;
|
|
243
|
+
flex-direction: column;
|
|
244
|
+
gap: 12px;
|
|
245
|
+
}
|
|
246
|
+
.form-grid {
|
|
247
|
+
display: grid;
|
|
248
|
+
grid-template-columns: 1fr 1fr;
|
|
249
|
+
gap: 12px;
|
|
250
|
+
}
|
|
251
|
+
@media (max-width: 720px) {
|
|
252
|
+
.form-grid {
|
|
253
|
+
grid-template-columns: 1fr;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
label.field {
|
|
257
|
+
display: flex;
|
|
258
|
+
flex-direction: column;
|
|
259
|
+
gap: 5px;
|
|
260
|
+
font-size: 12px;
|
|
261
|
+
font-weight: 500;
|
|
262
|
+
}
|
|
263
|
+
textarea,
|
|
264
|
+
select {
|
|
265
|
+
font-family: inherit;
|
|
266
|
+
font-size: 12.5px;
|
|
267
|
+
color: var(--idp-fg);
|
|
268
|
+
background: var(--idp-card);
|
|
269
|
+
border: 1px solid var(--idp-border);
|
|
270
|
+
border-radius: 8px;
|
|
271
|
+
padding: 8px 10px;
|
|
272
|
+
}
|
|
273
|
+
textarea {
|
|
274
|
+
font-family: var(--idp-mono, ui-monospace, monospace);
|
|
275
|
+
min-height: 96px;
|
|
276
|
+
resize: vertical;
|
|
277
|
+
}
|
|
278
|
+
textarea:focus-visible,
|
|
279
|
+
select:focus-visible {
|
|
280
|
+
outline: 2px solid color-mix(in srgb, var(--idp-accent), transparent 68%);
|
|
281
|
+
outline-offset: 1px;
|
|
282
|
+
}
|
|
283
|
+
.hint {
|
|
284
|
+
font-size: 11px;
|
|
285
|
+
font-weight: 400;
|
|
286
|
+
color: var(--idp-muted-fg);
|
|
287
|
+
}
|
|
288
|
+
.toggle-link {
|
|
289
|
+
background: none;
|
|
290
|
+
border: none;
|
|
291
|
+
padding: 0;
|
|
292
|
+
cursor: pointer;
|
|
293
|
+
font-family: inherit;
|
|
294
|
+
font-size: 11.5px;
|
|
295
|
+
color: var(--idp-accent);
|
|
296
|
+
text-align: left;
|
|
297
|
+
}
|
|
298
|
+
.error {
|
|
299
|
+
font-size: 12px;
|
|
300
|
+
color: var(--idp-destructive);
|
|
301
|
+
}
|
|
302
|
+
.form-actions {
|
|
303
|
+
display: flex;
|
|
304
|
+
gap: 8px;
|
|
305
|
+
justify-content: flex-end;
|
|
306
|
+
}
|
|
307
|
+
`,
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
private parseGroupMappings(rawArg: string): { idpGroup: string; orgRoles: string[] }[] {
|
|
311
|
+
return rawArg
|
|
312
|
+
.split('\n')
|
|
313
|
+
.map((lineArg) => lineArg.trim())
|
|
314
|
+
.filter(Boolean)
|
|
315
|
+
.map((lineArg) => {
|
|
316
|
+
const [groupPart, rolesPart] = lineArg.split('=');
|
|
317
|
+
return {
|
|
318
|
+
idpGroup: (groupPart || '').trim(),
|
|
319
|
+
orgRoles: (rolesPart || '')
|
|
320
|
+
.split(',')
|
|
321
|
+
.map((roleArg) => roleArg.trim())
|
|
322
|
+
.filter(Boolean),
|
|
323
|
+
};
|
|
324
|
+
})
|
|
325
|
+
.filter((mappingArg) => mappingArg.idpGroup && mappingArg.orgRoles.length > 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private parseList(rawArg: string): string[] {
|
|
329
|
+
return rawArg
|
|
330
|
+
.split(',')
|
|
331
|
+
.map((entryArg) => entryArg.trim())
|
|
332
|
+
.filter(Boolean);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private submitCreate() {
|
|
336
|
+
this.formError = '';
|
|
337
|
+
const displayName = this.formDisplayName.trim();
|
|
338
|
+
const emailDomains = this.parseList(this.formEmailDomains).map((domainArg) => domainArg.toLowerCase());
|
|
339
|
+
if (!displayName || emailDomains.length === 0) {
|
|
340
|
+
this.formError = 'Display name and at least one email domain are required.';
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const detail: IIdpSsoCreateEventDetail = {
|
|
344
|
+
displayName,
|
|
345
|
+
emailDomains,
|
|
346
|
+
provisioningMode: this.formProvisioningMode,
|
|
347
|
+
defaultOrgRoles: this.parseList(this.formDefaultRoles),
|
|
348
|
+
groupMappings: this.parseGroupMappings(this.formGroupMappings),
|
|
349
|
+
};
|
|
350
|
+
if (this.manualIdp) {
|
|
351
|
+
const entityId = this.formEntityId.trim();
|
|
352
|
+
const ssoUrl = this.formSsoUrl.trim();
|
|
353
|
+
const certificate = this.formCertificate.trim();
|
|
354
|
+
if (!entityId || !ssoUrl || !certificate) {
|
|
355
|
+
this.formError = 'Entity ID, SSO URL and a signing certificate are required for manual setup.';
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
detail.idp = { entityId, ssoUrl, signingCertificates: [certificate] };
|
|
359
|
+
} else {
|
|
360
|
+
const metadataXml = this.formMetadataXml.trim();
|
|
361
|
+
if (!metadataXml) {
|
|
362
|
+
this.formError = 'Paste the IdP metadata XML or switch to manual setup.';
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
detail.metadataXml = metadataXml;
|
|
366
|
+
}
|
|
367
|
+
this.dispatchEvent(
|
|
368
|
+
new CustomEvent<IIdpSsoCreateEventDetail>('idp-sso-create', {
|
|
369
|
+
detail,
|
|
370
|
+
bubbles: true,
|
|
371
|
+
composed: true,
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
this.resetForm();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private resetForm() {
|
|
378
|
+
this.formOpen = false;
|
|
379
|
+
this.manualIdp = false;
|
|
380
|
+
this.formDisplayName = '';
|
|
381
|
+
this.formEmailDomains = '';
|
|
382
|
+
this.formMetadataXml = '';
|
|
383
|
+
this.formEntityId = '';
|
|
384
|
+
this.formSsoUrl = '';
|
|
385
|
+
this.formCertificate = '';
|
|
386
|
+
this.formProvisioningMode = 'jit';
|
|
387
|
+
this.formDefaultRoles = 'member';
|
|
388
|
+
this.formGroupMappings = '';
|
|
389
|
+
this.formError = '';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private readInput(eventArg: Event): string {
|
|
393
|
+
const target = eventArg.composedPath()[0] as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
394
|
+
return target?.value ?? '';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private renderConnectionRow(connectionArg: IIdpSsoConnectionItem): TemplateResult {
|
|
398
|
+
return html`
|
|
399
|
+
<div class="row">
|
|
400
|
+
<div class="row-head">
|
|
401
|
+
<div class="row-name">
|
|
402
|
+
${connectionArg.displayName}
|
|
403
|
+
<idp-badge variant=${connectionArg.status === 'active' ? 'ok' : 'outline'}>${connectionArg.status}</idp-badge>
|
|
404
|
+
<idp-badge variant="outline">${connectionArg.provisioningMode}</idp-badge>
|
|
405
|
+
</div>
|
|
406
|
+
<div class="row-actions">
|
|
407
|
+
<button
|
|
408
|
+
class="table-action"
|
|
409
|
+
@click=${() =>
|
|
410
|
+
this.dispatchEvent(
|
|
411
|
+
new CustomEvent<IIdpSsoStatusEventDetail>('idp-sso-status', {
|
|
412
|
+
detail: {
|
|
413
|
+
connectionId: connectionArg.id,
|
|
414
|
+
status: connectionArg.status === 'active' ? 'disabled' : 'active',
|
|
415
|
+
},
|
|
416
|
+
bubbles: true,
|
|
417
|
+
composed: true,
|
|
418
|
+
}),
|
|
419
|
+
)}
|
|
420
|
+
>
|
|
421
|
+
${connectionArg.status === 'active' ? 'Disable' : 'Enable'}
|
|
422
|
+
</button>
|
|
423
|
+
<button
|
|
424
|
+
class="table-action destructive"
|
|
425
|
+
@click=${() =>
|
|
426
|
+
this.dispatchEvent(
|
|
427
|
+
new CustomEvent<IIdpSsoDeleteEventDetail>('idp-sso-delete', {
|
|
428
|
+
detail: { connectionId: connectionArg.id },
|
|
429
|
+
bubbles: true,
|
|
430
|
+
composed: true,
|
|
431
|
+
}),
|
|
432
|
+
)}
|
|
433
|
+
>
|
|
434
|
+
Delete
|
|
435
|
+
</button>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="row-meta">${connectionArg.entityId} · ${connectionArg.ssoUrl} · ${connectionArg.certificateCount} cert${connectionArg.certificateCount === 1 ? '' : 's'}</div>
|
|
439
|
+
<div class="chip-row">
|
|
440
|
+
${connectionArg.emailDomains.map((domainArg) => html`<idp-badge variant="accent">@${domainArg}</idp-badge>`)}
|
|
441
|
+
${connectionArg.groupMappings.map(
|
|
442
|
+
(mappingArg) => html`<idp-badge variant="outline">${mappingArg.idpGroup} → ${mappingArg.orgRoles.join(', ')}</idp-badge>`,
|
|
443
|
+
)}
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private renderForm(): TemplateResult {
|
|
450
|
+
return html`
|
|
451
|
+
<div class="form">
|
|
452
|
+
<div class="form-grid">
|
|
453
|
+
<idp-input
|
|
454
|
+
label="Display name"
|
|
455
|
+
placeholder="e.g. Okta (acme.com)"
|
|
456
|
+
.value=${this.formDisplayName}
|
|
457
|
+
@input=${(eventArg: Event) => (this.formDisplayName = this.readInput(eventArg))}
|
|
458
|
+
></idp-input>
|
|
459
|
+
<idp-input
|
|
460
|
+
label="Email domains (comma-separated)"
|
|
461
|
+
placeholder="acme.com, acme.io"
|
|
462
|
+
.value=${this.formEmailDomains}
|
|
463
|
+
@input=${(eventArg: Event) => (this.formEmailDomains = this.readInput(eventArg))}
|
|
464
|
+
></idp-input>
|
|
465
|
+
</div>
|
|
466
|
+
${this.manualIdp
|
|
467
|
+
? html`
|
|
468
|
+
<div class="form-grid">
|
|
469
|
+
<idp-input
|
|
470
|
+
label="IdP entity ID"
|
|
471
|
+
placeholder="http://www.okta.com/exk..."
|
|
472
|
+
.value=${this.formEntityId}
|
|
473
|
+
@input=${(eventArg: Event) => (this.formEntityId = this.readInput(eventArg))}
|
|
474
|
+
></idp-input>
|
|
475
|
+
<idp-input
|
|
476
|
+
label="IdP SSO URL"
|
|
477
|
+
placeholder="https://acme.okta.com/app/.../sso/saml"
|
|
478
|
+
.value=${this.formSsoUrl}
|
|
479
|
+
@input=${(eventArg: Event) => (this.formSsoUrl = this.readInput(eventArg))}
|
|
480
|
+
></idp-input>
|
|
481
|
+
</div>
|
|
482
|
+
<label class="field">
|
|
483
|
+
IdP signing certificate (PEM)
|
|
484
|
+
<textarea
|
|
485
|
+
placeholder="-----BEGIN CERTIFICATE-----"
|
|
486
|
+
.value=${this.formCertificate}
|
|
487
|
+
@input=${(eventArg: Event) => (this.formCertificate = this.readInput(eventArg))}
|
|
488
|
+
></textarea>
|
|
489
|
+
</label>
|
|
490
|
+
`
|
|
491
|
+
: html`
|
|
492
|
+
<label class="field">
|
|
493
|
+
IdP metadata XML
|
|
494
|
+
<span class="hint">Download it from your IdP (Okta: Sign On → SAML metadata; Entra: Federation metadata XML)</span>
|
|
495
|
+
<textarea
|
|
496
|
+
placeholder="<EntityDescriptor ...>"
|
|
497
|
+
.value=${this.formMetadataXml}
|
|
498
|
+
@input=${(eventArg: Event) => (this.formMetadataXml = this.readInput(eventArg))}
|
|
499
|
+
></textarea>
|
|
500
|
+
</label>
|
|
501
|
+
`}
|
|
502
|
+
<button class="toggle-link" @click=${() => (this.manualIdp = !this.manualIdp)}>
|
|
503
|
+
${this.manualIdp ? 'Use metadata XML instead' : 'Enter entity ID, SSO URL and certificate manually'}
|
|
504
|
+
</button>
|
|
505
|
+
<div class="form-grid">
|
|
506
|
+
<label class="field">
|
|
507
|
+
Provisioning
|
|
508
|
+
<select
|
|
509
|
+
.value=${this.formProvisioningMode}
|
|
510
|
+
@change=${(eventArg: Event) => (this.formProvisioningMode = this.readInput(eventArg) as typeof this.formProvisioningMode)}
|
|
511
|
+
>
|
|
512
|
+
<option value="jit">Just-in-time (create accounts on first login)</option>
|
|
513
|
+
<option value="scim-only">SCIM only (require provisioning before login)</option>
|
|
514
|
+
<option value="jit-and-scim">JIT + SCIM</option>
|
|
515
|
+
</select>
|
|
516
|
+
</label>
|
|
517
|
+
<idp-input
|
|
518
|
+
label="Default org roles (comma-separated)"
|
|
519
|
+
placeholder="member"
|
|
520
|
+
.value=${this.formDefaultRoles}
|
|
521
|
+
@input=${(eventArg: Event) => (this.formDefaultRoles = this.readInput(eventArg))}
|
|
522
|
+
></idp-input>
|
|
523
|
+
</div>
|
|
524
|
+
<label class="field">
|
|
525
|
+
Group → role mappings
|
|
526
|
+
<span class="hint">One per line: IdP group = role1, role2</span>
|
|
527
|
+
<textarea
|
|
528
|
+
style="min-height: 64px"
|
|
529
|
+
placeholder="Engineering = developer"
|
|
530
|
+
.value=${this.formGroupMappings}
|
|
531
|
+
@input=${(eventArg: Event) => (this.formGroupMappings = this.readInput(eventArg))}
|
|
532
|
+
></textarea>
|
|
533
|
+
</label>
|
|
534
|
+
${this.formError ? html`<div class="error">${this.formError}</div>` : html``}
|
|
535
|
+
<div class="form-actions">
|
|
536
|
+
<idp-button variant="ghost" size="sm" @click=${() => this.resetForm()}>Cancel</idp-button>
|
|
537
|
+
<idp-button variant="accent" size="sm" @click=${() => this.submitCreate()}>Create connection</idp-button>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
public render(): TemplateResult {
|
|
544
|
+
return html`
|
|
545
|
+
<div class="section">
|
|
546
|
+
<div class="headline">
|
|
547
|
+
<div>
|
|
548
|
+
<div class="title">SAML single sign-on</div>
|
|
549
|
+
<div class="muted">
|
|
550
|
+
Let members sign in through your organisation's identity provider. Users entering a
|
|
551
|
+
matching email domain are routed to your IdP automatically.
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
${!this.formOpen
|
|
555
|
+
? html`<idp-button variant="accent" size="sm" @click=${() => (this.formOpen = true)}>Add connection</idp-button>`
|
|
556
|
+
: html``}
|
|
557
|
+
</div>
|
|
558
|
+
${this.spDetails
|
|
559
|
+
? html`
|
|
560
|
+
<div class="sp-grid">
|
|
561
|
+
<span class="sp-key">SP entity ID</span><span class="sp-value">${this.spDetails.spEntityId}</span>
|
|
562
|
+
<span class="sp-key">ACS URL</span><span class="sp-value">${this.spDetails.acsUrl}</span>
|
|
563
|
+
<span class="sp-key">SP metadata</span><span class="sp-value">${this.spDetails.metadataUrl}</span>
|
|
564
|
+
</div>
|
|
565
|
+
`
|
|
566
|
+
: html``}
|
|
567
|
+
${this.connections.length
|
|
568
|
+
? this.connections.map((connectionArg) => this.renderConnectionRow(connectionArg))
|
|
569
|
+
: html`<div class="empty">No SSO connections yet.</div>`}
|
|
570
|
+
${this.formOpen ? this.renderForm() : html``}
|
|
571
|
+
</div>
|
|
572
|
+
`;
|
|
573
|
+
}
|
|
574
|
+
}
|
package/ts_web/elements/index.ts
CHANGED