@open-mercato/enterprise 0.4.6-develop-3a8b7804a5 → 0.4.6-develop-a71fbbd3da
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.
|
@@ -541,8 +541,19 @@ function RoleMappingsTab({
|
|
|
541
541
|
/* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: isSaving, children: isSaving ? t("common.saving", "Saving...") : t("common.save", "Save") })
|
|
542
542
|
] });
|
|
543
543
|
}
|
|
544
|
+
const googleIssuerHost = "accounts.google.com";
|
|
545
|
+
function isGoogleIssuer(issuer) {
|
|
546
|
+
if (!issuer) return false;
|
|
547
|
+
const normalizedIssuer = issuer.trim();
|
|
548
|
+
if (!normalizedIssuer) return false;
|
|
549
|
+
try {
|
|
550
|
+
return new URL(normalizedIssuer).hostname.toLowerCase() === googleIssuerHost;
|
|
551
|
+
} catch {
|
|
552
|
+
return normalizedIssuer.toLowerCase() === googleIssuerHost;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
544
555
|
function ScimProvisioningTab({ configId, jitEnabled, issuer, onProvisioningChange, runMutationWithContext }) {
|
|
545
|
-
const isGoogleProvider = issuer
|
|
556
|
+
const isGoogleProvider = isGoogleIssuer(issuer);
|
|
546
557
|
const t = useT();
|
|
547
558
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
548
559
|
const [tokens, setTokens] = React.useState([]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/sso/backend/sso/config/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useParams, useRouter, useSearchParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\ntype Tab = 'general' | 'domains' | 'roles' | 'scim' | 'activity'\n\ninterface SsoConfigDetail {\n id: string\n name: string | null\n tenantId: string | null\n organizationId: string\n protocol: string\n issuer: string | null\n clientId: string | null\n hasClientSecret: boolean\n allowedDomains: string[]\n jitEnabled: boolean\n autoLinkByEmail: boolean\n isActive: boolean\n ssoRequired: boolean\n appRoleMappings: Record<string, string>\n hasActiveScimTokens: boolean\n createdAt: string\n updatedAt: string\n}\n\ninterface SsoIdentityRow {\n id: string\n userId: string\n idpEmail: string\n idpName: string | null\n provisioningMethod: string\n lastLoginAt: string | null\n createdAt: string\n}\n\nexport default function SsoConfigDetailPage() {\n const params = useParams()\n const configId = (params?.slug && Array.isArray(params.slug))\n ? params.slug[2]\n : (Array.isArray(params?.id) ? params.id[0] : params?.id as string)\n const router = useRouter()\n const searchParams = useSearchParams()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [config, setConfig] = React.useState<SsoConfigDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [activeTab, setActiveTab] = React.useState<Tab>('general')\n const [showActivationBanner, setShowActivationBanner] = React.useState(searchParams?.get('created') === '1')\n const [activationError, setActivationError] = React.useState<string | null>(null)\n const [isActivating, setIsActivating] = React.useState(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: `sso-config:${configId ?? 'pending'}`,\n })\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { configId, retryLastMutation },\n })\n },\n [configId, retryLastMutation, runMutation],\n )\n\n // General tab form state\n const [name, setName] = React.useState('')\n const [issuer, setIssuer] = React.useState('')\n const [clientId, setClientId] = React.useState('')\n const [newClientSecret, setNewClientSecret] = React.useState('')\n const [showSecretField, setShowSecretField] = React.useState(false)\n const [jitEnabled, setJitEnabled] = React.useState(true)\n const [autoLinkByEmail, setAutoLinkByEmail] = React.useState(true)\n const [isSaving, setIsSaving] = React.useState(false)\n\n // Domains tab state\n const [domainInput, setDomainInput] = React.useState('')\n const [domainError, setDomainError] = React.useState('')\n\n const fetchConfig = React.useCallback(async () => {\n setIsLoading(true)\n const call = await apiCall<SsoConfigDetail>(`/api/sso/config/${configId}`)\n if (call.ok && call.result) {\n const c = call.result\n setConfig(c)\n setName(c.name ?? '')\n setIssuer(c.issuer ?? '')\n setClientId(c.clientId ?? '')\n setJitEnabled(c.jitEnabled)\n setAutoLinkByEmail(c.autoLinkByEmail)\n setError(null)\n } else {\n setError(t('sso.admin.error.loadFailed', 'Failed to load SSO configuration'))\n }\n setIsLoading(false)\n }, [configId, t])\n\n React.useEffect(() => { fetchConfig() }, [fetchConfig])\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n const payload: Record<string, unknown> = { name, issuer, clientId, jitEnabled, autoLinkByEmail }\n if (newClientSecret) payload.clientSecret = newClientSecret\n\n await runMutationWithContext(\n () => apiCallOrThrow<SsoConfigDetail>(\n `/api/sso/config/${configId}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: t('sso.admin.error.saveFailed', 'Failed to save SSO configuration') },\n ),\n payload,\n )\n flash(t('sso.admin.saved', 'SSO configuration saved'), 'success')\n setNewClientSecret('')\n setShowSecretField(false)\n fetchConfig()\n } catch {\n // apiCallOrThrow handles the error\n } finally {\n setIsSaving(false)\n }\n }\n\n const handleToggleActivation = async () => {\n if (!config) return\n setActivationError(null)\n setIsActivating(true)\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}/activate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active: !config.isActive }),\n },\n { errorMessage: t('sso.admin.error.activationFailed', 'Failed to update activation status') },\n ),\n { active: !config.isActive },\n )\n flash(\n config.isActive\n ? t('sso.admin.deactivated', 'SSO configuration deactivated')\n : t('sso.admin.activated', 'SSO configuration activated'),\n 'success',\n )\n setShowActivationBanner(false)\n fetchConfig()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const isNoDomains = message.toLowerCase().includes('no allowed domains')\n if (isNoDomains) {\n setActivationError(t('sso.admin.error.noDomainsForActivation', 'Add at least one allowed email domain before activating'))\n setActiveTab('domains')\n } else {\n setActivationError(message)\n }\n } finally {\n setIsActivating(false)\n }\n }\n\n const handleTestConnection = async () => {\n try {\n const call = await runMutationWithContext(\n () => apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/sso/config/${configId}/test`,\n { method: 'POST' },\n { errorMessage: t('sso.admin.error.testFailed', 'Connection test failed') },\n ),\n )\n if (call.result?.ok) {\n flash(t('sso.admin.test.success', 'Discovery successful \u2014 issuer is reachable'), 'success')\n } else {\n flash(call.result?.error || t('sso.admin.test.failed', 'Discovery failed'), 'error')\n }\n } catch {\n // handled by apiCallOrThrow\n }\n }\n\n const handleDelete = async () => {\n if (!config) return\n if (config.isActive) {\n flash(t('sso.admin.error.deleteActive', 'Cannot delete an active SSO configuration \u2014 deactivate it first'), 'error')\n return\n }\n const confirmed = await confirm({\n title: t('sso.admin.delete.title', 'Delete SSO Configuration'),\n text: t('sso.admin.delete.confirm', 'Are you sure? This will remove the SSO configuration.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n await runMutationWithContext(\n () => apiCallOrThrow(`/api/sso/config/${configId}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.error.deleteFailed', 'Failed to delete SSO configuration'),\n }),\n { id: configId },\n )\n flash(t('sso.admin.delete.success', 'SSO configuration deleted'), 'success')\n router.push('/backend/sso')\n }\n\n const handleAddDomain = async () => {\n const normalized = domainInput.trim().toLowerCase()\n if (!normalized) return\n\n const domainRegex = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/\n if (!domainRegex.test(normalized) || !normalized.includes('.')) {\n setDomainError(t('sso.admin.wizard.domain.invalid', 'Invalid domain format'))\n return\n }\n\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}/domains`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ domain: normalized }),\n },\n { errorMessage: t('sso.admin.error.domainAddFailed', 'Failed to add domain') },\n ),\n { domain: normalized },\n )\n setDomainInput('')\n setDomainError('')\n fetchConfig()\n } catch {\n // handled by apiCallOrThrow\n }\n }\n\n const handleRemoveDomain = async (domain: string) => {\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}/domains?domain=${encodeURIComponent(domain)}`,\n { method: 'DELETE' },\n { errorMessage: t('sso.admin.error.domainRemoveFailed', 'Failed to remove domain') },\n ),\n { domain },\n )\n fetchConfig()\n } catch {\n // handled by apiCallOrThrow\n }\n }\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('common.loading', 'Loading...')} /></PageBody></Page>\n if (error || !config) return <Page><PageBody><ErrorMessage label={error || t('common.notFound', 'Not found')} /></PageBody></Page>\n\n const tabs: { id: Tab; label: string }[] = [\n { id: 'general', label: t('sso.admin.tab.general', 'General') },\n { id: 'domains', label: t('sso.admin.tab.domains', 'Domains') },\n { id: 'roles', label: t('sso.admin.tab.roles', 'Role Mappings') },\n { id: 'scim', label: t('sso.admin.tab.scim', 'Provisioning') },\n { id: 'activity', label: t('sso.admin.tab.activity', 'Activity') },\n ]\n\n const statusBadge = (\n <span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${config.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'}`}>\n {config.isActive ? t('sso.admin.status.active', 'Active') : t('sso.admin.status.inactive', 'Inactive')}\n </span>\n )\n\n return (\n <Page>\n <PageBody>\n <div className=\"flex flex-col gap-6 max-w-3xl\">\n {/* Activation banner after creation */}\n {showActivationBanner && !config.isActive && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-4\">\n <p className=\"text-sm font-medium text-blue-900 mb-3\">\n {t('sso.admin.banner.created', 'Your SSO configuration has been created. Would you like to activate it now?')}\n </p>\n {activationError && (\n <p className=\"text-sm text-destructive mb-3\">{activationError}</p>\n )}\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" onClick={handleToggleActivation} disabled={isActivating}>\n {isActivating\n ? t('common.activating', 'Activating...')\n : t('sso.admin.banner.activateNow', 'Activate Now')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowActivationBanner(false)}>\n {t('sso.admin.banner.notYet', 'Not Yet')}\n </Button>\n </div>\n </div>\n )}\n\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/sso\"\n backLabel={t('sso.admin.detail.backToList', 'Back to SSO')}\n title={config.name || config.issuer || t('sso.admin.detail.title', 'SSO Configuration')}\n statusBadge={statusBadge}\n actionsContent={\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleTestConnection}>\n {t('sso.admin.action.test', 'Verify Discovery')}\n </Button>\n <Button\n type=\"button\"\n variant={config.isActive ? 'outline' : 'default'}\n size=\"sm\"\n onClick={handleToggleActivation}\n >\n {config.isActive\n ? t('sso.admin.action.deactivate', 'Deactivate')\n : t('sso.admin.action.activate', 'Activate')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleDelete} className=\"text-destructive\">\n {t('common.delete', 'Delete')}\n </Button>\n </div>\n }\n />\n\n {/* Tabs */}\n <div className=\"flex gap-1 border-b\">\n {tabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`h-auto rounded-none border-b-2 px-4 py-2 hover:bg-transparent ${\n activeTab === tab.id\n ? 'border-primary text-foreground'\n : 'border-transparent text-muted-foreground'\n }`}\n onClick={() => setActiveTab(tab.id)}\n >\n {tab.label}\n </Button>\n ))}\n </div>\n\n {/* Tab content */}\n {activeTab === 'general' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <h2 className=\"mb-4 text-sm font-semibold uppercase text-muted-foreground\">\n {t('sso.admin.section.oidcSettings', 'OIDC Settings')}\n </h2>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.name', 'Configuration Name')}</label>\n <input\n type=\"text\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.protocol', 'Protocol')}</label>\n <input type=\"text\" className=\"w-full rounded-md border px-3 py-2 text-sm bg-muted\" value={config.protocol.toUpperCase()} disabled />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.issuer', 'Issuer URL')}</label>\n <input\n type=\"url\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={issuer}\n onChange={(e) => setIssuer(e.target.value)}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.clientId', 'Client ID')}</label>\n <input\n type=\"text\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={clientId}\n onChange={(e) => setClientId(e.target.value)}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.clientSecret', 'Client Secret')}</label>\n {config.hasClientSecret && !showSecretField ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">{t('sso.admin.field.secretSet', 'Client secret is configured')}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setShowSecretField(true)}>\n {t('sso.admin.field.changeSecret', 'Change')}\n </Button>\n </div>\n ) : (\n <input\n type=\"password\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={newClientSecret}\n onChange={(e) => setNewClientSecret(e.target.value)}\n placeholder={config.hasClientSecret\n ? t('sso.admin.field.secretPlaceholder', 'Enter new secret to replace existing')\n : t('sso.admin.field.secretRequired', 'Enter client secret')}\n />\n )}\n </div>\n <div className=\"space-y-3 pt-2\">\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={jitEnabled}\n onChange={(e) => setJitEnabled(e.target.checked)}\n disabled={config.hasActiveScimTokens}\n className=\"accent-primary\"\n />\n <div>\n <span className=\"text-sm font-medium\">{t('sso.admin.field.jitEnabled', 'Just-in-Time Provisioning')}</span>\n <span className=\"text-xs text-muted-foreground ml-2\">\n {config.hasActiveScimTokens\n ? t('sso.admin.field.jitDisabledByScim', 'Unavailable \u2014 SCIM directory sync is active. Revoke SCIM tokens to enable JIT.')\n : t('sso.admin.field.jitEnabledDesc', 'Automatically create user accounts on first SSO login')}\n </span>\n </div>\n </label>\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={autoLinkByEmail}\n onChange={(e) => setAutoLinkByEmail(e.target.checked)}\n className=\"accent-primary\"\n />\n <div>\n <span className=\"text-sm font-medium\">{t('sso.admin.field.autoLinkByEmail', 'Auto-link by Email')}</span>\n <span className=\"text-xs text-muted-foreground ml-2\">\n {t('sso.admin.field.autoLinkByEmailDesc', 'Automatically link existing users by matching email address')}\n </span>\n </div>\n </label>\n </div>\n <div className=\"pt-4\">\n <Button onClick={handleSave} disabled={isSaving}>\n {isSaving ? t('common.saving', 'Saving...') : t('common.save', 'Save')}\n </Button>\n </div>\n </div>\n </div>\n )}\n\n {activeTab === 'domains' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <h2 className=\"mb-4 text-sm font-semibold uppercase text-muted-foreground\">\n {t('sso.admin.section.allowedDomains', 'Allowed Domains')}\n </h2>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('sso.admin.wizard.domains.description', 'Users with email addresses matching these domains will be redirected to your SSO provider.')}\n </p>\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded-md border px-3 py-2 text-sm\"\n placeholder={t('sso.admin.wizard.domains.placeholder', 'example.com')}\n value={domainInput}\n onChange={(e) => { setDomainInput(e.target.value); setDomainError('') }}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddDomain() } }}\n />\n <Button type=\"button\" variant=\"outline\" onClick={handleAddDomain}>\n {t('common.add', 'Add')}\n </Button>\n </div>\n {domainError && <p className=\"text-sm text-destructive mb-2\">{domainError}</p>}\n {config.allowedDomains.length > 0 ? (\n <div className=\"space-y-2\">\n {config.allowedDomains.map((domain) => (\n <div key={domain} className=\"flex items-center justify-between p-3 border rounded-md\">\n <code className=\"text-sm font-mono\">{domain}</code>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => handleRemoveDomain(domain)}>\n {t('common.remove', 'Remove')}\n </Button>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {t('sso.admin.domains.empty', 'No domains configured. Add at least one domain before activating SSO.')}\n </p>\n )}\n </div>\n )}\n\n {activeTab === 'roles' && config && (\n <div className=\"rounded-lg border bg-card p-4\">\n <RoleMappingsTab\n configId={configId}\n appRoleMappings={config.appRoleMappings ?? {}}\n onSaved={fetchConfig}\n runMutationWithContext={runMutationWithContext}\n />\n </div>\n )}\n\n {activeTab === 'scim' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <ScimProvisioningTab configId={configId} jitEnabled={config.jitEnabled} issuer={config.issuer ?? undefined} onProvisioningChange={fetchConfig} runMutationWithContext={runMutationWithContext} />\n </div>\n )}\n\n {activeTab === 'activity' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <SsoActivityTab configId={configId} />\n </div>\n )}\n </div>\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n\nfunction RoleMappingsTab({\n configId,\n appRoleMappings,\n onSaved,\n runMutationWithContext,\n}: {\n configId: string\n appRoleMappings: Record<string, string>\n onSaved: () => void\n runMutationWithContext: <T>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => Promise<T>\n}) {\n const t = useT()\n const [mappings, setMappings] = React.useState<Record<string, string>>(appRoleMappings)\n const [idpRoleInput, setIdpRoleInput] = React.useState('')\n const [localRoleInput, setLocalRoleInput] = React.useState('')\n const [roleOptions, setRoleOptions] = React.useState<{ value: string; label: string }[]>([])\n const [isSaving, setIsSaving] = React.useState(false)\n const [inputError, setInputError] = React.useState('')\n\n React.useEffect(() => {\n setMappings(appRoleMappings)\n }, [appRoleMappings])\n\n React.useEffect(() => {\n const loadRoles = async () => {\n const call = await apiCall<{ items?: Array<{ id?: string | null; name?: string | null }> }>(\n '/api/auth/roles?page=1&pageSize=50',\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && Array.isArray(call.result?.items)) {\n const options = call.result.items\n .map((item) => {\n const name = typeof item?.name === 'string' ? item.name.trim() : ''\n if (!name || name === 'superadmin') return null\n return { value: name, label: name }\n })\n .filter((opt): opt is { value: string; label: string } => !!opt)\n setRoleOptions(options)\n if (options.length > 0 && !localRoleInput) {\n setLocalRoleInput(options[0].value)\n }\n }\n }\n loadRoles()\n }, [])\n\n const handleAdd = () => {\n const trimmed = idpRoleInput.trim()\n if (!trimmed) {\n setInputError(t('sso.admin.roles.error.emptyIdpRole', 'IdP role name is required'))\n return\n }\n if (!localRoleInput) {\n setInputError(t('sso.admin.roles.error.emptyLocalRole', 'Select a local role'))\n return\n }\n if (mappings[trimmed]) {\n setInputError(t('sso.admin.roles.error.duplicate', 'This IdP role is already mapped'))\n return\n }\n setMappings({ ...mappings, [trimmed]: localRoleInput })\n setIdpRoleInput('')\n setInputError('')\n }\n\n const handleRemove = (idpRole: string) => {\n const updated = { ...mappings }\n delete updated[idpRole]\n setMappings(updated)\n }\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ appRoleMappings: mappings }),\n },\n { errorMessage: t('sso.admin.roles.error.saveFailed', 'Failed to save role mappings') },\n ),\n { appRoleMappings: mappings },\n )\n flash(t('sso.admin.roles.saved', 'Role mappings saved'), 'success')\n onSaved()\n } catch {\n // handled by apiCallOrThrow\n } finally {\n setIsSaving(false)\n }\n }\n\n const mappingEntries = Object.entries(mappings)\n\n return (\n <div>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('sso.admin.roles.description', 'Map IdP app role names to local roles. On each SSO login, SSO-sourced roles are synced \u2014 roles no longer sent by the IdP are removed, while manually-assigned roles are preserved.')}\n </p>\n <div className=\"flex items-end gap-2 mb-4\">\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium mb-1\">{t('sso.admin.roles.idpRole', 'IdP Role Name')}</label>\n <input\n type=\"text\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n placeholder={t('sso.admin.roles.idpRolePlaceholder', 'e.g. OpenMercato.Admin')}\n value={idpRoleInput}\n onChange={(e) => { setIdpRoleInput(e.target.value); setInputError('') }}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAdd() } }}\n />\n </div>\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium mb-1\">{t('sso.admin.roles.localRole', 'Local Role')}</label>\n <select\n className=\"w-full rounded-md border px-3 py-2 text-sm bg-background\"\n value={localRoleInput}\n onChange={(e) => setLocalRoleInput(e.target.value)}\n >\n {roleOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n </div>\n <Button variant=\"outline\" onClick={handleAdd}>\n {t('common.add', 'Add')}\n </Button>\n </div>\n {inputError && <p className=\"text-sm text-destructive mb-2\">{inputError}</p>}\n\n {mappingEntries.length > 0 ? (\n <div className=\"space-y-2 mb-4\">\n {mappingEntries.map(([idpRole, localRole]) => (\n <div key={idpRole} className=\"flex items-center justify-between p-3 border rounded-md\">\n <div className=\"flex items-center gap-2\">\n <code className=\"text-sm font-mono\">{idpRole}</code>\n <span className=\"text-muted-foreground text-sm\">→</span>\n <span className=\"text-sm font-medium\">{localRole}</span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => handleRemove(idpRole)}>\n {t('common.remove', 'Remove')}\n </Button>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground py-4 text-center mb-4\">\n {t('sso.admin.roles.empty', 'No role mappings configured. IdP role names will be matched directly against local role names.')}\n </p>\n )}\n\n <Button onClick={handleSave} disabled={isSaving}>\n {isSaving ? t('common.saving', 'Saving...') : t('common.save', 'Save')}\n </Button>\n </div>\n )\n}\n\ninterface ScimTokenRow {\n id: string\n ssoConfigId: string\n name: string\n tokenPrefix: string\n isActive: boolean\n createdAt: string\n}\n\ninterface ScimLogRow {\n id: string\n operation: string\n resourceType: string\n resourceId: string | null\n scimExternalId: string | null\n responseStatus: number\n errorMessage: string | null\n createdAt: string\n}\n\nfunction ScimProvisioningTab({ configId, jitEnabled, issuer, onProvisioningChange, runMutationWithContext }: { configId: string; jitEnabled: boolean; issuer?: string; onProvisioningChange: () => void; runMutationWithContext: <T>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => Promise<T> }) {\n const isGoogleProvider = issuer?.includes('accounts.google.com') === true\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [tokens, setTokens] = React.useState<ScimTokenRow[]>([])\n const [logs, setLogs] = React.useState<ScimLogRow[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [showCreateForm, setShowCreateForm] = React.useState(false)\n const [tokenName, setTokenName] = React.useState('')\n const [isCreating, setIsCreating] = React.useState(false)\n const [newlyCreatedToken, setNewlyCreatedToken] = React.useState<string | null>(null)\n\n const fetchData = React.useCallback(async () => {\n setIsLoading(true)\n const [tokensCall, logsCall] = await Promise.all([\n apiCall<{ items: ScimTokenRow[] }>(`/api/sso/scim/tokens?ssoConfigId=${configId}`),\n apiCall<{ items: ScimLogRow[] }>(`/api/sso/scim/logs?ssoConfigId=${configId}`),\n ])\n if (tokensCall.ok && tokensCall.result) setTokens(tokensCall.result.items)\n if (logsCall.ok && logsCall.result) setLogs(logsCall.result.items)\n setIsLoading(false)\n }, [configId])\n\n React.useEffect(() => { fetchData() }, [fetchData])\n\n const handleCreateToken = async () => {\n if (!tokenName.trim()) return\n setIsCreating(true)\n try {\n const result = await runMutationWithContext(\n () => apiCallOrThrow<{ id: string; token: string; prefix: string; name: string }>(\n '/api/sso/scim/tokens',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ ssoConfigId: configId, name: tokenName.trim() }),\n },\n { errorMessage: t('sso.admin.scim.error.createFailed', 'Failed to create SCIM token') },\n ),\n { ssoConfigId: configId, name: tokenName.trim() },\n )\n if (result.result) {\n setNewlyCreatedToken(result.result.token)\n setShowCreateForm(false)\n setTokenName('')\n fetchData()\n onProvisioningChange()\n }\n } catch {\n // handled by apiCallOrThrow\n } finally {\n setIsCreating(false)\n }\n }\n\n const handleRevokeToken = async (tokenId: string) => {\n const confirmed = await confirm({\n title: t('sso.admin.scim.revoke.title', 'Revoke Token'),\n text: t('sso.admin.scim.revoke.confirm', 'Are you sure? This token will no longer authenticate SCIM requests.'),\n confirmText: t('sso.admin.scim.revoke.action', 'Revoke'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(`/api/sso/scim/tokens/${tokenId}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.scim.error.revokeFailed', 'Failed to revoke token'),\n }),\n { tokenId },\n )\n flash(t('sso.admin.scim.revoked', 'Token revoked'), 'success')\n fetchData()\n onProvisioningChange()\n } catch {\n // handled\n }\n }\n\n const handleCopyEndpoint = () => {\n const url = `${window.location.origin}/api/sso/scim/v2`\n navigator.clipboard.writeText(url).then(() => {\n flash(t('sso.admin.scim.endpointCopied', 'SCIM endpoint URL copied'), 'success')\n })\n }\n\n const handleCopyToken = () => {\n if (!newlyCreatedToken) return\n navigator.clipboard.writeText(newlyCreatedToken).then(() => {\n flash(t('sso.admin.scim.tokenCopied', 'Token copied to clipboard'), 'success')\n })\n }\n\n if (isLoading) return <LoadingMessage label={t('common.loading', 'Loading...')} />\n\n return (\n <div className=\"space-y-6\">\n {/* Google provider banner \u2014 SCIM not supported */}\n {isGoogleProvider && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-4\">\n <p className=\"text-sm text-blue-900\">\n {t('sso.admin.scim.googleNotSupported', 'Google Workspace does not support SCIM provisioning. Users are provisioned via Just-In-Time (JIT) on first login.')}\n </p>\n </div>\n )}\n\n {/* JIT active banner */}\n {!isGoogleProvider && jitEnabled && (\n <div className=\"rounded-lg border border-amber-200 bg-amber-50 p-4\">\n <p className=\"text-sm text-amber-900\">\n {t('sso.admin.scim.jitActiveWarning', 'SCIM provisioning is unavailable while JIT provisioning is enabled. Disable JIT in the General tab to configure SCIM.')}\n </p>\n </div>\n )}\n\n {isGoogleProvider ? null : <>\n {/* SCIM Endpoint URL */}\n <div>\n <h3 className=\"text-sm font-medium mb-2\">{t('sso.admin.scim.endpointUrl', 'SCIM Endpoint URL')}</h3>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 rounded-md border bg-muted px-3 py-2 text-sm font-mono\">\n {typeof window !== 'undefined' ? `${window.location.origin}/api/sso/scim/v2` : '/api/sso/scim/v2'}\n </code>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCopyEndpoint}>\n {t('common.copy', 'Copy')}\n </Button>\n </div>\n </div>\n\n {/* Newly created token banner */}\n {newlyCreatedToken && (\n <div className=\"rounded-lg border border-amber-200 bg-amber-50 p-4\">\n <p className=\"text-sm font-medium text-amber-900 mb-2\">\n {t('sso.admin.scim.tokenCreated', 'Your SCIM token has been created. Copy it now \u2014 it will not be shown again.')}\n </p>\n <div className=\"flex items-center gap-2 mb-2\">\n <code className=\"flex-1 rounded-md border bg-white px-3 py-2 text-xs font-mono break-all\">\n {newlyCreatedToken}\n </code>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCopyToken}>\n {t('common.copy', 'Copy')}\n </Button>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setNewlyCreatedToken(null)}>\n {t('common.dismiss', 'Dismiss')}\n </Button>\n </div>\n )}\n\n {/* Token management */}\n <div>\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"text-sm font-medium\">{t('sso.admin.scim.tokens', 'Bearer Tokens')}</h3>\n {!showCreateForm && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowCreateForm(true)} disabled={jitEnabled}>\n {t('sso.admin.scim.generateToken', 'Generate Token')}\n </Button>\n )}\n </div>\n\n {showCreateForm && (\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded-md border px-3 py-2 text-sm\"\n placeholder={t('sso.admin.scim.tokenNamePlaceholder', 'Token name (e.g., Entra ID Production)')}\n value={tokenName}\n onChange={(e) => setTokenName(e.target.value)}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleCreateToken() } }}\n autoFocus\n />\n <Button size=\"sm\" onClick={handleCreateToken} disabled={isCreating || !tokenName.trim()}>\n {isCreating ? t('common.creating', 'Creating...') : t('common.create', 'Create')}\n </Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => { setShowCreateForm(false); setTokenName('') }}>\n {t('common.cancel', 'Cancel')}\n </Button>\n </div>\n )}\n\n {tokens.length === 0 ? (\n <div className=\"text-center py-8 border rounded-md\">\n <p className=\"text-sm text-muted-foreground\">\n {t('sso.admin.scim.noTokens', 'SCIM provisioning is not configured. Generate a bearer token to enable your identity provider to sync users automatically.')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {tokens.map((token) => (\n <div key={token.id} className=\"flex items-center justify-between p-3 border rounded-md\">\n <div className=\"flex items-center gap-3\">\n <div>\n <span className=\"text-sm font-medium\">{token.name}</span>\n <span className=\"text-xs text-muted-foreground ml-2 font-mono\">{token.tokenPrefix}...</span>\n </div>\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n token.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-500'\n }`}>\n {token.isActive ? t('sso.admin.scim.tokenActive', 'Active') : t('sso.admin.scim.tokenRevoked', 'Revoked')}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {new Date(token.createdAt).toLocaleDateString()}\n </span>\n {token.isActive && (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => handleRevokeToken(token.id)} className=\"text-destructive\">\n {t('sso.admin.scim.revoke.action', 'Revoke')}\n </Button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Provisioning log */}\n {logs.length > 0 && (\n <div>\n <h3 className=\"text-sm font-medium mb-3\">{t('sso.admin.scim.recentActivity', 'Recent Provisioning Activity')}</h3>\n <div className=\"border rounded-md overflow-hidden\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/50\">\n <tr>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.time', 'Time')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.operation', 'Operation')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.resource', 'Resource')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.status', 'Status')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.error', 'Error')}</th>\n </tr>\n </thead>\n <tbody>\n {logs.map((log) => (\n <tr key={log.id} className=\"border-t\">\n <td className=\"px-3 py-2 text-xs text-muted-foreground whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleString()}\n </td>\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium bg-muted\">\n {log.operation}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs\">{log.resourceType}</td>\n <td className=\"px-3 py-2\">\n <span className={`text-xs font-medium ${log.responseStatus < 300 ? 'text-green-700' : 'text-red-600'}`}>\n {log.responseStatus}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-muted-foreground truncate max-w-[200px]\">\n {log.errorMessage || '\u2014'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n )}\n\n </>}\n\n {ConfirmDialogElement}\n </div>\n )\n}\n\nfunction SsoActivityTab({ configId }: { configId: string }) {\n const t = useT()\n const [identities, setIdentities] = React.useState<SsoIdentityRow[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n\n React.useEffect(() => {\n const load = async () => {\n setIsLoading(true)\n // For now, just show a placeholder \u2014 the identities list API is an M3 deliverable\n setIdentities([])\n setIsLoading(false)\n }\n load()\n }, [configId])\n\n if (isLoading) return <LoadingMessage label={t('common.loading', 'Loading...')} />\n\n if (identities.length === 0) {\n return (\n <div className=\"text-center py-8\">\n <p className=\"text-sm text-muted-foreground\">\n {t('sso.admin.activity.empty', 'No SSO login activity yet. Activity will appear here once users start logging in via SSO.')}\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n {identities.map((identity) => (\n <div key={identity.id} className=\"flex items-center justify-between p-3 border rounded-md\">\n <div>\n <span className=\"text-sm font-medium\">{identity.idpEmail}</span>\n {identity.idpName && <span className=\"text-sm text-muted-foreground ml-2\">({identity.idpName})</span>}\n </div>\n <span className=\"text-xs text-muted-foreground\">\n {identity.lastLoginAt ? new Date(identity.lastLoginAt).toLocaleString() : '\u2014'}\n </span>\n </div>\n ))}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AA8QwC,SA+iBP,UA/iBO,KA8B1B,YA9B0B;AA5QxC,OAAO,WAAW;AAClB,SAAS,WAAW,WAAW,uBAAuB;AACtD,SAAS,MAAM,gBAAgB;AAC/B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,SAAS,sBAAsB;AACxC,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,YAAY;AACrB,SAAS,0BAA0B;AAkCpB,SAAR,sBAAuC;AAC5C,QAAM,SAAS,UAAU;AACzB,QAAM,WAAY,QAAQ,QAAQ,MAAM,QAAQ,OAAO,IAAI,IACvD,OAAO,KAAK,CAAC,IACZ,MAAM,QAAQ,QAAQ,EAAE,IAAI,OAAO,GAAG,CAAC,IAAI,QAAQ;AACxD,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,gBAAgB;AACrC,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAiC,IAAI;AACvE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAc,SAAS;AAC/D,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,MAAM,SAAS,cAAc,IAAI,SAAS,MAAM,GAAG;AAC3G,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAwB,IAAI;AAChF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAE5D,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAA4C;AAAA,IACrF,WAAW,cAAc,YAAY,SAAS;AAAA,EAChD,CAAC;AACD,QAAM,yBAAyB,MAAM;AAAA,IACnC,OAAW,WAA6B,oBAA0D;AAChG,aAAO,YAAY;AAAA,QACjB;AAAA,QACA;AAAA,QACA,SAAS,EAAE,UAAU,kBAAkB;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,IACA,CAAC,UAAU,mBAAmB,WAAW;AAAA,EAC3C;AAGA,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,EAAE;AACzC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,EAAE;AAC/D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,KAAK;AAClE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,IAAI;AACvD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,IAAI;AACjE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AAGpD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AAEvD,QAAM,cAAc,MAAM,YAAY,YAAY;AAChD,iBAAa,IAAI;AACjB,UAAM,OAAO,MAAM,QAAyB,mBAAmB,QAAQ,EAAE;AACzE,QAAI,KAAK,MAAM,KAAK,QAAQ;AAC1B,YAAM,IAAI,KAAK;AACf,gBAAU,CAAC;AACX,cAAQ,EAAE,QAAQ,EAAE;AACpB,gBAAU,EAAE,UAAU,EAAE;AACxB,kBAAY,EAAE,YAAY,EAAE;AAC5B,oBAAc,EAAE,UAAU;AAC1B,yBAAmB,EAAE,eAAe;AACpC,eAAS,IAAI;AAAA,IACf,OAAO;AACL,eAAS,EAAE,8BAA8B,kCAAkC,CAAC;AAAA,IAC9E;AACA,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,UAAU,CAAC,CAAC;AAEhB,QAAM,UAAU,MAAM;AAAE,gBAAY;AAAA,EAAE,GAAG,CAAC,WAAW,CAAC;AAEtD,QAAM,aAAa,YAAY;AAC7B,gBAAY,IAAI;AAChB,QAAI;AACF,YAAM,UAAmC,EAAE,MAAM,QAAQ,UAAU,YAAY,gBAAgB;AAC/F,UAAI,gBAAiB,SAAQ,eAAe;AAE5C,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,mBAAmB,QAAQ;AAAA,UAC3B;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,cAAc,EAAE,8BAA8B,kCAAkC,EAAE;AAAA,QACtF;AAAA,QACA;AAAA,MACF;AACA,YAAM,EAAE,mBAAmB,yBAAyB,GAAG,SAAS;AAChE,yBAAmB,EAAE;AACrB,yBAAmB,KAAK;AACxB,kBAAY;AAAA,IACd,QAAQ;AAAA,IAER,UAAE;AACA,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,yBAAyB,YAAY;AACzC,QAAI,CAAC,OAAQ;AACb,uBAAmB,IAAI;AACvB,oBAAgB,IAAI;AACpB,QAAI;AACF,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,mBAAmB,QAAQ;AAAA,UAC3B;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC,OAAO,SAAS,CAAC;AAAA,UACnD;AAAA,UACA,EAAE,cAAc,EAAE,oCAAoC,oCAAoC,EAAE;AAAA,QAC9F;AAAA,QACA,EAAE,QAAQ,CAAC,OAAO,SAAS;AAAA,MAC7B;AACA;AAAA,QACE,OAAO,WACH,EAAE,yBAAyB,+BAA+B,IAC1D,EAAE,uBAAuB,6BAA6B;AAAA,QAC1D;AAAA,MACF;AACA,8BAAwB,KAAK;AAC7B,kBAAY;AAAA,IACd,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,cAAc,QAAQ,YAAY,EAAE,SAAS,oBAAoB;AACvE,UAAI,aAAa;AACf,2BAAmB,EAAE,0CAA0C,yDAAyD,CAAC;AACzH,qBAAa,SAAS;AAAA,MACxB,OAAO;AACL,2BAAmB,OAAO;AAAA,MAC5B;AAAA,IACF,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,uBAAuB,YAAY;AACvC,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,MAAM;AAAA,UACJ,mBAAmB,QAAQ;AAAA,UAC3B,EAAE,QAAQ,OAAO;AAAA,UACjB,EAAE,cAAc,EAAE,8BAA8B,wBAAwB,EAAE;AAAA,QAC5E;AAAA,MACF;AACA,UAAI,KAAK,QAAQ,IAAI;AACnB,cAAM,EAAE,0BAA0B,iDAA4C,GAAG,SAAS;AAAA,MAC5F,OAAO;AACL,cAAM,KAAK,QAAQ,SAAS,EAAE,yBAAyB,kBAAkB,GAAG,OAAO;AAAA,MACrF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,eAAe,YAAY;AAC/B,QAAI,CAAC,OAAQ;AACb,QAAI,OAAO,UAAU;AACnB,YAAM,EAAE,gCAAgC,sEAAiE,GAAG,OAAO;AACnH;AAAA,IACF;AACA,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,0BAA0B,0BAA0B;AAAA,MAC7D,MAAM,EAAE,4BAA4B,uDAAuD;AAAA,MAC3F,aAAa,EAAE,iBAAiB,QAAQ;AAAA,MACxC,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAEhB,UAAM;AAAA,MACJ,MAAM,eAAe,mBAAmB,QAAQ,IAAI,EAAE,QAAQ,SAAS,GAAG;AAAA,QACxE,cAAc,EAAE,gCAAgC,oCAAoC;AAAA,MACtF,CAAC;AAAA,MACD,EAAE,IAAI,SAAS;AAAA,IACjB;AACA,UAAM,EAAE,4BAA4B,2BAA2B,GAAG,SAAS;AAC3E,WAAO,KAAK,cAAc;AAAA,EAC5B;AAEA,QAAM,kBAAkB,YAAY;AAClC,UAAM,aAAa,YAAY,KAAK,EAAE,YAAY;AAClD,QAAI,CAAC,WAAY;AAEjB,UAAM,cAAc;AACpB,QAAI,CAAC,YAAY,KAAK,UAAU,KAAK,CAAC,WAAW,SAAS,GAAG,GAAG;AAC9D,qBAAe,EAAE,mCAAmC,uBAAuB,CAAC;AAC5E;AAAA,IACF;AAEA,QAAI;AACF,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,mBAAmB,QAAQ;AAAA,UAC3B;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,WAAW,CAAC;AAAA,UAC7C;AAAA,UACA,EAAE,cAAc,EAAE,mCAAmC,sBAAsB,EAAE;AAAA,QAC/E;AAAA,QACA,EAAE,QAAQ,WAAW;AAAA,MACvB;AACA,qBAAe,EAAE;AACjB,qBAAe,EAAE;AACjB,kBAAY;AAAA,IACd,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,qBAAqB,OAAO,WAAmB;AACnD,QAAI;AACF,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,mBAAmB,QAAQ,mBAAmB,mBAAmB,MAAM,CAAC;AAAA,UACxE,EAAE,QAAQ,SAAS;AAAA,UACnB,EAAE,cAAc,EAAE,sCAAsC,yBAAyB,EAAE;AAAA,QACrF;AAAA,QACA,EAAE,OAAO;AAAA,MACX;AACA,kBAAY;AAAA,IACd,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,UAAW,QAAO,oBAAC,QAAK,8BAAC,YAAS,8BAAC,kBAAe,OAAO,EAAE,kBAAkB,YAAY,GAAG,GAAE,GAAW;AAC7G,MAAI,SAAS,CAAC,OAAQ,QAAO,oBAAC,QAAK,8BAAC,YAAS,8BAAC,gBAAa,OAAO,SAAS,EAAE,mBAAmB,WAAW,GAAG,GAAE,GAAW;AAE3H,QAAM,OAAqC;AAAA,IACzC,EAAE,IAAI,WAAW,OAAO,EAAE,yBAAyB,SAAS,EAAE;AAAA,IAC9D,EAAE,IAAI,WAAW,OAAO,EAAE,yBAAyB,SAAS,EAAE;AAAA,IAC9D,EAAE,IAAI,SAAS,OAAO,EAAE,uBAAuB,eAAe,EAAE;AAAA,IAChE,EAAE,IAAI,QAAQ,OAAO,EAAE,sBAAsB,cAAc,EAAE;AAAA,IAC7D,EAAE,IAAI,YAAY,OAAO,EAAE,0BAA0B,UAAU,EAAE;AAAA,EACnE;AAEA,QAAM,cACJ,oBAAC,UAAK,WAAW,uEAAuE,OAAO,WAAW,+BAA+B,2BAA2B,IACjK,iBAAO,WAAW,EAAE,2BAA2B,QAAQ,IAAI,EAAE,6BAA6B,UAAU,GACvG;AAGF,SACE,oBAAC,QACC,+BAAC,YACC;AAAA,yBAAC,SAAI,WAAU,iCAEZ;AAAA,8BAAwB,CAAC,OAAO,YAC/B,qBAAC,SAAI,WAAU,oDACb;AAAA,4BAAC,OAAE,WAAU,0CACV,YAAE,4BAA4B,6EAA6E,GAC9G;AAAA,QACC,mBACC,oBAAC,OAAE,WAAU,iCAAiC,2BAAgB;AAAA,QAEhE,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,UAAO,MAAK,MAAK,SAAS,wBAAwB,UAAU,cAC1D,yBACG,EAAE,qBAAqB,eAAe,IACtC,EAAE,gCAAgC,cAAc,GACtD;AAAA,UACA,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,wBAAwB,KAAK,GAC7E,YAAE,2BAA2B,SAAS,GACzC;AAAA,WACF;AAAA,SACF;AAAA,MAGF;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAS;AAAA,UACT,WAAW,EAAE,+BAA+B,aAAa;AAAA,UACzD,OAAO,OAAO,QAAQ,OAAO,UAAU,EAAE,0BAA0B,mBAAmB;AAAA,UACtF;AAAA,UACA,gBACE,qBAAC,SAAI,WAAU,2BACb;AAAA,gCAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,sBACxD,YAAE,yBAAyB,kBAAkB,GAChD;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,OAAO,WAAW,YAAY;AAAA,gBACvC,MAAK;AAAA,gBACL,SAAS;AAAA,gBAER,iBAAO,WACJ,EAAE,+BAA+B,YAAY,IAC7C,EAAE,6BAA6B,UAAU;AAAA;AAAA,YAC/C;AAAA,YACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,cAAc,WAAU,oBAChF,YAAE,iBAAiB,QAAQ,GAC9B;AAAA,aACF;AAAA;AAAA,MAEJ;AAAA,MAGA,oBAAC,SAAI,WAAU,uBACZ,eAAK,IAAI,CAAC,QACT;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,WAAW,iEACT,cAAc,IAAI,KACd,mCACA,0CACN;AAAA,UACA,SAAS,MAAM,aAAa,IAAI,EAAE;AAAA,UAEjC,cAAI;AAAA;AAAA,QAXA,IAAI;AAAA,MAYX,CACD,GACH;AAAA,MAGC,cAAc,aACb,qBAAC,SAAI,WAAU,iCACb;AAAA,4BAAC,QAAG,WAAU,8DACX,YAAE,kCAAkC,eAAe,GACtD;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SACC;AAAA,gCAAC,WAAM,WAAU,kCAAkC,YAAE,wBAAwB,oBAAoB,GAAE;AAAA,YACnG;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA;AAAA,YACzC;AAAA,aACF;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,WAAM,WAAU,kCAAkC,YAAE,4BAA4B,UAAU,GAAE;AAAA,YAC7F,oBAAC,WAAM,MAAK,QAAO,WAAU,uDAAsD,OAAO,OAAO,SAAS,YAAY,GAAG,UAAQ,MAAC;AAAA,aACpI;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,WAAM,WAAU,kCAAkC,YAAE,0BAA0B,YAAY,GAAE;AAAA,YAC7F;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK;AAAA;AAAA,YAC3C;AAAA,aACF;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,WAAM,WAAU,kCAAkC,YAAE,4BAA4B,WAAW,GAAE;AAAA,YAC9F;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA;AAAA,YAC7C;AAAA,aACF;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,WAAM,WAAU,kCAAkC,YAAE,gCAAgC,eAAe,GAAE;AAAA,YACrG,OAAO,mBAAmB,CAAC,kBAC1B,qBAAC,SAAI,WAAU,2BACb;AAAA,kCAAC,UAAK,WAAU,iCAAiC,YAAE,6BAA6B,6BAA6B,GAAE;AAAA,cAC/G,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,mBAAmB,IAAI,GACrF,YAAE,gCAAgC,QAAQ,GAC7C;AAAA,eACF,IAEA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,KAAK;AAAA,gBAClD,aAAa,OAAO,kBAChB,EAAE,qCAAqC,sCAAsC,IAC7E,EAAE,kCAAkC,qBAAqB;AAAA;AAAA,YAC/D;AAAA,aAEJ;AAAA,UACA,qBAAC,SAAI,WAAU,kBACb;AAAA,iCAAC,WAAM,WAAU,2BACf;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,OAAO;AAAA,kBAC/C,UAAU,OAAO;AAAA,kBACjB,WAAU;AAAA;AAAA,cACZ;AAAA,cACA,qBAAC,SACC;AAAA,oCAAC,UAAK,WAAU,uBAAuB,YAAE,8BAA8B,2BAA2B,GAAE;AAAA,gBACpG,oBAAC,UAAK,WAAU,sCACb,iBAAO,sBACJ,EAAE,qCAAqC,qFAAgF,IACvH,EAAE,kCAAkC,uDAAuD,GACjG;AAAA,iBACF;AAAA,eACF;AAAA,YACA,qBAAC,WAAM,WAAU,2BACf;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,OAAO;AAAA,kBACpD,WAAU;AAAA;AAAA,cACZ;AAAA,cACA,qBAAC,SACC;AAAA,oCAAC,UAAK,WAAU,uBAAuB,YAAE,mCAAmC,oBAAoB,GAAE;AAAA,gBAClG,oBAAC,UAAK,WAAU,sCACb,YAAE,uCAAuC,6DAA6D,GACzG;AAAA,iBACF;AAAA,eACF;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,QACb,8BAAC,UAAO,SAAS,YAAY,UAAU,UACpC,qBAAW,EAAE,iBAAiB,WAAW,IAAI,EAAE,eAAe,MAAM,GACvE,GACF;AAAA,WACF;AAAA,SACF;AAAA,MAGD,cAAc,aACb,qBAAC,SAAI,WAAU,iCACb;AAAA,4BAAC,QAAG,WAAU,8DACX,YAAE,oCAAoC,iBAAiB,GAC1D;AAAA,QACA,oBAAC,OAAE,WAAU,sCACV,YAAE,wCAAwC,4FAA4F,GACzI;AAAA,QACA,qBAAC,SAAI,WAAU,gCACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,aAAa,EAAE,wCAAwC,aAAa;AAAA,cACpE,OAAO;AAAA,cACP,UAAU,CAAC,MAAM;AAAE,+BAAe,EAAE,OAAO,KAAK;AAAG,+BAAe,EAAE;AAAA,cAAE;AAAA,cACtE,WAAW,CAAC,MAAM;AAAE,oBAAI,EAAE,QAAQ,SAAS;AAAE,oBAAE,eAAe;AAAG,kCAAgB;AAAA,gBAAE;AAAA,cAAE;AAAA;AAAA,UACvF;AAAA,UACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,iBAC9C,YAAE,cAAc,KAAK,GACxB;AAAA,WACF;AAAA,QACC,eAAe,oBAAC,OAAE,WAAU,iCAAiC,uBAAY;AAAA,QACzE,OAAO,eAAe,SAAS,IAC9B,oBAAC,SAAI,WAAU,aACZ,iBAAO,eAAe,IAAI,CAAC,WAC1B,qBAAC,SAAiB,WAAU,2DAC1B;AAAA,8BAAC,UAAK,WAAU,qBAAqB,kBAAO;AAAA,UAC5C,oBAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,mBAAmB,MAAM,GACrF,YAAE,iBAAiB,QAAQ,GAC9B;AAAA,aAJQ,MAKV,CACD,GACH,IAEA,oBAAC,OAAE,WAAU,kDACV,YAAE,2BAA2B,uEAAuE,GACvG;AAAA,SAEJ;AAAA,MAGD,cAAc,WAAW,UACxB,oBAAC,SAAI,WAAU,iCACb;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,iBAAiB,OAAO,mBAAmB,CAAC;AAAA,UAC5C,SAAS;AAAA,UACT;AAAA;AAAA,MACF,GACF;AAAA,MAGD,cAAc,UACb,oBAAC,SAAI,WAAU,iCACb,8BAAC,uBAAoB,UAAoB,YAAY,OAAO,YAAY,QAAQ,OAAO,UAAU,QAAW,sBAAsB,aAAa,wBAAgD,GACjM;AAAA,MAGD,cAAc,cACb,oBAAC,SAAI,WAAU,iCACb,8BAAC,kBAAe,UAAoB,GACtC;AAAA,OAEJ;AAAA,IACC;AAAA,KACH,GACF;AAEJ;AAEA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAiC,eAAe;AACtF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,EAAE;AACzD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,EAAE;AAC7D,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAA6C,CAAC,CAAC;AAC3F,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AAErD,QAAM,UAAU,MAAM;AACpB,gBAAY,eAAe;AAAA,EAC7B,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,UAAU,MAAM;AACpB,UAAM,YAAY,YAAY;AAC5B,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,MAC5B;AACA,UAAI,KAAK,MAAM,MAAM,QAAQ,KAAK,QAAQ,KAAK,GAAG;AAChD,cAAM,UAAU,KAAK,OAAO,MACzB,IAAI,CAAC,SAAS;AACb,gBAAM,OAAO,OAAO,MAAM,SAAS,WAAW,KAAK,KAAK,KAAK,IAAI;AACjE,cAAI,CAAC,QAAQ,SAAS,aAAc,QAAO;AAC3C,iBAAO,EAAE,OAAO,MAAM,OAAO,KAAK;AAAA,QACpC,CAAC,EACA,OAAO,CAAC,QAAiD,CAAC,CAAC,GAAG;AACjE,uBAAe,OAAO;AACtB,YAAI,QAAQ,SAAS,KAAK,CAAC,gBAAgB;AACzC,4BAAkB,QAAQ,CAAC,EAAE,KAAK;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AACA,cAAU;AAAA,EACZ,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,MAAM;AACtB,UAAM,UAAU,aAAa,KAAK;AAClC,QAAI,CAAC,SAAS;AACZ,oBAAc,EAAE,sCAAsC,2BAA2B,CAAC;AAClF;AAAA,IACF;AACA,QAAI,CAAC,gBAAgB;AACnB,oBAAc,EAAE,wCAAwC,qBAAqB,CAAC;AAC9E;AAAA,IACF;AACA,QAAI,SAAS,OAAO,GAAG;AACrB,oBAAc,EAAE,mCAAmC,iCAAiC,CAAC;AACrF;AAAA,IACF;AACA,gBAAY,EAAE,GAAG,UAAU,CAAC,OAAO,GAAG,eAAe,CAAC;AACtD,oBAAgB,EAAE;AAClB,kBAAc,EAAE;AAAA,EAClB;AAEA,QAAM,eAAe,CAAC,YAAoB;AACxC,UAAM,UAAU,EAAE,GAAG,SAAS;AAC9B,WAAO,QAAQ,OAAO;AACtB,gBAAY,OAAO;AAAA,EACrB;AAEA,QAAM,aAAa,YAAY;AAC7B,gBAAY,IAAI;AAChB,QAAI;AACF,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,mBAAmB,QAAQ;AAAA,UAC3B;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,iBAAiB,SAAS,CAAC;AAAA,UACpD;AAAA,UACA,EAAE,cAAc,EAAE,oCAAoC,8BAA8B,EAAE;AAAA,QACxF;AAAA,QACA,EAAE,iBAAiB,SAAS;AAAA,MAC9B;AACA,YAAM,EAAE,yBAAyB,qBAAqB,GAAG,SAAS;AAClE,cAAQ;AAAA,IACV,QAAQ;AAAA,IAER,UAAE;AACA,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,iBAAiB,OAAO,QAAQ,QAAQ;AAE9C,SACE,qBAAC,SACC;AAAA,wBAAC,OAAE,WAAU,sCACV,YAAE,+BAA+B,yLAAoL,GACxN;AAAA,IACA,qBAAC,SAAI,WAAU,6BACb;AAAA,2BAAC,SAAI,WAAU,UACb;AAAA,4BAAC,WAAM,WAAU,kCAAkC,YAAE,2BAA2B,eAAe,GAAE;AAAA,QACjG;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,aAAa,EAAE,sCAAsC,wBAAwB;AAAA,YAC7E,OAAO;AAAA,YACP,UAAU,CAAC,MAAM;AAAE,8BAAgB,EAAE,OAAO,KAAK;AAAG,4BAAc,EAAE;AAAA,YAAE;AAAA,YACtE,WAAW,CAAC,MAAM;AAAE,kBAAI,EAAE,QAAQ,SAAS;AAAE,kBAAE,eAAe;AAAG,0BAAU;AAAA,cAAE;AAAA,YAAE;AAAA;AAAA,QACjF;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,UACb;AAAA,4BAAC,WAAM,WAAU,kCAAkC,YAAE,6BAA6B,YAAY,GAAE;AAAA,QAChG;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,kBAAkB,EAAE,OAAO,KAAK;AAAA,YAEhD,sBAAY,IAAI,CAAC,QAChB,oBAAC,YAAuB,OAAO,IAAI,OAAQ,cAAI,SAAlC,IAAI,KAAoC,CACtD;AAAA;AAAA,QACH;AAAA,SACF;AAAA,MACA,oBAAC,UAAO,SAAQ,WAAU,SAAS,WAChC,YAAE,cAAc,KAAK,GACxB;AAAA,OACF;AAAA,IACC,cAAc,oBAAC,OAAE,WAAU,iCAAiC,sBAAW;AAAA,IAEvE,eAAe,SAAS,IACvB,oBAAC,SAAI,WAAU,kBACZ,yBAAe,IAAI,CAAC,CAAC,SAAS,SAAS,MACtC,qBAAC,SAAkB,WAAU,2DAC3B;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,4BAAC,UAAK,WAAU,qBAAqB,mBAAQ;AAAA,QAC7C,oBAAC,UAAK,WAAU,iCAAgC,oBAAM;AAAA,QACtD,oBAAC,UAAK,WAAU,uBAAuB,qBAAU;AAAA,SACnD;AAAA,MACA,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,aAAa,OAAO,GAClE,YAAE,iBAAiB,QAAQ,GAC9B;AAAA,SARQ,OASV,CACD,GACH,IAEA,oBAAC,OAAE,WAAU,uDACV,YAAE,yBAAyB,gGAAgG,GAC9H;AAAA,IAGF,oBAAC,UAAO,SAAS,YAAY,UAAU,UACpC,qBAAW,EAAE,iBAAiB,WAAW,IAAI,EAAE,eAAe,MAAM,GACvE;AAAA,KACF;AAEJ;AAsBA,SAAS,oBAAoB,EAAE,UAAU,YAAY,QAAQ,sBAAsB,uBAAuB,GAAoN;AAC5T,QAAM,mBAAmB,QAAQ,SAAS,qBAAqB,MAAM;AACrE,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC7D,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAuB,CAAC,CAAC;AACvD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,EAAE;AACnD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAwB,IAAI;AAEpF,QAAM,YAAY,MAAM,YAAY,YAAY;AAC9C,iBAAa,IAAI;AACjB,UAAM,CAAC,YAAY,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC/C,QAAmC,oCAAoC,QAAQ,EAAE;AAAA,MACjF,QAAiC,kCAAkC,QAAQ,EAAE;AAAA,IAC/E,CAAC;AACD,QAAI,WAAW,MAAM,WAAW,OAAQ,WAAU,WAAW,OAAO,KAAK;AACzE,QAAI,SAAS,MAAM,SAAS,OAAQ,SAAQ,SAAS,OAAO,KAAK;AACjE,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,UAAU,MAAM;AAAE,cAAU;AAAA,EAAE,GAAG,CAAC,SAAS,CAAC;AAElD,QAAM,oBAAoB,YAAY;AACpC,QAAI,CAAC,UAAU,KAAK,EAAG;AACvB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,aAAa,UAAU,MAAM,UAAU,KAAK,EAAE,CAAC;AAAA,UACxE;AAAA,UACA,EAAE,cAAc,EAAE,qCAAqC,6BAA6B,EAAE;AAAA,QACxF;AAAA,QACA,EAAE,aAAa,UAAU,MAAM,UAAU,KAAK,EAAE;AAAA,MAClD;AACA,UAAI,OAAO,QAAQ;AACjB,6BAAqB,OAAO,OAAO,KAAK;AACxC,0BAAkB,KAAK;AACvB,qBAAa,EAAE;AACf,kBAAU;AACV,6BAAqB;AAAA,MACvB;AAAA,IACF,QAAQ;AAAA,IAER,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,oBAAoB,OAAO,YAAoB;AACnD,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,+BAA+B,cAAc;AAAA,MACtD,MAAM,EAAE,iCAAiC,qEAAqE;AAAA,MAC9G,aAAa,EAAE,gCAAgC,QAAQ;AAAA,MACvD,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAEhB,QAAI;AACF,YAAM;AAAA,QACJ,MAAM,eAAe,wBAAwB,OAAO,IAAI,EAAE,QAAQ,SAAS,GAAG;AAAA,UAC5E,cAAc,EAAE,qCAAqC,wBAAwB;AAAA,QAC/E,CAAC;AAAA,QACD,EAAE,QAAQ;AAAA,MACZ;AACA,YAAM,EAAE,0BAA0B,eAAe,GAAG,SAAS;AAC7D,gBAAU;AACV,2BAAqB;AAAA,IACvB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM;AAC/B,UAAM,MAAM,GAAG,OAAO,SAAS,MAAM;AACrC,cAAU,UAAU,UAAU,GAAG,EAAE,KAAK,MAAM;AAC5C,YAAM,EAAE,iCAAiC,0BAA0B,GAAG,SAAS;AAAA,IACjF,CAAC;AAAA,EACH;AAEA,QAAM,kBAAkB,MAAM;AAC5B,QAAI,CAAC,kBAAmB;AACxB,cAAU,UAAU,UAAU,iBAAiB,EAAE,KAAK,MAAM;AAC1D,YAAM,EAAE,8BAA8B,2BAA2B,GAAG,SAAS;AAAA,IAC/E,CAAC;AAAA,EACH;AAEA,MAAI,UAAW,QAAO,oBAAC,kBAAe,OAAO,EAAE,kBAAkB,YAAY,GAAG;AAEhF,SACE,qBAAC,SAAI,WAAU,aAEZ;AAAA,wBACC,oBAAC,SAAI,WAAU,oDACb,8BAAC,OAAE,WAAU,yBACV,YAAE,qCAAqC,mHAAmH,GAC7J,GACF;AAAA,IAID,CAAC,oBAAoB,cACpB,oBAAC,SAAI,WAAU,sDACb,8BAAC,OAAE,WAAU,0BACV,YAAE,mCAAmC,uHAAuH,GAC/J,GACF;AAAA,IAGD,mBAAmB,OAAO,iCAE3B;AAAA,2BAAC,SACC;AAAA,4BAAC,QAAG,WAAU,4BAA4B,YAAE,8BAA8B,mBAAmB,GAAE;AAAA,QAC/F,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,UAAK,WAAU,iEACb,iBAAO,WAAW,cAAc,GAAG,OAAO,SAAS,MAAM,qBAAqB,oBACjF;AAAA,UACA,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,oBAC1C,YAAE,eAAe,MAAM,GAC1B;AAAA,WACF;AAAA,SACF;AAAA,MAGC,qBACC,qBAAC,SAAI,WAAU,sDACb;AAAA,4BAAC,OAAE,WAAU,2CACV,YAAE,+BAA+B,kFAA6E,GACjH;AAAA,QACA,qBAAC,SAAI,WAAU,gCACb;AAAA,8BAAC,UAAK,WAAU,2EACb,6BACH;AAAA,UACA,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,iBAC1C,YAAE,eAAe,MAAM,GAC1B;AAAA,WACF;AAAA,QACA,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,qBAAqB,IAAI,GACvE,YAAE,kBAAkB,SAAS,GAChC;AAAA,SACF;AAAA,MAIF,qBAAC,SACC;AAAA,6BAAC,SAAI,WAAU,0CACb;AAAA,8BAAC,QAAG,WAAU,uBAAuB,YAAE,yBAAyB,eAAe,GAAE;AAAA,UAChF,CAAC,kBACA,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,kBAAkB,IAAI,GAAG,UAAU,YACnF,YAAE,gCAAgC,gBAAgB,GACrD;AAAA,WAEJ;AAAA,QAEC,kBACC,qBAAC,SAAI,WAAU,gCACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,aAAa,EAAE,uCAAuC,wCAAwC;AAAA,cAC9F,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,cAC5C,WAAW,CAAC,MAAM;AAAE,oBAAI,EAAE,QAAQ,SAAS;AAAE,oBAAE,eAAe;AAAG,oCAAkB;AAAA,gBAAE;AAAA,cAAE;AAAA,cACvF,WAAS;AAAA;AAAA,UACX;AAAA,UACA,oBAAC,UAAO,MAAK,MAAK,SAAS,mBAAmB,UAAU,cAAc,CAAC,UAAU,KAAK,GACnF,uBAAa,EAAE,mBAAmB,aAAa,IAAI,EAAE,iBAAiB,QAAQ,GACjF;AAAA,UACA,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM;AAAE,8BAAkB,KAAK;AAAG,yBAAa,EAAE;AAAA,UAAE,GAC3F,YAAE,iBAAiB,QAAQ,GAC9B;AAAA,WACF;AAAA,QAGD,OAAO,WAAW,IACjB,oBAAC,SAAI,WAAU,sCACb,8BAAC,OAAE,WAAU,iCACV,YAAE,2BAA2B,4HAA4H,GAC5J,GACF,IAEA,oBAAC,SAAI,WAAU,aACZ,iBAAO,IAAI,CAAC,UACX,qBAAC,SAAmB,WAAU,2DAC5B;AAAA,+BAAC,SAAI,WAAU,2BACb;AAAA,iCAAC,SACC;AAAA,kCAAC,UAAK,WAAU,uBAAuB,gBAAM,MAAK;AAAA,cAClD,qBAAC,UAAK,WAAU,gDAAgD;AAAA,sBAAM;AAAA,gBAAY;AAAA,iBAAG;AAAA,eACvF;AAAA,YACA,oBAAC,UAAK,WAAW,yEACf,MAAM,WAAW,+BAA+B,2BAClD,IACG,gBAAM,WAAW,EAAE,8BAA8B,QAAQ,IAAI,EAAE,+BAA+B,SAAS,GAC1G;AAAA,aACF;AAAA,UACA,qBAAC,SAAI,WAAU,2BACb;AAAA,gCAAC,UAAK,WAAU,iCACb,cAAI,KAAK,MAAM,SAAS,EAAE,mBAAmB,GAChD;AAAA,YACC,MAAM,YACL,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,kBAAkB,MAAM,EAAE,GAAG,WAAU,oBACrF,YAAE,gCAAgC,QAAQ,GAC7C;AAAA,aAEJ;AAAA,aArBQ,MAAM,EAsBhB,CACD,GACH;AAAA,SAEJ;AAAA,MAGC,KAAK,SAAS,KACb,qBAAC,SACC;AAAA,4BAAC,QAAG,WAAU,4BAA4B,YAAE,iCAAiC,8BAA8B,GAAE;AAAA,QAC7G,oBAAC,SAAI,WAAU,qCACb,+BAAC,WAAM,WAAU,kBACf;AAAA,8BAAC,WAAM,WAAU,eACf,+BAAC,QACC;AAAA,gCAAC,QAAG,WAAU,mCAAmC,YAAE,2BAA2B,MAAM,GAAE;AAAA,YACtF,oBAAC,QAAG,WAAU,mCAAmC,YAAE,gCAAgC,WAAW,GAAE;AAAA,YAChG,oBAAC,QAAG,WAAU,mCAAmC,YAAE,+BAA+B,UAAU,GAAE;AAAA,YAC9F,oBAAC,QAAG,WAAU,mCAAmC,YAAE,6BAA6B,QAAQ,GAAE;AAAA,YAC1F,oBAAC,QAAG,WAAU,mCAAmC,YAAE,4BAA4B,OAAO,GAAE;AAAA,aAC1F,GACF;AAAA,UACA,oBAAC,WACE,eAAK,IAAI,CAAC,QACT,qBAAC,QAAgB,WAAU,YACzB;AAAA,gCAAC,QAAG,WAAU,6DACX,cAAI,KAAK,IAAI,SAAS,EAAE,eAAe,GAC1C;AAAA,YACA,oBAAC,QAAG,WAAU,aACZ,8BAAC,UAAK,WAAU,+EACb,cAAI,WACP,GACF;AAAA,YACA,oBAAC,QAAG,WAAU,qBAAqB,cAAI,cAAa;AAAA,YACpD,oBAAC,QAAG,WAAU,aACZ,8BAAC,UAAK,WAAW,uBAAuB,IAAI,iBAAiB,MAAM,mBAAmB,cAAc,IACjG,cAAI,gBACP,GACF;AAAA,YACA,oBAAC,QAAG,WAAU,kEACX,cAAI,gBAAgB,UACvB;AAAA,eAjBO,IAAI,EAkBb,CACD,GACH;AAAA,WACF,GACF;AAAA,SACF;AAAA,OAGF;AAAA,IAEC;AAAA,KACH;AAEJ;AAEA,SAAS,eAAe,EAAE,SAAS,GAAyB;AAC1D,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAA2B,CAAC,CAAC;AACvE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AAErD,QAAM,UAAU,MAAM;AACpB,UAAM,OAAO,YAAY;AACvB,mBAAa,IAAI;AAEjB,oBAAc,CAAC,CAAC;AAChB,mBAAa,KAAK;AAAA,IACpB;AACA,SAAK;AAAA,EACP,GAAG,CAAC,QAAQ,CAAC;AAEb,MAAI,UAAW,QAAO,oBAAC,kBAAe,OAAO,EAAE,kBAAkB,YAAY,GAAG;AAEhF,MAAI,WAAW,WAAW,GAAG;AAC3B,WACE,oBAAC,SAAI,WAAU,oBACb,8BAAC,OAAE,WAAU,iCACV,YAAE,4BAA4B,2FAA2F,GAC5H,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAU,aACZ,qBAAW,IAAI,CAAC,aACf,qBAAC,SAAsB,WAAU,2DAC/B;AAAA,yBAAC,SACC;AAAA,0BAAC,UAAK,WAAU,uBAAuB,mBAAS,UAAS;AAAA,MACxD,SAAS,WAAW,qBAAC,UAAK,WAAU,sCAAqC;AAAA;AAAA,QAAE,SAAS;AAAA,QAAQ;AAAA,SAAC;AAAA,OAChG;AAAA,IACA,oBAAC,UAAK,WAAU,iCACb,mBAAS,cAAc,IAAI,KAAK,SAAS,WAAW,EAAE,eAAe,IAAI,UAC5E;AAAA,OAPQ,SAAS,EAQnB,CACD,GACH;AAEJ;",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useParams, useRouter, useSearchParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\ntype Tab = 'general' | 'domains' | 'roles' | 'scim' | 'activity'\n\ninterface SsoConfigDetail {\n id: string\n name: string | null\n tenantId: string | null\n organizationId: string\n protocol: string\n issuer: string | null\n clientId: string | null\n hasClientSecret: boolean\n allowedDomains: string[]\n jitEnabled: boolean\n autoLinkByEmail: boolean\n isActive: boolean\n ssoRequired: boolean\n appRoleMappings: Record<string, string>\n hasActiveScimTokens: boolean\n createdAt: string\n updatedAt: string\n}\n\ninterface SsoIdentityRow {\n id: string\n userId: string\n idpEmail: string\n idpName: string | null\n provisioningMethod: string\n lastLoginAt: string | null\n createdAt: string\n}\n\nexport default function SsoConfigDetailPage() {\n const params = useParams()\n const configId = (params?.slug && Array.isArray(params.slug))\n ? params.slug[2]\n : (Array.isArray(params?.id) ? params.id[0] : params?.id as string)\n const router = useRouter()\n const searchParams = useSearchParams()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [config, setConfig] = React.useState<SsoConfigDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [activeTab, setActiveTab] = React.useState<Tab>('general')\n const [showActivationBanner, setShowActivationBanner] = React.useState(searchParams?.get('created') === '1')\n const [activationError, setActivationError] = React.useState<string | null>(null)\n const [isActivating, setIsActivating] = React.useState(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: `sso-config:${configId ?? 'pending'}`,\n })\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { configId, retryLastMutation },\n })\n },\n [configId, retryLastMutation, runMutation],\n )\n\n // General tab form state\n const [name, setName] = React.useState('')\n const [issuer, setIssuer] = React.useState('')\n const [clientId, setClientId] = React.useState('')\n const [newClientSecret, setNewClientSecret] = React.useState('')\n const [showSecretField, setShowSecretField] = React.useState(false)\n const [jitEnabled, setJitEnabled] = React.useState(true)\n const [autoLinkByEmail, setAutoLinkByEmail] = React.useState(true)\n const [isSaving, setIsSaving] = React.useState(false)\n\n // Domains tab state\n const [domainInput, setDomainInput] = React.useState('')\n const [domainError, setDomainError] = React.useState('')\n\n const fetchConfig = React.useCallback(async () => {\n setIsLoading(true)\n const call = await apiCall<SsoConfigDetail>(`/api/sso/config/${configId}`)\n if (call.ok && call.result) {\n const c = call.result\n setConfig(c)\n setName(c.name ?? '')\n setIssuer(c.issuer ?? '')\n setClientId(c.clientId ?? '')\n setJitEnabled(c.jitEnabled)\n setAutoLinkByEmail(c.autoLinkByEmail)\n setError(null)\n } else {\n setError(t('sso.admin.error.loadFailed', 'Failed to load SSO configuration'))\n }\n setIsLoading(false)\n }, [configId, t])\n\n React.useEffect(() => { fetchConfig() }, [fetchConfig])\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n const payload: Record<string, unknown> = { name, issuer, clientId, jitEnabled, autoLinkByEmail }\n if (newClientSecret) payload.clientSecret = newClientSecret\n\n await runMutationWithContext(\n () => apiCallOrThrow<SsoConfigDetail>(\n `/api/sso/config/${configId}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: t('sso.admin.error.saveFailed', 'Failed to save SSO configuration') },\n ),\n payload,\n )\n flash(t('sso.admin.saved', 'SSO configuration saved'), 'success')\n setNewClientSecret('')\n setShowSecretField(false)\n fetchConfig()\n } catch {\n // apiCallOrThrow handles the error\n } finally {\n setIsSaving(false)\n }\n }\n\n const handleToggleActivation = async () => {\n if (!config) return\n setActivationError(null)\n setIsActivating(true)\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}/activate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active: !config.isActive }),\n },\n { errorMessage: t('sso.admin.error.activationFailed', 'Failed to update activation status') },\n ),\n { active: !config.isActive },\n )\n flash(\n config.isActive\n ? t('sso.admin.deactivated', 'SSO configuration deactivated')\n : t('sso.admin.activated', 'SSO configuration activated'),\n 'success',\n )\n setShowActivationBanner(false)\n fetchConfig()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const isNoDomains = message.toLowerCase().includes('no allowed domains')\n if (isNoDomains) {\n setActivationError(t('sso.admin.error.noDomainsForActivation', 'Add at least one allowed email domain before activating'))\n setActiveTab('domains')\n } else {\n setActivationError(message)\n }\n } finally {\n setIsActivating(false)\n }\n }\n\n const handleTestConnection = async () => {\n try {\n const call = await runMutationWithContext(\n () => apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/sso/config/${configId}/test`,\n { method: 'POST' },\n { errorMessage: t('sso.admin.error.testFailed', 'Connection test failed') },\n ),\n )\n if (call.result?.ok) {\n flash(t('sso.admin.test.success', 'Discovery successful \u2014 issuer is reachable'), 'success')\n } else {\n flash(call.result?.error || t('sso.admin.test.failed', 'Discovery failed'), 'error')\n }\n } catch {\n // handled by apiCallOrThrow\n }\n }\n\n const handleDelete = async () => {\n if (!config) return\n if (config.isActive) {\n flash(t('sso.admin.error.deleteActive', 'Cannot delete an active SSO configuration \u2014 deactivate it first'), 'error')\n return\n }\n const confirmed = await confirm({\n title: t('sso.admin.delete.title', 'Delete SSO Configuration'),\n text: t('sso.admin.delete.confirm', 'Are you sure? This will remove the SSO configuration.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n await runMutationWithContext(\n () => apiCallOrThrow(`/api/sso/config/${configId}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.error.deleteFailed', 'Failed to delete SSO configuration'),\n }),\n { id: configId },\n )\n flash(t('sso.admin.delete.success', 'SSO configuration deleted'), 'success')\n router.push('/backend/sso')\n }\n\n const handleAddDomain = async () => {\n const normalized = domainInput.trim().toLowerCase()\n if (!normalized) return\n\n const domainRegex = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/\n if (!domainRegex.test(normalized) || !normalized.includes('.')) {\n setDomainError(t('sso.admin.wizard.domain.invalid', 'Invalid domain format'))\n return\n }\n\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}/domains`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ domain: normalized }),\n },\n { errorMessage: t('sso.admin.error.domainAddFailed', 'Failed to add domain') },\n ),\n { domain: normalized },\n )\n setDomainInput('')\n setDomainError('')\n fetchConfig()\n } catch {\n // handled by apiCallOrThrow\n }\n }\n\n const handleRemoveDomain = async (domain: string) => {\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}/domains?domain=${encodeURIComponent(domain)}`,\n { method: 'DELETE' },\n { errorMessage: t('sso.admin.error.domainRemoveFailed', 'Failed to remove domain') },\n ),\n { domain },\n )\n fetchConfig()\n } catch {\n // handled by apiCallOrThrow\n }\n }\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('common.loading', 'Loading...')} /></PageBody></Page>\n if (error || !config) return <Page><PageBody><ErrorMessage label={error || t('common.notFound', 'Not found')} /></PageBody></Page>\n\n const tabs: { id: Tab; label: string }[] = [\n { id: 'general', label: t('sso.admin.tab.general', 'General') },\n { id: 'domains', label: t('sso.admin.tab.domains', 'Domains') },\n { id: 'roles', label: t('sso.admin.tab.roles', 'Role Mappings') },\n { id: 'scim', label: t('sso.admin.tab.scim', 'Provisioning') },\n { id: 'activity', label: t('sso.admin.tab.activity', 'Activity') },\n ]\n\n const statusBadge = (\n <span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${config.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'}`}>\n {config.isActive ? t('sso.admin.status.active', 'Active') : t('sso.admin.status.inactive', 'Inactive')}\n </span>\n )\n\n return (\n <Page>\n <PageBody>\n <div className=\"flex flex-col gap-6 max-w-3xl\">\n {/* Activation banner after creation */}\n {showActivationBanner && !config.isActive && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-4\">\n <p className=\"text-sm font-medium text-blue-900 mb-3\">\n {t('sso.admin.banner.created', 'Your SSO configuration has been created. Would you like to activate it now?')}\n </p>\n {activationError && (\n <p className=\"text-sm text-destructive mb-3\">{activationError}</p>\n )}\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" onClick={handleToggleActivation} disabled={isActivating}>\n {isActivating\n ? t('common.activating', 'Activating...')\n : t('sso.admin.banner.activateNow', 'Activate Now')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowActivationBanner(false)}>\n {t('sso.admin.banner.notYet', 'Not Yet')}\n </Button>\n </div>\n </div>\n )}\n\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/sso\"\n backLabel={t('sso.admin.detail.backToList', 'Back to SSO')}\n title={config.name || config.issuer || t('sso.admin.detail.title', 'SSO Configuration')}\n statusBadge={statusBadge}\n actionsContent={\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleTestConnection}>\n {t('sso.admin.action.test', 'Verify Discovery')}\n </Button>\n <Button\n type=\"button\"\n variant={config.isActive ? 'outline' : 'default'}\n size=\"sm\"\n onClick={handleToggleActivation}\n >\n {config.isActive\n ? t('sso.admin.action.deactivate', 'Deactivate')\n : t('sso.admin.action.activate', 'Activate')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleDelete} className=\"text-destructive\">\n {t('common.delete', 'Delete')}\n </Button>\n </div>\n }\n />\n\n {/* Tabs */}\n <div className=\"flex gap-1 border-b\">\n {tabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`h-auto rounded-none border-b-2 px-4 py-2 hover:bg-transparent ${\n activeTab === tab.id\n ? 'border-primary text-foreground'\n : 'border-transparent text-muted-foreground'\n }`}\n onClick={() => setActiveTab(tab.id)}\n >\n {tab.label}\n </Button>\n ))}\n </div>\n\n {/* Tab content */}\n {activeTab === 'general' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <h2 className=\"mb-4 text-sm font-semibold uppercase text-muted-foreground\">\n {t('sso.admin.section.oidcSettings', 'OIDC Settings')}\n </h2>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.name', 'Configuration Name')}</label>\n <input\n type=\"text\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.protocol', 'Protocol')}</label>\n <input type=\"text\" className=\"w-full rounded-md border px-3 py-2 text-sm bg-muted\" value={config.protocol.toUpperCase()} disabled />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.issuer', 'Issuer URL')}</label>\n <input\n type=\"url\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={issuer}\n onChange={(e) => setIssuer(e.target.value)}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.clientId', 'Client ID')}</label>\n <input\n type=\"text\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={clientId}\n onChange={(e) => setClientId(e.target.value)}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">{t('sso.admin.field.clientSecret', 'Client Secret')}</label>\n {config.hasClientSecret && !showSecretField ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">{t('sso.admin.field.secretSet', 'Client secret is configured')}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setShowSecretField(true)}>\n {t('sso.admin.field.changeSecret', 'Change')}\n </Button>\n </div>\n ) : (\n <input\n type=\"password\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n value={newClientSecret}\n onChange={(e) => setNewClientSecret(e.target.value)}\n placeholder={config.hasClientSecret\n ? t('sso.admin.field.secretPlaceholder', 'Enter new secret to replace existing')\n : t('sso.admin.field.secretRequired', 'Enter client secret')}\n />\n )}\n </div>\n <div className=\"space-y-3 pt-2\">\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={jitEnabled}\n onChange={(e) => setJitEnabled(e.target.checked)}\n disabled={config.hasActiveScimTokens}\n className=\"accent-primary\"\n />\n <div>\n <span className=\"text-sm font-medium\">{t('sso.admin.field.jitEnabled', 'Just-in-Time Provisioning')}</span>\n <span className=\"text-xs text-muted-foreground ml-2\">\n {config.hasActiveScimTokens\n ? t('sso.admin.field.jitDisabledByScim', 'Unavailable \u2014 SCIM directory sync is active. Revoke SCIM tokens to enable JIT.')\n : t('sso.admin.field.jitEnabledDesc', 'Automatically create user accounts on first SSO login')}\n </span>\n </div>\n </label>\n <label className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={autoLinkByEmail}\n onChange={(e) => setAutoLinkByEmail(e.target.checked)}\n className=\"accent-primary\"\n />\n <div>\n <span className=\"text-sm font-medium\">{t('sso.admin.field.autoLinkByEmail', 'Auto-link by Email')}</span>\n <span className=\"text-xs text-muted-foreground ml-2\">\n {t('sso.admin.field.autoLinkByEmailDesc', 'Automatically link existing users by matching email address')}\n </span>\n </div>\n </label>\n </div>\n <div className=\"pt-4\">\n <Button onClick={handleSave} disabled={isSaving}>\n {isSaving ? t('common.saving', 'Saving...') : t('common.save', 'Save')}\n </Button>\n </div>\n </div>\n </div>\n )}\n\n {activeTab === 'domains' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <h2 className=\"mb-4 text-sm font-semibold uppercase text-muted-foreground\">\n {t('sso.admin.section.allowedDomains', 'Allowed Domains')}\n </h2>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('sso.admin.wizard.domains.description', 'Users with email addresses matching these domains will be redirected to your SSO provider.')}\n </p>\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded-md border px-3 py-2 text-sm\"\n placeholder={t('sso.admin.wizard.domains.placeholder', 'example.com')}\n value={domainInput}\n onChange={(e) => { setDomainInput(e.target.value); setDomainError('') }}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddDomain() } }}\n />\n <Button type=\"button\" variant=\"outline\" onClick={handleAddDomain}>\n {t('common.add', 'Add')}\n </Button>\n </div>\n {domainError && <p className=\"text-sm text-destructive mb-2\">{domainError}</p>}\n {config.allowedDomains.length > 0 ? (\n <div className=\"space-y-2\">\n {config.allowedDomains.map((domain) => (\n <div key={domain} className=\"flex items-center justify-between p-3 border rounded-md\">\n <code className=\"text-sm font-mono\">{domain}</code>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => handleRemoveDomain(domain)}>\n {t('common.remove', 'Remove')}\n </Button>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {t('sso.admin.domains.empty', 'No domains configured. Add at least one domain before activating SSO.')}\n </p>\n )}\n </div>\n )}\n\n {activeTab === 'roles' && config && (\n <div className=\"rounded-lg border bg-card p-4\">\n <RoleMappingsTab\n configId={configId}\n appRoleMappings={config.appRoleMappings ?? {}}\n onSaved={fetchConfig}\n runMutationWithContext={runMutationWithContext}\n />\n </div>\n )}\n\n {activeTab === 'scim' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <ScimProvisioningTab configId={configId} jitEnabled={config.jitEnabled} issuer={config.issuer ?? undefined} onProvisioningChange={fetchConfig} runMutationWithContext={runMutationWithContext} />\n </div>\n )}\n\n {activeTab === 'activity' && (\n <div className=\"rounded-lg border bg-card p-4\">\n <SsoActivityTab configId={configId} />\n </div>\n )}\n </div>\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n\nfunction RoleMappingsTab({\n configId,\n appRoleMappings,\n onSaved,\n runMutationWithContext,\n}: {\n configId: string\n appRoleMappings: Record<string, string>\n onSaved: () => void\n runMutationWithContext: <T>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => Promise<T>\n}) {\n const t = useT()\n const [mappings, setMappings] = React.useState<Record<string, string>>(appRoleMappings)\n const [idpRoleInput, setIdpRoleInput] = React.useState('')\n const [localRoleInput, setLocalRoleInput] = React.useState('')\n const [roleOptions, setRoleOptions] = React.useState<{ value: string; label: string }[]>([])\n const [isSaving, setIsSaving] = React.useState(false)\n const [inputError, setInputError] = React.useState('')\n\n React.useEffect(() => {\n setMappings(appRoleMappings)\n }, [appRoleMappings])\n\n React.useEffect(() => {\n const loadRoles = async () => {\n const call = await apiCall<{ items?: Array<{ id?: string | null; name?: string | null }> }>(\n '/api/auth/roles?page=1&pageSize=50',\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && Array.isArray(call.result?.items)) {\n const options = call.result.items\n .map((item) => {\n const name = typeof item?.name === 'string' ? item.name.trim() : ''\n if (!name || name === 'superadmin') return null\n return { value: name, label: name }\n })\n .filter((opt): opt is { value: string; label: string } => !!opt)\n setRoleOptions(options)\n if (options.length > 0 && !localRoleInput) {\n setLocalRoleInput(options[0].value)\n }\n }\n }\n loadRoles()\n }, [])\n\n const handleAdd = () => {\n const trimmed = idpRoleInput.trim()\n if (!trimmed) {\n setInputError(t('sso.admin.roles.error.emptyIdpRole', 'IdP role name is required'))\n return\n }\n if (!localRoleInput) {\n setInputError(t('sso.admin.roles.error.emptyLocalRole', 'Select a local role'))\n return\n }\n if (mappings[trimmed]) {\n setInputError(t('sso.admin.roles.error.duplicate', 'This IdP role is already mapped'))\n return\n }\n setMappings({ ...mappings, [trimmed]: localRoleInput })\n setIdpRoleInput('')\n setInputError('')\n }\n\n const handleRemove = (idpRole: string) => {\n const updated = { ...mappings }\n delete updated[idpRole]\n setMappings(updated)\n }\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(\n `/api/sso/config/${configId}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ appRoleMappings: mappings }),\n },\n { errorMessage: t('sso.admin.roles.error.saveFailed', 'Failed to save role mappings') },\n ),\n { appRoleMappings: mappings },\n )\n flash(t('sso.admin.roles.saved', 'Role mappings saved'), 'success')\n onSaved()\n } catch {\n // handled by apiCallOrThrow\n } finally {\n setIsSaving(false)\n }\n }\n\n const mappingEntries = Object.entries(mappings)\n\n return (\n <div>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('sso.admin.roles.description', 'Map IdP app role names to local roles. On each SSO login, SSO-sourced roles are synced \u2014 roles no longer sent by the IdP are removed, while manually-assigned roles are preserved.')}\n </p>\n <div className=\"flex items-end gap-2 mb-4\">\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium mb-1\">{t('sso.admin.roles.idpRole', 'IdP Role Name')}</label>\n <input\n type=\"text\"\n className=\"w-full rounded-md border px-3 py-2 text-sm\"\n placeholder={t('sso.admin.roles.idpRolePlaceholder', 'e.g. OpenMercato.Admin')}\n value={idpRoleInput}\n onChange={(e) => { setIdpRoleInput(e.target.value); setInputError('') }}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAdd() } }}\n />\n </div>\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium mb-1\">{t('sso.admin.roles.localRole', 'Local Role')}</label>\n <select\n className=\"w-full rounded-md border px-3 py-2 text-sm bg-background\"\n value={localRoleInput}\n onChange={(e) => setLocalRoleInput(e.target.value)}\n >\n {roleOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n </div>\n <Button variant=\"outline\" onClick={handleAdd}>\n {t('common.add', 'Add')}\n </Button>\n </div>\n {inputError && <p className=\"text-sm text-destructive mb-2\">{inputError}</p>}\n\n {mappingEntries.length > 0 ? (\n <div className=\"space-y-2 mb-4\">\n {mappingEntries.map(([idpRole, localRole]) => (\n <div key={idpRole} className=\"flex items-center justify-between p-3 border rounded-md\">\n <div className=\"flex items-center gap-2\">\n <code className=\"text-sm font-mono\">{idpRole}</code>\n <span className=\"text-muted-foreground text-sm\">→</span>\n <span className=\"text-sm font-medium\">{localRole}</span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => handleRemove(idpRole)}>\n {t('common.remove', 'Remove')}\n </Button>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground py-4 text-center mb-4\">\n {t('sso.admin.roles.empty', 'No role mappings configured. IdP role names will be matched directly against local role names.')}\n </p>\n )}\n\n <Button onClick={handleSave} disabled={isSaving}>\n {isSaving ? t('common.saving', 'Saving...') : t('common.save', 'Save')}\n </Button>\n </div>\n )\n}\n\ninterface ScimTokenRow {\n id: string\n ssoConfigId: string\n name: string\n tokenPrefix: string\n isActive: boolean\n createdAt: string\n}\n\ninterface ScimLogRow {\n id: string\n operation: string\n resourceType: string\n resourceId: string | null\n scimExternalId: string | null\n responseStatus: number\n errorMessage: string | null\n createdAt: string\n}\n\nconst googleIssuerHost = 'accounts.google.com'\n\nfunction isGoogleIssuer(issuer?: string): boolean {\n if (!issuer) return false\n\n const normalizedIssuer = issuer.trim()\n if (!normalizedIssuer) return false\n\n try {\n return new URL(normalizedIssuer).hostname.toLowerCase() === googleIssuerHost\n } catch {\n return normalizedIssuer.toLowerCase() === googleIssuerHost\n }\n}\n\nfunction ScimProvisioningTab({ configId, jitEnabled, issuer, onProvisioningChange, runMutationWithContext }: { configId: string; jitEnabled: boolean; issuer?: string; onProvisioningChange: () => void; runMutationWithContext: <T>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => Promise<T> }) {\n const isGoogleProvider = isGoogleIssuer(issuer)\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [tokens, setTokens] = React.useState<ScimTokenRow[]>([])\n const [logs, setLogs] = React.useState<ScimLogRow[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [showCreateForm, setShowCreateForm] = React.useState(false)\n const [tokenName, setTokenName] = React.useState('')\n const [isCreating, setIsCreating] = React.useState(false)\n const [newlyCreatedToken, setNewlyCreatedToken] = React.useState<string | null>(null)\n\n const fetchData = React.useCallback(async () => {\n setIsLoading(true)\n const [tokensCall, logsCall] = await Promise.all([\n apiCall<{ items: ScimTokenRow[] }>(`/api/sso/scim/tokens?ssoConfigId=${configId}`),\n apiCall<{ items: ScimLogRow[] }>(`/api/sso/scim/logs?ssoConfigId=${configId}`),\n ])\n if (tokensCall.ok && tokensCall.result) setTokens(tokensCall.result.items)\n if (logsCall.ok && logsCall.result) setLogs(logsCall.result.items)\n setIsLoading(false)\n }, [configId])\n\n React.useEffect(() => { fetchData() }, [fetchData])\n\n const handleCreateToken = async () => {\n if (!tokenName.trim()) return\n setIsCreating(true)\n try {\n const result = await runMutationWithContext(\n () => apiCallOrThrow<{ id: string; token: string; prefix: string; name: string }>(\n '/api/sso/scim/tokens',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ ssoConfigId: configId, name: tokenName.trim() }),\n },\n { errorMessage: t('sso.admin.scim.error.createFailed', 'Failed to create SCIM token') },\n ),\n { ssoConfigId: configId, name: tokenName.trim() },\n )\n if (result.result) {\n setNewlyCreatedToken(result.result.token)\n setShowCreateForm(false)\n setTokenName('')\n fetchData()\n onProvisioningChange()\n }\n } catch {\n // handled by apiCallOrThrow\n } finally {\n setIsCreating(false)\n }\n }\n\n const handleRevokeToken = async (tokenId: string) => {\n const confirmed = await confirm({\n title: t('sso.admin.scim.revoke.title', 'Revoke Token'),\n text: t('sso.admin.scim.revoke.confirm', 'Are you sure? This token will no longer authenticate SCIM requests.'),\n confirmText: t('sso.admin.scim.revoke.action', 'Revoke'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await runMutationWithContext(\n () => apiCallOrThrow(`/api/sso/scim/tokens/${tokenId}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.scim.error.revokeFailed', 'Failed to revoke token'),\n }),\n { tokenId },\n )\n flash(t('sso.admin.scim.revoked', 'Token revoked'), 'success')\n fetchData()\n onProvisioningChange()\n } catch {\n // handled\n }\n }\n\n const handleCopyEndpoint = () => {\n const url = `${window.location.origin}/api/sso/scim/v2`\n navigator.clipboard.writeText(url).then(() => {\n flash(t('sso.admin.scim.endpointCopied', 'SCIM endpoint URL copied'), 'success')\n })\n }\n\n const handleCopyToken = () => {\n if (!newlyCreatedToken) return\n navigator.clipboard.writeText(newlyCreatedToken).then(() => {\n flash(t('sso.admin.scim.tokenCopied', 'Token copied to clipboard'), 'success')\n })\n }\n\n if (isLoading) return <LoadingMessage label={t('common.loading', 'Loading...')} />\n\n return (\n <div className=\"space-y-6\">\n {/* Google provider banner \u2014 SCIM not supported */}\n {isGoogleProvider && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-4\">\n <p className=\"text-sm text-blue-900\">\n {t('sso.admin.scim.googleNotSupported', 'Google Workspace does not support SCIM provisioning. Users are provisioned via Just-In-Time (JIT) on first login.')}\n </p>\n </div>\n )}\n\n {/* JIT active banner */}\n {!isGoogleProvider && jitEnabled && (\n <div className=\"rounded-lg border border-amber-200 bg-amber-50 p-4\">\n <p className=\"text-sm text-amber-900\">\n {t('sso.admin.scim.jitActiveWarning', 'SCIM provisioning is unavailable while JIT provisioning is enabled. Disable JIT in the General tab to configure SCIM.')}\n </p>\n </div>\n )}\n\n {isGoogleProvider ? null : <>\n {/* SCIM Endpoint URL */}\n <div>\n <h3 className=\"text-sm font-medium mb-2\">{t('sso.admin.scim.endpointUrl', 'SCIM Endpoint URL')}</h3>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 rounded-md border bg-muted px-3 py-2 text-sm font-mono\">\n {typeof window !== 'undefined' ? `${window.location.origin}/api/sso/scim/v2` : '/api/sso/scim/v2'}\n </code>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCopyEndpoint}>\n {t('common.copy', 'Copy')}\n </Button>\n </div>\n </div>\n\n {/* Newly created token banner */}\n {newlyCreatedToken && (\n <div className=\"rounded-lg border border-amber-200 bg-amber-50 p-4\">\n <p className=\"text-sm font-medium text-amber-900 mb-2\">\n {t('sso.admin.scim.tokenCreated', 'Your SCIM token has been created. Copy it now \u2014 it will not be shown again.')}\n </p>\n <div className=\"flex items-center gap-2 mb-2\">\n <code className=\"flex-1 rounded-md border bg-white px-3 py-2 text-xs font-mono break-all\">\n {newlyCreatedToken}\n </code>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCopyToken}>\n {t('common.copy', 'Copy')}\n </Button>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setNewlyCreatedToken(null)}>\n {t('common.dismiss', 'Dismiss')}\n </Button>\n </div>\n )}\n\n {/* Token management */}\n <div>\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"text-sm font-medium\">{t('sso.admin.scim.tokens', 'Bearer Tokens')}</h3>\n {!showCreateForm && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowCreateForm(true)} disabled={jitEnabled}>\n {t('sso.admin.scim.generateToken', 'Generate Token')}\n </Button>\n )}\n </div>\n\n {showCreateForm && (\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded-md border px-3 py-2 text-sm\"\n placeholder={t('sso.admin.scim.tokenNamePlaceholder', 'Token name (e.g., Entra ID Production)')}\n value={tokenName}\n onChange={(e) => setTokenName(e.target.value)}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleCreateToken() } }}\n autoFocus\n />\n <Button size=\"sm\" onClick={handleCreateToken} disabled={isCreating || !tokenName.trim()}>\n {isCreating ? t('common.creating', 'Creating...') : t('common.create', 'Create')}\n </Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => { setShowCreateForm(false); setTokenName('') }}>\n {t('common.cancel', 'Cancel')}\n </Button>\n </div>\n )}\n\n {tokens.length === 0 ? (\n <div className=\"text-center py-8 border rounded-md\">\n <p className=\"text-sm text-muted-foreground\">\n {t('sso.admin.scim.noTokens', 'SCIM provisioning is not configured. Generate a bearer token to enable your identity provider to sync users automatically.')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {tokens.map((token) => (\n <div key={token.id} className=\"flex items-center justify-between p-3 border rounded-md\">\n <div className=\"flex items-center gap-3\">\n <div>\n <span className=\"text-sm font-medium\">{token.name}</span>\n <span className=\"text-xs text-muted-foreground ml-2 font-mono\">{token.tokenPrefix}...</span>\n </div>\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n token.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-500'\n }`}>\n {token.isActive ? t('sso.admin.scim.tokenActive', 'Active') : t('sso.admin.scim.tokenRevoked', 'Revoked')}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {new Date(token.createdAt).toLocaleDateString()}\n </span>\n {token.isActive && (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => handleRevokeToken(token.id)} className=\"text-destructive\">\n {t('sso.admin.scim.revoke.action', 'Revoke')}\n </Button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Provisioning log */}\n {logs.length > 0 && (\n <div>\n <h3 className=\"text-sm font-medium mb-3\">{t('sso.admin.scim.recentActivity', 'Recent Provisioning Activity')}</h3>\n <div className=\"border rounded-md overflow-hidden\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/50\">\n <tr>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.time', 'Time')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.operation', 'Operation')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.resource', 'Resource')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.status', 'Status')}</th>\n <th className=\"text-left px-3 py-2 font-medium\">{t('sso.admin.scim.log.error', 'Error')}</th>\n </tr>\n </thead>\n <tbody>\n {logs.map((log) => (\n <tr key={log.id} className=\"border-t\">\n <td className=\"px-3 py-2 text-xs text-muted-foreground whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleString()}\n </td>\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium bg-muted\">\n {log.operation}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs\">{log.resourceType}</td>\n <td className=\"px-3 py-2\">\n <span className={`text-xs font-medium ${log.responseStatus < 300 ? 'text-green-700' : 'text-red-600'}`}>\n {log.responseStatus}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-muted-foreground truncate max-w-[200px]\">\n {log.errorMessage || '\u2014'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n )}\n\n </>}\n\n {ConfirmDialogElement}\n </div>\n )\n}\n\nfunction SsoActivityTab({ configId }: { configId: string }) {\n const t = useT()\n const [identities, setIdentities] = React.useState<SsoIdentityRow[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n\n React.useEffect(() => {\n const load = async () => {\n setIsLoading(true)\n // For now, just show a placeholder \u2014 the identities list API is an M3 deliverable\n setIdentities([])\n setIsLoading(false)\n }\n load()\n }, [configId])\n\n if (isLoading) return <LoadingMessage label={t('common.loading', 'Loading...')} />\n\n if (identities.length === 0) {\n return (\n <div className=\"text-center py-8\">\n <p className=\"text-sm text-muted-foreground\">\n {t('sso.admin.activity.empty', 'No SSO login activity yet. Activity will appear here once users start logging in via SSO.')}\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n {identities.map((identity) => (\n <div key={identity.id} className=\"flex items-center justify-between p-3 border rounded-md\">\n <div>\n <span className=\"text-sm font-medium\">{identity.idpEmail}</span>\n {identity.idpName && <span className=\"text-sm text-muted-foreground ml-2\">({identity.idpName})</span>}\n </div>\n <span className=\"text-xs text-muted-foreground\">\n {identity.lastLoginAt ? new Date(identity.lastLoginAt).toLocaleString() : '\u2014'}\n </span>\n </div>\n ))}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/enterprise",
|
|
3
|
-
"version": "0.4.6-develop-
|
|
3
|
+
"version": "0.4.6-develop-a71fbbd3da",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@open-mercato/core": "0.4.6-develop-
|
|
68
|
-
"@open-mercato/shared": "0.4.6-develop-
|
|
69
|
-
"@open-mercato/ui": "0.4.6-develop-
|
|
67
|
+
"@open-mercato/core": "0.4.6-develop-a71fbbd3da",
|
|
68
|
+
"@open-mercato/shared": "0.4.6-develop-a71fbbd3da",
|
|
69
|
+
"@open-mercato/ui": "0.4.6-develop-a71fbbd3da",
|
|
70
70
|
"openid-client": "^6.3.3"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
@@ -711,8 +711,23 @@ interface ScimLogRow {
|
|
|
711
711
|
createdAt: string
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
+
const googleIssuerHost = 'accounts.google.com'
|
|
715
|
+
|
|
716
|
+
function isGoogleIssuer(issuer?: string): boolean {
|
|
717
|
+
if (!issuer) return false
|
|
718
|
+
|
|
719
|
+
const normalizedIssuer = issuer.trim()
|
|
720
|
+
if (!normalizedIssuer) return false
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
return new URL(normalizedIssuer).hostname.toLowerCase() === googleIssuerHost
|
|
724
|
+
} catch {
|
|
725
|
+
return normalizedIssuer.toLowerCase() === googleIssuerHost
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
714
729
|
function ScimProvisioningTab({ configId, jitEnabled, issuer, onProvisioningChange, runMutationWithContext }: { configId: string; jitEnabled: boolean; issuer?: string; onProvisioningChange: () => void; runMutationWithContext: <T>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => Promise<T> }) {
|
|
715
|
-
const isGoogleProvider = issuer
|
|
730
|
+
const isGoogleProvider = isGoogleIssuer(issuer)
|
|
716
731
|
const t = useT()
|
|
717
732
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
718
733
|
|