@stigmer/react 0.0.89 → 0.0.90
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/identity-provider/CreateIdentityProviderForm.d.ts.map +1 -1
- package/identity-provider/CreateIdentityProviderForm.js +60 -2
- package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +87 -4
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/identity-provider/IdentityProviderListPanel.js +5 -3
- package/identity-provider/IdentityProviderListPanel.js.map +1 -1
- package/identity-provider/IdentityProviderWizard.d.ts.map +1 -1
- package/identity-provider/IdentityProviderWizard.js +59 -4
- package/identity-provider/IdentityProviderWizard.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/package.json +7 -7
- package/platform-client/CreatePlatformClientForm.d.ts +42 -0
- package/platform-client/CreatePlatformClientForm.d.ts.map +1 -0
- package/platform-client/CreatePlatformClientForm.js +148 -0
- package/platform-client/CreatePlatformClientForm.js.map +1 -0
- package/platform-client/PlatformClientDetailPanel.d.ts +51 -0
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -0
- package/platform-client/PlatformClientDetailPanel.js +247 -0
- package/platform-client/PlatformClientDetailPanel.js.map +1 -0
- package/platform-client/PlatformClientListPanel.d.ts +41 -0
- package/platform-client/PlatformClientListPanel.d.ts.map +1 -0
- package/platform-client/PlatformClientListPanel.js +123 -0
- package/platform-client/PlatformClientListPanel.js.map +1 -0
- package/platform-client/PlatformClientSecretAlert.d.ts +39 -0
- package/platform-client/PlatformClientSecretAlert.d.ts.map +1 -0
- package/platform-client/PlatformClientSecretAlert.js +74 -0
- package/platform-client/PlatformClientSecretAlert.js.map +1 -0
- package/platform-client/index.d.ts +11 -0
- package/platform-client/index.d.ts.map +1 -0
- package/platform-client/index.js +11 -0
- package/platform-client/index.js.map +1 -0
- package/platform-client/useCreatePlatformClient.d.ts +42 -0
- package/platform-client/useCreatePlatformClient.d.ts.map +1 -0
- package/platform-client/useCreatePlatformClient.js +49 -0
- package/platform-client/useCreatePlatformClient.js.map +1 -0
- package/platform-client/useDeletePlatformClient.d.ts +31 -0
- package/platform-client/useDeletePlatformClient.d.ts.map +1 -0
- package/platform-client/useDeletePlatformClient.js +42 -0
- package/platform-client/useDeletePlatformClient.js.map +1 -0
- package/platform-client/usePlatformClient.d.ts +37 -0
- package/platform-client/usePlatformClient.d.ts.map +1 -0
- package/platform-client/usePlatformClient.js +62 -0
- package/platform-client/usePlatformClient.js.map +1 -0
- package/platform-client/usePlatformClientList.d.ts +42 -0
- package/platform-client/usePlatformClientList.d.ts.map +1 -0
- package/platform-client/usePlatformClientList.js +71 -0
- package/platform-client/usePlatformClientList.js.map +1 -0
- package/platform-client/useRotatePlatformClientSecret.d.ts +35 -0
- package/platform-client/useRotatePlatformClientSecret.d.ts.map +1 -0
- package/platform-client/useRotatePlatformClientSecret.js +43 -0
- package/platform-client/useRotatePlatformClientSecret.js.map +1 -0
- package/platform-client/useUpdatePlatformClient.d.ts +39 -0
- package/platform-client/useUpdatePlatformClient.d.ts.map +1 -0
- package/platform-client/useUpdatePlatformClient.js +50 -0
- package/platform-client/useUpdatePlatformClient.js.map +1 -0
- package/src/identity-provider/CreateIdentityProviderForm.tsx +220 -0
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +288 -6
- package/src/identity-provider/IdentityProviderListPanel.tsx +9 -2
- package/src/identity-provider/IdentityProviderWizard.tsx +231 -25
- package/src/index.ts +26 -0
- package/src/platform-client/CreatePlatformClientForm.tsx +519 -0
- package/src/platform-client/PlatformClientDetailPanel.tsx +898 -0
- package/src/platform-client/PlatformClientListPanel.tsx +413 -0
- package/src/platform-client/PlatformClientSecretAlert.tsx +252 -0
- package/src/platform-client/index.ts +49 -0
- package/src/platform-client/useCreatePlatformClient.ts +77 -0
- package/src/platform-client/useDeletePlatformClient.ts +64 -0
- package/src/platform-client/usePlatformClient.ts +86 -0
- package/src/platform-client/usePlatformClientList.ts +96 -0
- package/src/platform-client/useRotatePlatformClientSecret.ts +68 -0
- package/src/platform-client/useUpdatePlatformClient.ts +70 -0
- package/src/test/index.ts +6 -0
- package/src/{demo → test}/samples.ts +1 -1
- package/styles.css +1 -1
- package/test/__tests__/samples.test.d.ts.map +1 -0
- package/{demo → test}/__tests__/samples.test.js.map +1 -1
- package/test/index.d.ts +2 -0
- package/test/index.d.ts.map +1 -0
- package/test/index.js +6 -0
- package/test/index.js.map +1 -0
- package/{demo → test}/samples.d.ts +1 -1
- package/{demo → test}/samples.d.ts.map +1 -1
- package/{demo → test}/samples.js +1 -1
- package/{demo → test}/samples.js.map +1 -1
- package/demo/__tests__/demo-client.test.d.ts +0 -2
- package/demo/__tests__/demo-client.test.d.ts.map +0 -1
- package/demo/__tests__/demo-client.test.js +0 -133
- package/demo/__tests__/demo-client.test.js.map +0 -1
- package/demo/__tests__/fixtures.test.d.ts +0 -2
- package/demo/__tests__/fixtures.test.d.ts.map +0 -1
- package/demo/__tests__/fixtures.test.js +0 -135
- package/demo/__tests__/fixtures.test.js.map +0 -1
- package/demo/__tests__/samples.test.d.ts.map +0 -1
- package/demo/client.d.ts +0 -29
- package/demo/client.d.ts.map +0 -1
- package/demo/client.js +0 -52
- package/demo/client.js.map +0 -1
- package/demo/fixtures.d.ts +0 -194
- package/demo/fixtures.d.ts.map +0 -1
- package/demo/fixtures.js +0 -267
- package/demo/fixtures.js.map +0 -1
- package/demo/index.d.ts +0 -6
- package/demo/index.d.ts.map +0 -1
- package/demo/index.js +0 -6
- package/demo/index.js.map +0 -1
- package/demo/transport.d.ts +0 -59
- package/demo/transport.d.ts.map +0 -1
- package/demo/transport.js +0 -75
- package/demo/transport.js.map +0 -1
- package/demo/types.d.ts +0 -62
- package/demo/types.d.ts.map +0 -1
- package/demo/types.js +0 -16
- package/demo/types.js.map +0 -1
- package/src/demo/__tests__/demo-client.test.tsx +0 -213
- package/src/demo/__tests__/fixtures.test.ts +0 -214
- package/src/demo/client.ts +0 -78
- package/src/demo/fixtures.ts +0 -409
- package/src/demo/index.ts +0 -12
- package/src/demo/transport.ts +0 -116
- package/src/demo/types.ts +0 -69
- /package/src/{demo → test}/__tests__/samples.test.ts +0 -0
- /package/{demo → test}/__tests__/samples.test.d.ts +0 -0
- /package/{demo → test}/__tests__/samples.test.js +0 -0
|
@@ -4,6 +4,7 @@ import { useCallback, useState, type FormEvent } from "react";
|
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { getUserMessage } from "@stigmer/sdk";
|
|
6
6
|
import type { IdentityProvider } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
|
|
7
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
7
8
|
import { useCreateIdentityProvider } from "./useCreateIdentityProvider";
|
|
8
9
|
|
|
9
10
|
/** Props for {@link CreateIdentityProviderForm}. */
|
|
@@ -59,6 +60,30 @@ export function CreateIdentityProviderForm({
|
|
|
59
60
|
const [isSso, setIsSso] = useState(false);
|
|
60
61
|
const [oidcClientId, setOidcClientId] = useState("");
|
|
61
62
|
|
|
63
|
+
// JIT provisioning
|
|
64
|
+
const [autoProvision, setAutoProvision] = useState(false);
|
|
65
|
+
const [autoGrant, setAutoGrant] = useState(false);
|
|
66
|
+
const [autoGrantRole, setAutoGrantRole] = useState<IamRole>(IamRole.iam_role_unspecified);
|
|
67
|
+
const [tenantOrgClaim, setTenantOrgClaim] = useState("");
|
|
68
|
+
|
|
69
|
+
const handleAutoProvisionChange = useCallback((v: boolean) => {
|
|
70
|
+
setAutoProvision(v);
|
|
71
|
+
if (!v) {
|
|
72
|
+
setAutoGrant(false);
|
|
73
|
+
setAutoGrantRole(IamRole.iam_role_unspecified);
|
|
74
|
+
setTenantOrgClaim("");
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
const handleAutoGrantChange = useCallback((v: boolean) => {
|
|
79
|
+
setAutoGrant(v);
|
|
80
|
+
if (v) setAutoProvision(true);
|
|
81
|
+
if (!v) {
|
|
82
|
+
setAutoGrantRole(IamRole.iam_role_unspecified);
|
|
83
|
+
setTenantOrgClaim("");
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
62
87
|
const trimmedName = name.trim();
|
|
63
88
|
const trimmedJwksUri = jwksUri.trim();
|
|
64
89
|
const trimmedIssuers = issuers.trim();
|
|
@@ -91,6 +116,16 @@ export function CreateIdentityProviderForm({
|
|
|
91
116
|
isSsoProvider: true,
|
|
92
117
|
oidcClientId: oidcClientId.trim(),
|
|
93
118
|
}),
|
|
119
|
+
...(!isSso && {
|
|
120
|
+
autoProvisionAccounts: autoProvision,
|
|
121
|
+
autoGrantOnOrg: autoGrant,
|
|
122
|
+
...(autoGrant && autoGrantRole !== IamRole.iam_role_unspecified && {
|
|
123
|
+
autoGrantRole,
|
|
124
|
+
}),
|
|
125
|
+
...(autoGrant && tenantOrgClaim.trim() && {
|
|
126
|
+
tenantOrgClaim: tenantOrgClaim.trim(),
|
|
127
|
+
}),
|
|
128
|
+
}),
|
|
94
129
|
});
|
|
95
130
|
onCreated?.(idp);
|
|
96
131
|
} catch {
|
|
@@ -106,6 +141,10 @@ export function CreateIdentityProviderForm({
|
|
|
106
141
|
trimmedAudience,
|
|
107
142
|
isSso,
|
|
108
143
|
oidcClientId,
|
|
144
|
+
autoProvision,
|
|
145
|
+
autoGrant,
|
|
146
|
+
autoGrantRole,
|
|
147
|
+
tenantOrgClaim,
|
|
109
148
|
create,
|
|
110
149
|
clearError,
|
|
111
150
|
onCreated,
|
|
@@ -194,6 +233,20 @@ export function CreateIdentityProviderForm({
|
|
|
194
233
|
required
|
|
195
234
|
/>
|
|
196
235
|
)}
|
|
236
|
+
|
|
237
|
+
{/* JIT provisioning */}
|
|
238
|
+
<JitSection
|
|
239
|
+
isSso={isSso}
|
|
240
|
+
autoProvision={autoProvision}
|
|
241
|
+
onAutoProvisionChange={handleAutoProvisionChange}
|
|
242
|
+
autoGrant={autoGrant}
|
|
243
|
+
onAutoGrantChange={handleAutoGrantChange}
|
|
244
|
+
autoGrantRole={autoGrantRole}
|
|
245
|
+
onAutoGrantRoleChange={setAutoGrantRole}
|
|
246
|
+
tenantOrgClaim={tenantOrgClaim}
|
|
247
|
+
onTenantOrgClaimChange={setTenantOrgClaim}
|
|
248
|
+
disabled={isCreating}
|
|
249
|
+
/>
|
|
197
250
|
</div>
|
|
198
251
|
|
|
199
252
|
{error && (
|
|
@@ -285,6 +338,173 @@ function FormField({
|
|
|
285
338
|
);
|
|
286
339
|
}
|
|
287
340
|
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// JIT provisioning section
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
const JIT_ROLE_OPTIONS: readonly { readonly value: string; readonly label: string }[] = [
|
|
346
|
+
{ value: String(IamRole.iam_role_unspecified), label: "Default (viewer)" },
|
|
347
|
+
{ value: String(IamRole.viewer), label: "Viewer" },
|
|
348
|
+
{ value: String(IamRole.member), label: "Member" },
|
|
349
|
+
{ value: String(IamRole.admin), label: "Admin" },
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
function JitSection({
|
|
353
|
+
isSso,
|
|
354
|
+
autoProvision,
|
|
355
|
+
onAutoProvisionChange,
|
|
356
|
+
autoGrant,
|
|
357
|
+
onAutoGrantChange,
|
|
358
|
+
autoGrantRole,
|
|
359
|
+
onAutoGrantRoleChange,
|
|
360
|
+
tenantOrgClaim,
|
|
361
|
+
onTenantOrgClaimChange,
|
|
362
|
+
disabled,
|
|
363
|
+
}: {
|
|
364
|
+
isSso: boolean;
|
|
365
|
+
autoProvision: boolean;
|
|
366
|
+
onAutoProvisionChange: (v: boolean) => void;
|
|
367
|
+
autoGrant: boolean;
|
|
368
|
+
onAutoGrantChange: (v: boolean) => void;
|
|
369
|
+
autoGrantRole: IamRole;
|
|
370
|
+
onAutoGrantRoleChange: (v: IamRole) => void;
|
|
371
|
+
tenantOrgClaim: string;
|
|
372
|
+
onTenantOrgClaimChange: (v: string) => void;
|
|
373
|
+
disabled: boolean;
|
|
374
|
+
}) {
|
|
375
|
+
if (isSso) {
|
|
376
|
+
return (
|
|
377
|
+
<div className="rounded-md border border-border/60 bg-muted/30 px-3 py-2">
|
|
378
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
379
|
+
SSO providers automatically provision accounts and grant the{" "}
|
|
380
|
+
<span className="font-medium text-foreground">viewer</span> role on
|
|
381
|
+
the owning organization. JIT provisioning settings are not applicable.
|
|
382
|
+
</p>
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<fieldset className="space-y-2.5" disabled={disabled}>
|
|
389
|
+
<hr className="border-border/40" />
|
|
390
|
+
<legend className="text-xs font-medium text-foreground">
|
|
391
|
+
JIT provisioning
|
|
392
|
+
</legend>
|
|
393
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
394
|
+
Configure automatic account creation and role assignment for users
|
|
395
|
+
authenticating with this provider.
|
|
396
|
+
</p>
|
|
397
|
+
|
|
398
|
+
<ToggleSwitch
|
|
399
|
+
checked={autoProvision}
|
|
400
|
+
onChange={onAutoProvisionChange}
|
|
401
|
+
label="Auto-provision accounts"
|
|
402
|
+
hint="Create a federated account automatically on first authentication"
|
|
403
|
+
disabled={disabled}
|
|
404
|
+
/>
|
|
405
|
+
|
|
406
|
+
<ToggleSwitch
|
|
407
|
+
checked={autoGrant}
|
|
408
|
+
onChange={onAutoGrantChange}
|
|
409
|
+
label="Auto-grant on organization"
|
|
410
|
+
hint="Grant a role on the owning organization when an account is provisioned"
|
|
411
|
+
disabled={disabled || !autoProvision}
|
|
412
|
+
/>
|
|
413
|
+
|
|
414
|
+
{autoGrant && (
|
|
415
|
+
<>
|
|
416
|
+
<div className="space-y-1">
|
|
417
|
+
<label
|
|
418
|
+
htmlFor="stgm-idp-grant-role"
|
|
419
|
+
className="text-xs font-medium text-foreground"
|
|
420
|
+
>
|
|
421
|
+
Auto-grant role
|
|
422
|
+
</label>
|
|
423
|
+
<select
|
|
424
|
+
id="stgm-idp-grant-role"
|
|
425
|
+
value={String(autoGrantRole)}
|
|
426
|
+
onChange={(e) => onAutoGrantRoleChange(Number(e.target.value) as IamRole)}
|
|
427
|
+
disabled={disabled}
|
|
428
|
+
className={cn(
|
|
429
|
+
"w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
|
|
430
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
431
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
432
|
+
)}
|
|
433
|
+
>
|
|
434
|
+
{JIT_ROLE_OPTIONS.map((opt) => (
|
|
435
|
+
<option key={opt.value} value={opt.value}>
|
|
436
|
+
{opt.label}
|
|
437
|
+
</option>
|
|
438
|
+
))}
|
|
439
|
+
</select>
|
|
440
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
441
|
+
Role granted automatically — org admins can upgrade later
|
|
442
|
+
</p>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<FormField
|
|
446
|
+
id="stgm-idp-tenant-claim"
|
|
447
|
+
label="Tenant org claim"
|
|
448
|
+
value={tenantOrgClaim}
|
|
449
|
+
onChange={onTenantOrgClaimChange}
|
|
450
|
+
placeholder="e.g., org_id"
|
|
451
|
+
hint="JWT claim name that maps to a platform-managed organization (max 256 chars)"
|
|
452
|
+
disabled={disabled}
|
|
453
|
+
/>
|
|
454
|
+
</>
|
|
455
|
+
)}
|
|
456
|
+
</fieldset>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
// Toggle switch
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
|
|
464
|
+
function ToggleSwitch({
|
|
465
|
+
checked,
|
|
466
|
+
onChange,
|
|
467
|
+
label,
|
|
468
|
+
hint,
|
|
469
|
+
disabled,
|
|
470
|
+
}: {
|
|
471
|
+
checked: boolean;
|
|
472
|
+
onChange: (v: boolean) => void;
|
|
473
|
+
label: string;
|
|
474
|
+
hint?: string;
|
|
475
|
+
disabled?: boolean;
|
|
476
|
+
}) {
|
|
477
|
+
return (
|
|
478
|
+
<div className="space-y-0.5">
|
|
479
|
+
<div className="flex items-center gap-2">
|
|
480
|
+
<button
|
|
481
|
+
type="button"
|
|
482
|
+
role="switch"
|
|
483
|
+
aria-checked={checked}
|
|
484
|
+
onClick={() => onChange(!checked)}
|
|
485
|
+
disabled={disabled}
|
|
486
|
+
className={cn(
|
|
487
|
+
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors",
|
|
488
|
+
checked ? "bg-primary" : "bg-muted",
|
|
489
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
490
|
+
)}
|
|
491
|
+
>
|
|
492
|
+
<span
|
|
493
|
+
className={cn(
|
|
494
|
+
"pointer-events-none inline-block h-4 w-4 rounded-full bg-background shadow-sm ring-0 transition-transform",
|
|
495
|
+
checked ? "translate-x-4" : "translate-x-0",
|
|
496
|
+
)}
|
|
497
|
+
/>
|
|
498
|
+
</button>
|
|
499
|
+
<span className="text-xs font-medium text-foreground">{label}</span>
|
|
500
|
+
</div>
|
|
501
|
+
{hint && (
|
|
502
|
+
<p className="pl-11 text-[0.65rem] text-muted-foreground">{hint}</p>
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
288
508
|
// ---------------------------------------------------------------------------
|
|
289
509
|
// Icons
|
|
290
510
|
// ---------------------------------------------------------------------------
|
|
@@ -4,6 +4,7 @@ import { useCallback, useState, type FormEvent } from "react";
|
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { getUserMessage } from "@stigmer/sdk";
|
|
6
6
|
import type { IdentityProvider } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
|
|
7
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
7
8
|
import { timestampDate, type Timestamp } from "@bufbuild/protobuf/wkt";
|
|
8
9
|
import { useUpdateIdentityProvider } from "./useUpdateIdentityProvider";
|
|
9
10
|
|
|
@@ -76,6 +77,30 @@ export function IdentityProviderDetailPanel({
|
|
|
76
77
|
spec?.oidcClientId ?? "",
|
|
77
78
|
);
|
|
78
79
|
|
|
80
|
+
// JIT provisioning
|
|
81
|
+
const [autoProvision, setAutoProvision] = useState(spec?.autoProvisionAccounts ?? false);
|
|
82
|
+
const [autoGrant, setAutoGrant] = useState(spec?.autoGrantOnOrg ?? false);
|
|
83
|
+
const [autoGrantRole, setAutoGrantRole] = useState<IamRole>(spec?.autoGrantRole ?? IamRole.iam_role_unspecified);
|
|
84
|
+
const [tenantOrgClaim, setTenantOrgClaim] = useState(spec?.tenantOrgClaim ?? "");
|
|
85
|
+
|
|
86
|
+
const handleAutoProvisionChange = useCallback((v: boolean) => {
|
|
87
|
+
setAutoProvision(v);
|
|
88
|
+
if (!v) {
|
|
89
|
+
setAutoGrant(false);
|
|
90
|
+
setAutoGrantRole(IamRole.iam_role_unspecified);
|
|
91
|
+
setTenantOrgClaim("");
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const handleAutoGrantChange = useCallback((v: boolean) => {
|
|
96
|
+
setAutoGrant(v);
|
|
97
|
+
if (v) setAutoProvision(true);
|
|
98
|
+
if (!v) {
|
|
99
|
+
setAutoGrantRole(IamRole.iam_role_unspecified);
|
|
100
|
+
setTenantOrgClaim("");
|
|
101
|
+
}
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
79
104
|
const enterEdit = useCallback(() => {
|
|
80
105
|
setDisplayName(spec?.displayName ?? "");
|
|
81
106
|
setJwksUri(spec?.jwksUri ?? "");
|
|
@@ -84,6 +109,10 @@ export function IdentityProviderDetailPanel({
|
|
|
84
109
|
setUserinfoEndpoint(spec?.userinfoEndpoint ?? "");
|
|
85
110
|
setIsSso(spec?.isSsoProvider ?? false);
|
|
86
111
|
setOidcClientId(spec?.oidcClientId ?? "");
|
|
112
|
+
setAutoProvision(spec?.autoProvisionAccounts ?? false);
|
|
113
|
+
setAutoGrant(spec?.autoGrantOnOrg ?? false);
|
|
114
|
+
setAutoGrantRole(spec?.autoGrantRole ?? IamRole.iam_role_unspecified);
|
|
115
|
+
setTenantOrgClaim(spec?.tenantOrgClaim ?? "");
|
|
87
116
|
clearError();
|
|
88
117
|
setMode("edit");
|
|
89
118
|
}, [spec, clearError]);
|
|
@@ -112,6 +141,16 @@ export function IdentityProviderDetailPanel({
|
|
|
112
141
|
userinfoEndpoint: userinfoEndpoint.trim() || undefined,
|
|
113
142
|
isSsoProvider: isSso,
|
|
114
143
|
oidcClientId: isSso ? oidcClientId.trim() : undefined,
|
|
144
|
+
...(!isSso && {
|
|
145
|
+
autoProvisionAccounts: autoProvision,
|
|
146
|
+
autoGrantOnOrg: autoGrant,
|
|
147
|
+
...(autoGrant && autoGrantRole !== IamRole.iam_role_unspecified && {
|
|
148
|
+
autoGrantRole,
|
|
149
|
+
}),
|
|
150
|
+
...(autoGrant && tenantOrgClaim.trim() && {
|
|
151
|
+
tenantOrgClaim: tenantOrgClaim.trim(),
|
|
152
|
+
}),
|
|
153
|
+
}),
|
|
115
154
|
});
|
|
116
155
|
setMode("view");
|
|
117
156
|
onUpdated?.(updated);
|
|
@@ -121,7 +160,8 @@ export function IdentityProviderDetailPanel({
|
|
|
121
160
|
},
|
|
122
161
|
[
|
|
123
162
|
meta, displayName, jwksUri, issuers, audience,
|
|
124
|
-
userinfoEndpoint, isSso, oidcClientId,
|
|
163
|
+
userinfoEndpoint, isSso, oidcClientId, autoProvision, autoGrant,
|
|
164
|
+
autoGrantRole, tenantOrgClaim, update, clearError, onUpdated,
|
|
125
165
|
],
|
|
126
166
|
);
|
|
127
167
|
|
|
@@ -160,11 +200,7 @@ export function IdentityProviderDetailPanel({
|
|
|
160
200
|
{meta.slug}
|
|
161
201
|
</span>
|
|
162
202
|
)}
|
|
163
|
-
{spec
|
|
164
|
-
<span className="inline-flex items-center rounded-full border border-primary/30 bg-primary-subtle px-2 py-0.5 text-[0.65rem] font-medium text-primary">
|
|
165
|
-
SSO
|
|
166
|
-
</span>
|
|
167
|
-
)}
|
|
203
|
+
<ProvisioningModeBadge spec={spec} />
|
|
168
204
|
</div>
|
|
169
205
|
</div>
|
|
170
206
|
|
|
@@ -279,6 +315,20 @@ export function IdentityProviderDetailPanel({
|
|
|
279
315
|
/>
|
|
280
316
|
)}
|
|
281
317
|
|
|
318
|
+
{/* JIT provisioning */}
|
|
319
|
+
<JitEditSection
|
|
320
|
+
isSso={isSso}
|
|
321
|
+
autoProvision={autoProvision}
|
|
322
|
+
onAutoProvisionChange={handleAutoProvisionChange}
|
|
323
|
+
autoGrant={autoGrant}
|
|
324
|
+
onAutoGrantChange={handleAutoGrantChange}
|
|
325
|
+
autoGrantRole={autoGrantRole}
|
|
326
|
+
onAutoGrantRoleChange={setAutoGrantRole}
|
|
327
|
+
tenantOrgClaim={tenantOrgClaim}
|
|
328
|
+
onTenantOrgClaimChange={setTenantOrgClaim}
|
|
329
|
+
disabled={isUpdating}
|
|
330
|
+
/>
|
|
331
|
+
|
|
282
332
|
{error && (
|
|
283
333
|
<p className="text-destructive text-[0.65rem]" role="alert">
|
|
284
334
|
{getUserMessage(error)}
|
|
@@ -360,6 +410,35 @@ function ViewMode({
|
|
|
360
410
|
value={`${spec!.rateLimitBudget} req/min`}
|
|
361
411
|
/>
|
|
362
412
|
)}
|
|
413
|
+
|
|
414
|
+
{/* JIT provisioning fields */}
|
|
415
|
+
{!spec?.isSsoProvider && spec?.autoProvisionAccounts && (
|
|
416
|
+
<>
|
|
417
|
+
<hr className="border-border/40" />
|
|
418
|
+
<Field
|
|
419
|
+
label="Auto-provision accounts"
|
|
420
|
+
value={spec.autoProvisionAccounts ? "Enabled" : "Disabled"}
|
|
421
|
+
/>
|
|
422
|
+
<Field
|
|
423
|
+
label="Auto-grant on organization"
|
|
424
|
+
value={spec.autoGrantOnOrg ? "Enabled" : "Disabled"}
|
|
425
|
+
/>
|
|
426
|
+
{spec.autoGrantOnOrg && (
|
|
427
|
+
<Field
|
|
428
|
+
label="Auto-grant role"
|
|
429
|
+
value={formatIamRole(spec.autoGrantRole)}
|
|
430
|
+
/>
|
|
431
|
+
)}
|
|
432
|
+
{spec.autoGrantOnOrg && spec.tenantOrgClaim && (
|
|
433
|
+
<Field
|
|
434
|
+
label="Tenant org claim"
|
|
435
|
+
value={spec.tenantOrgClaim}
|
|
436
|
+
mono
|
|
437
|
+
/>
|
|
438
|
+
)}
|
|
439
|
+
</>
|
|
440
|
+
)}
|
|
441
|
+
|
|
363
442
|
<div className="flex gap-6">
|
|
364
443
|
{createdAt && (
|
|
365
444
|
<Field
|
|
@@ -534,6 +613,209 @@ function FieldInput({
|
|
|
534
613
|
);
|
|
535
614
|
}
|
|
536
615
|
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
617
|
+
// Provisioning mode badge
|
|
618
|
+
// ---------------------------------------------------------------------------
|
|
619
|
+
|
|
620
|
+
function ProvisioningModeBadge({ spec }: { spec: IdentityProvider["spec"] }) {
|
|
621
|
+
if (spec?.isSsoProvider) {
|
|
622
|
+
return (
|
|
623
|
+
<span className="inline-flex items-center rounded-full border border-primary/30 bg-primary-subtle px-2 py-0.5 text-[0.65rem] font-medium text-primary">
|
|
624
|
+
SSO
|
|
625
|
+
</span>
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
if (spec?.autoProvisionAccounts) {
|
|
629
|
+
return (
|
|
630
|
+
<span className="inline-flex items-center rounded-full border border-primary/30 bg-primary-subtle px-2 py-0.5 text-[0.65rem] font-medium text-primary">
|
|
631
|
+
JIT
|
|
632
|
+
</span>
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// ---------------------------------------------------------------------------
|
|
639
|
+
// JIT edit section
|
|
640
|
+
// ---------------------------------------------------------------------------
|
|
641
|
+
|
|
642
|
+
const JIT_ROLE_OPTIONS: readonly { readonly value: string; readonly label: string }[] = [
|
|
643
|
+
{ value: String(IamRole.iam_role_unspecified), label: "Default (viewer)" },
|
|
644
|
+
{ value: String(IamRole.viewer), label: "Viewer" },
|
|
645
|
+
{ value: String(IamRole.member), label: "Member" },
|
|
646
|
+
{ value: String(IamRole.admin), label: "Admin" },
|
|
647
|
+
];
|
|
648
|
+
|
|
649
|
+
function JitEditSection({
|
|
650
|
+
isSso,
|
|
651
|
+
autoProvision,
|
|
652
|
+
onAutoProvisionChange,
|
|
653
|
+
autoGrant,
|
|
654
|
+
onAutoGrantChange,
|
|
655
|
+
autoGrantRole,
|
|
656
|
+
onAutoGrantRoleChange,
|
|
657
|
+
tenantOrgClaim,
|
|
658
|
+
onTenantOrgClaimChange,
|
|
659
|
+
disabled,
|
|
660
|
+
}: {
|
|
661
|
+
isSso: boolean;
|
|
662
|
+
autoProvision: boolean;
|
|
663
|
+
onAutoProvisionChange: (v: boolean) => void;
|
|
664
|
+
autoGrant: boolean;
|
|
665
|
+
onAutoGrantChange: (v: boolean) => void;
|
|
666
|
+
autoGrantRole: IamRole;
|
|
667
|
+
onAutoGrantRoleChange: (v: IamRole) => void;
|
|
668
|
+
tenantOrgClaim: string;
|
|
669
|
+
onTenantOrgClaimChange: (v: string) => void;
|
|
670
|
+
disabled?: boolean;
|
|
671
|
+
}) {
|
|
672
|
+
if (isSso) {
|
|
673
|
+
return (
|
|
674
|
+
<div className="rounded-md border border-border/60 bg-muted/30 px-3 py-2">
|
|
675
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
676
|
+
SSO providers automatically provision accounts and grant the{" "}
|
|
677
|
+
<span className="font-medium text-foreground">viewer</span> role on
|
|
678
|
+
the owning organization. JIT provisioning settings are not applicable.
|
|
679
|
+
</p>
|
|
680
|
+
</div>
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return (
|
|
685
|
+
<fieldset className="space-y-2.5" disabled={disabled}>
|
|
686
|
+
<hr className="border-border/40" />
|
|
687
|
+
<legend className="text-xs font-medium text-foreground">
|
|
688
|
+
JIT provisioning
|
|
689
|
+
</legend>
|
|
690
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
691
|
+
Configure automatic account creation and role assignment for users
|
|
692
|
+
authenticating with this provider.
|
|
693
|
+
</p>
|
|
694
|
+
|
|
695
|
+
<ToggleSwitch
|
|
696
|
+
checked={autoProvision}
|
|
697
|
+
onChange={onAutoProvisionChange}
|
|
698
|
+
label="Auto-provision accounts"
|
|
699
|
+
hint="Create a federated account automatically on first authentication"
|
|
700
|
+
disabled={disabled}
|
|
701
|
+
/>
|
|
702
|
+
|
|
703
|
+
<ToggleSwitch
|
|
704
|
+
checked={autoGrant}
|
|
705
|
+
onChange={onAutoGrantChange}
|
|
706
|
+
label="Auto-grant on organization"
|
|
707
|
+
hint="Grant a role on the owning organization when an account is provisioned"
|
|
708
|
+
disabled={disabled || !autoProvision}
|
|
709
|
+
/>
|
|
710
|
+
|
|
711
|
+
{autoGrant && (
|
|
712
|
+
<>
|
|
713
|
+
<div className="space-y-1">
|
|
714
|
+
<label
|
|
715
|
+
htmlFor="stgm-idp-edit-grant-role"
|
|
716
|
+
className="text-xs font-medium text-foreground"
|
|
717
|
+
>
|
|
718
|
+
Auto-grant role
|
|
719
|
+
</label>
|
|
720
|
+
<select
|
|
721
|
+
id="stgm-idp-edit-grant-role"
|
|
722
|
+
value={String(autoGrantRole)}
|
|
723
|
+
onChange={(e) => onAutoGrantRoleChange(Number(e.target.value) as IamRole)}
|
|
724
|
+
disabled={disabled}
|
|
725
|
+
className={cn(
|
|
726
|
+
"w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
|
|
727
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
728
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
729
|
+
)}
|
|
730
|
+
>
|
|
731
|
+
{JIT_ROLE_OPTIONS.map((opt) => (
|
|
732
|
+
<option key={opt.value} value={opt.value}>
|
|
733
|
+
{opt.label}
|
|
734
|
+
</option>
|
|
735
|
+
))}
|
|
736
|
+
</select>
|
|
737
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
738
|
+
Role granted automatically — org admins can upgrade later
|
|
739
|
+
</p>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
<FieldInput
|
|
743
|
+
id="stgm-idp-edit-tenant-claim"
|
|
744
|
+
label="Tenant org claim"
|
|
745
|
+
value={tenantOrgClaim}
|
|
746
|
+
onChange={onTenantOrgClaimChange}
|
|
747
|
+
placeholder="e.g., org_id"
|
|
748
|
+
hint="JWT claim name that maps to a platform-managed organization (max 256 chars)"
|
|
749
|
+
disabled={disabled}
|
|
750
|
+
/>
|
|
751
|
+
</>
|
|
752
|
+
)}
|
|
753
|
+
</fieldset>
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ---------------------------------------------------------------------------
|
|
758
|
+
// Toggle switch
|
|
759
|
+
// ---------------------------------------------------------------------------
|
|
760
|
+
|
|
761
|
+
function ToggleSwitch({
|
|
762
|
+
checked,
|
|
763
|
+
onChange,
|
|
764
|
+
label,
|
|
765
|
+
hint,
|
|
766
|
+
disabled,
|
|
767
|
+
}: {
|
|
768
|
+
checked: boolean;
|
|
769
|
+
onChange: (v: boolean) => void;
|
|
770
|
+
label: string;
|
|
771
|
+
hint?: string;
|
|
772
|
+
disabled?: boolean;
|
|
773
|
+
}) {
|
|
774
|
+
return (
|
|
775
|
+
<div className="space-y-0.5">
|
|
776
|
+
<div className="flex items-center gap-2">
|
|
777
|
+
<button
|
|
778
|
+
type="button"
|
|
779
|
+
role="switch"
|
|
780
|
+
aria-checked={checked}
|
|
781
|
+
onClick={() => onChange(!checked)}
|
|
782
|
+
disabled={disabled}
|
|
783
|
+
className={cn(
|
|
784
|
+
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors",
|
|
785
|
+
checked ? "bg-primary" : "bg-muted",
|
|
786
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
787
|
+
)}
|
|
788
|
+
>
|
|
789
|
+
<span
|
|
790
|
+
className={cn(
|
|
791
|
+
"pointer-events-none inline-block h-4 w-4 rounded-full bg-background shadow-sm ring-0 transition-transform",
|
|
792
|
+
checked ? "translate-x-4" : "translate-x-0",
|
|
793
|
+
)}
|
|
794
|
+
/>
|
|
795
|
+
</button>
|
|
796
|
+
<span className="text-xs font-medium text-foreground">{label}</span>
|
|
797
|
+
</div>
|
|
798
|
+
{hint && (
|
|
799
|
+
<p className="pl-11 text-[0.65rem] text-muted-foreground">{hint}</p>
|
|
800
|
+
)}
|
|
801
|
+
</div>
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
// Helpers
|
|
807
|
+
// ---------------------------------------------------------------------------
|
|
808
|
+
|
|
809
|
+
function formatIamRole(role: IamRole): string {
|
|
810
|
+
switch (role) {
|
|
811
|
+
case IamRole.viewer: return "Viewer";
|
|
812
|
+
case IamRole.member: return "Member";
|
|
813
|
+
case IamRole.admin: return "Admin";
|
|
814
|
+
case IamRole.owner: return "Owner";
|
|
815
|
+
default: return "Viewer (default)";
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
537
819
|
function formatDate(date: Date): string {
|
|
538
820
|
return date.toLocaleDateString(undefined, {
|
|
539
821
|
month: "short",
|
|
@@ -148,12 +148,14 @@ function IdpRow({
|
|
|
148
148
|
const { deleteProvider, isDeleting, error } = useDeleteIdentityProvider();
|
|
149
149
|
|
|
150
150
|
const id = identityProvider.metadata?.id ?? "";
|
|
151
|
+
const spec = identityProvider.spec;
|
|
151
152
|
const name =
|
|
152
|
-
|
|
153
|
+
spec?.displayName ||
|
|
153
154
|
identityProvider.metadata?.name ||
|
|
154
155
|
"Unnamed provider";
|
|
155
156
|
const slug = identityProvider.metadata?.slug;
|
|
156
|
-
const isSso =
|
|
157
|
+
const isSso = spec?.isSsoProvider;
|
|
158
|
+
const isJit = !isSso && spec?.autoProvisionAccounts;
|
|
157
159
|
const createdAt =
|
|
158
160
|
identityProvider.status?.audit?.specAudit?.createdAt;
|
|
159
161
|
|
|
@@ -238,6 +240,11 @@ function IdpRow({
|
|
|
238
240
|
SSO
|
|
239
241
|
</span>
|
|
240
242
|
)}
|
|
243
|
+
{isJit && (
|
|
244
|
+
<span className="inline-flex items-center rounded-full border border-primary/30 bg-primary-subtle px-2 py-0.5 text-[0.65rem] font-medium text-primary">
|
|
245
|
+
JIT
|
|
246
|
+
</span>
|
|
247
|
+
)}
|
|
241
248
|
{createdAt && (
|
|
242
249
|
<span title={`Created ${timestampDate(createdAt).toISOString()}`}>
|
|
243
250
|
{formatShortDate(timestampDate(createdAt))}
|