@open-mercato/enterprise 0.6.6-develop.6465.1.019f0fb26f → 0.6.6-develop.6472.1.1673e7e66b

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.
Files changed (66) hide show
  1. package/dist/modules/record_locks/subscribers/conflict-detected-notification.js +3 -1
  2. package/dist/modules/record_locks/subscribers/conflict-detected-notification.js.map +2 -2
  3. package/dist/modules/record_locks/subscribers/conflict-resolved-notification.js +3 -1
  4. package/dist/modules/record_locks/subscribers/conflict-resolved-notification.js.map +2 -2
  5. package/dist/modules/record_locks/subscribers/lock-contended-notification.js +3 -1
  6. package/dist/modules/record_locks/subscribers/lock-contended-notification.js.map +2 -2
  7. package/dist/modules/record_locks/subscribers/lock-force-released-notification.js +3 -1
  8. package/dist/modules/record_locks/subscribers/lock-force-released-notification.js.map +2 -2
  9. package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js +4 -2
  10. package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js.map +2 -2
  11. package/dist/modules/security/api/enforcement/_shared.js +3 -1
  12. package/dist/modules/security/api/enforcement/_shared.js.map +2 -2
  13. package/dist/modules/security/api/mfa/_shared.js +3 -1
  14. package/dist/modules/security/api/mfa/_shared.js.map +2 -2
  15. package/dist/modules/security/api/profile/password/route.js +3 -1
  16. package/dist/modules/security/api/profile/password/route.js.map +2 -2
  17. package/dist/modules/security/api/sudo/_shared.js +3 -1
  18. package/dist/modules/security/api/sudo/_shared.js.map +2 -2
  19. package/dist/modules/security/api/users/_shared.js +3 -1
  20. package/dist/modules/security/api/users/_shared.js.map +2 -2
  21. package/dist/modules/sso/api/callback/oidc/route.js +5 -3
  22. package/dist/modules/sso/api/callback/oidc/route.js.map +2 -2
  23. package/dist/modules/sso/api/error-handler.js +4 -2
  24. package/dist/modules/sso/api/error-handler.js.map +2 -2
  25. package/dist/modules/sso/api/hrd/route.js +3 -1
  26. package/dist/modules/sso/api/hrd/route.js.map +2 -2
  27. package/dist/modules/sso/api/initiate/route.js +4 -2
  28. package/dist/modules/sso/api/initiate/route.js.map +2 -2
  29. package/dist/modules/sso/backend/sso/config/[id]/page.js +2 -2
  30. package/dist/modules/sso/backend/sso/config/[id]/page.js.map +2 -2
  31. package/dist/modules/sso/i18n/de.json +2 -2
  32. package/dist/modules/sso/i18n/en.json +2 -2
  33. package/dist/modules/sso/i18n/es.json +2 -2
  34. package/dist/modules/sso/i18n/pl.json +2 -2
  35. package/dist/modules/sso/services/accountLinkingService.js +9 -46
  36. package/dist/modules/sso/services/accountLinkingService.js.map +2 -2
  37. package/dist/modules/sso/services/ssoConfigService.js +9 -7
  38. package/dist/modules/sso/services/ssoConfigService.js.map +2 -2
  39. package/dist/modules/sso/services/ssoService.js +4 -2
  40. package/dist/modules/sso/services/ssoService.js.map +2 -2
  41. package/dist/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.js +4 -2
  42. package/dist/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.js.map +2 -2
  43. package/package.json +5 -5
  44. package/src/modules/record_locks/subscribers/conflict-detected-notification.ts +4 -1
  45. package/src/modules/record_locks/subscribers/conflict-resolved-notification.ts +4 -1
  46. package/src/modules/record_locks/subscribers/lock-contended-notification.ts +4 -1
  47. package/src/modules/record_locks/subscribers/lock-force-released-notification.ts +4 -1
  48. package/src/modules/record_locks/widgets/injection/record-locking/widget.client.tsx +5 -2
  49. package/src/modules/security/api/enforcement/_shared.ts +4 -1
  50. package/src/modules/security/api/mfa/_shared.ts +4 -1
  51. package/src/modules/security/api/profile/password/route.ts +4 -1
  52. package/src/modules/security/api/sudo/_shared.ts +4 -1
  53. package/src/modules/security/api/users/_shared.ts +4 -1
  54. package/src/modules/sso/api/callback/oidc/route.ts +6 -3
  55. package/src/modules/sso/api/error-handler.ts +5 -2
  56. package/src/modules/sso/api/hrd/route.ts +4 -1
  57. package/src/modules/sso/api/initiate/route.ts +5 -2
  58. package/src/modules/sso/backend/sso/config/[id]/page.tsx +2 -2
  59. package/src/modules/sso/i18n/de.json +2 -2
  60. package/src/modules/sso/i18n/en.json +2 -2
  61. package/src/modules/sso/i18n/es.json +2 -2
  62. package/src/modules/sso/i18n/pl.json +2 -2
  63. package/src/modules/sso/services/accountLinkingService.ts +10 -52
  64. package/src/modules/sso/services/ssoConfigService.ts +10 -7
  65. package/src/modules/sso/services/ssoService.ts +5 -2
  66. package/src/modules/system_status_overlays/subscribers/application-bootstrap-enterprise-warning.ts +6 -2
@@ -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 { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } 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 [isNotFound, setIsNotFound] = React.useState(false)\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 setIsNotFound(false)\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 if (call.status === 404) {\n setIsNotFound(true)\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 (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('sso.admin.error.notFound', 'SSO configuration not found.')}\n backHref=\"/backend/sso\"\n backLabel={t('sso.admin.backToList', 'Back to SSO configurations')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !config) return <Page><PageBody><ErrorMessage label={error ?? t('sso.admin.error.loadFailed', 'Failed to load SSO configuration')} /></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-status-success-bg text-status-success-text' : 'bg-status-neutral-bg text-status-neutral-text'}`}>\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-status-info-border bg-status-info-bg p-4\">\n <p className=\"text-sm font-medium text-status-info-text 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-accent-indigo 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 <PasswordInput\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\">&rarr;</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-status-info-border bg-status-info-bg p-4\">\n <p className=\"text-sm text-status-info-text\">\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-status-warning-border bg-status-warning-bg p-4\">\n <p className=\"text-sm text-status-warning-text\">\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-status-warning-border bg-status-warning-bg p-4\">\n <p className=\"text-sm font-medium text-status-warning-text 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-status-success-bg text-status-success-text' : 'bg-status-neutral-bg text-status-neutral-text'\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-status-success-text' : 'text-destructive'}`}>\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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",
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 { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } 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 [isNotFound, setIsNotFound] = React.useState(false)\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 setIsNotFound(false)\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 if (call.status === 404) {\n setIsNotFound(true)\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 (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('sso.admin.error.notFound', 'SSO configuration not found.')}\n backHref=\"/backend/sso\"\n backLabel={t('sso.admin.backToList', 'Back to SSO configurations')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !config) return <Page><PageBody><ErrorMessage label={error ?? t('sso.admin.error.loadFailed', 'Failed to load SSO configuration')} /></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-status-success-bg text-status-success-text' : 'bg-status-neutral-bg text-status-neutral-text'}`}>\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-status-info-border bg-status-info-bg p-4\">\n <p className=\"text-sm font-medium text-status-info-text 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-accent-indigo 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 <PasswordInput\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. Only explicitly mapped IdP roles grant local roles; on each SSO login, SSO-sourced roles are synced 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\">&rarr;</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. Users will not receive SSO-sourced roles until an IdP role is explicitly mapped to a local role.')}\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-status-info-border bg-status-info-bg p-4\">\n <p className=\"text-sm text-status-info-text\">\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-status-warning-border bg-status-warning-bg p-4\">\n <p className=\"text-sm text-status-warning-text\">\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-status-warning-border bg-status-warning-bg p-4\">\n <p className=\"text-sm font-medium text-status-warning-text 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-status-success-bg text-status-success-text' : 'bg-status-neutral-bg text-status-neutral-text'\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-status-success-text' : 'text-destructive'}`}>\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
  }
@@ -72,8 +72,8 @@
72
72
  "sso.admin.field.secretRequired": "Client-Geheimnis eingeben",
73
73
  "sso.admin.field.secretSet": "Client-Geheimnis ist konfiguriert",
74
74
  "sso.admin.new": "Neue SSO-Konfiguration",
75
- "sso.admin.roles.description": "Ordnen Sie IdP-App-Rollennamen lokalen Rollen zu. Bei jeder SSO-Anmeldung werden SSO-basierte Rollen synchronisiert — Rollen, die der IdP nicht mehr sendet, werden entfernt, während manuell zugewiesene Rollen erhalten bleiben.",
76
- "sso.admin.roles.empty": "Keine Rollenzuordnungen konfiguriert. IdP-Rollennamen werden direkt mit lokalen Rollennamen abgeglichen.",
75
+ "sso.admin.roles.description": "Ordnen Sie IdP-App-Rollennamen lokalen Rollen zu. Nur ausdrücklich zugeordnete IdP-Rollen vergeben lokale Rollen; bei jeder SSO-Anmeldung werden SSO-basierte Rollen synchronisiert, während manuell zugewiesene Rollen erhalten bleiben.",
76
+ "sso.admin.roles.empty": "Keine Rollenzuordnungen konfiguriert. Benutzer erhalten keine SSO-basierten Rollen, bis eine IdP-Rolle ausdrücklich einer lokalen Rolle zugeordnet ist.",
77
77
  "sso.admin.roles.error.duplicate": "Diese IdP-Rolle ist bereits zugeordnet",
78
78
  "sso.admin.roles.error.emptyIdpRole": "IdP-Rollenname ist erforderlich",
79
79
  "sso.admin.roles.error.emptyLocalRole": "Wählen Sie eine lokale Rolle",
@@ -72,8 +72,8 @@
72
72
  "sso.admin.field.secretRequired": "Enter client secret",
73
73
  "sso.admin.field.secretSet": "Client secret is configured",
74
74
  "sso.admin.new": "New SSO Config",
75
- "sso.admin.roles.description": "Map IdP app role names to local roles. On each SSO login, SSO-sourced roles are synced roles no longer sent by the IdP are removed, while manually-assigned roles are preserved.",
76
- "sso.admin.roles.empty": "No role mappings configured. IdP role names will be matched directly against local role names.",
75
+ "sso.admin.roles.description": "Map IdP app role names to local roles. Only explicitly mapped IdP roles grant local roles; on each SSO login, SSO-sourced roles are synced while manually-assigned roles are preserved.",
76
+ "sso.admin.roles.empty": "No role mappings configured. Users will not receive SSO-sourced roles until an IdP role is explicitly mapped to a local role.",
77
77
  "sso.admin.roles.error.duplicate": "This IdP role is already mapped",
78
78
  "sso.admin.roles.error.emptyIdpRole": "IdP role name is required",
79
79
  "sso.admin.roles.error.emptyLocalRole": "Select a local role",
@@ -72,8 +72,8 @@
72
72
  "sso.admin.field.secretRequired": "Ingrese el secreto de cliente",
73
73
  "sso.admin.field.secretSet": "El secreto de cliente está configurado",
74
74
  "sso.admin.new": "Nueva configuración SSO",
75
- "sso.admin.roles.description": "Asigne nombres de roles de aplicación IdP a roles locales. En cada inicio de sesión SSO, los roles de origen SSO se sincronizan los roles que el IdP ya no envía se eliminan, mientras que los roles asignados manualmente se conservan.",
76
- "sso.admin.roles.empty": "No hay asignaciones de roles configuradas. Los nombres de roles del IdP se compararán directamente con los nombres de roles locales.",
75
+ "sso.admin.roles.description": "Asigne nombres de roles de aplicación IdP a roles locales. Solo los roles de IdP asignados explícitamente conceden roles locales; en cada inicio de sesión SSO, los roles de origen SSO se sincronizan mientras se conservan los roles asignados manualmente.",
76
+ "sso.admin.roles.empty": "No hay asignaciones de roles configuradas. Los usuarios no recibirán roles de origen SSO hasta que un rol de IdP se asigne explícitamente a un rol local.",
77
77
  "sso.admin.roles.error.duplicate": "Este rol de IdP ya está asignado",
78
78
  "sso.admin.roles.error.emptyIdpRole": "El nombre del rol de IdP es obligatorio",
79
79
  "sso.admin.roles.error.emptyLocalRole": "Seleccione un rol local",
@@ -72,8 +72,8 @@
72
72
  "sso.admin.field.secretRequired": "Wprowadź client secret",
73
73
  "sso.admin.field.secretSet": "Client secret jest skonfigurowany",
74
74
  "sso.admin.new": "Nowa konfiguracja SSO",
75
- "sso.admin.roles.description": "Mapuj nazwy ról IdP na lokalne role. Przy każdym logowaniu SSO role źródłowe SSO są synchronizowane role nieotrzymane od IdP są usuwane, role przypisane ręcznie zachowane.",
76
- "sso.admin.roles.empty": "Brak skonfigurowanych mapowań ról. Nazwy ról IdP będą dopasowywane bezpośrednio do nazw lokalnych ról.",
75
+ "sso.admin.roles.description": "Mapuj nazwy ról IdP na lokalne role. Tylko jawnie zmapowane role IdP nadają lokalne role; przy każdym logowaniu SSO role źródłowe SSO są synchronizowane, a role przypisane ręcznie pozostają zachowane.",
76
+ "sso.admin.roles.empty": "Brak skonfigurowanych mapowań ról. Użytkownicy nie otrzymają ról źródłowych SSO, dopóki rola IdP nie zostanie jawnie zmapowana na lokalną rolę.",
77
77
  "sso.admin.roles.error.duplicate": "Ta rola IdP jest już zmapowana",
78
78
  "sso.admin.roles.error.emptyIdpRole": "Nazwa roli IdP jest wymagana",
79
79
  "sso.admin.roles.error.emptyLocalRole": "Wybierz lokalną rolę",
@@ -4,6 +4,8 @@ import { computeEmailHash } from "@open-mercato/core/modules/auth/lib/emailHash"
4
4
  import { SsoIdentity, SsoRoleGrant, ScimToken } from "../data/entities.js";
5
5
  import { emitSsoEvent } from "../events.js";
6
6
  import { EmailNotVerifiedError } from "../lib/errors.js";
7
+ import { createLogger } from "@open-mercato/shared/lib/logger";
8
+ const logger = createLogger("sso").child({ component: "account-linking" });
7
9
  class AccountLinkingService {
8
10
  constructor(em) {
9
11
  this.em = em;
@@ -98,7 +100,7 @@ class AccountLinkingService {
98
100
  id: identity.id,
99
101
  tenantId,
100
102
  organizationId: config.organizationId
101
- }).catch((e) => console.error("[SSO Event]", e));
103
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
102
104
  return { user, identity };
103
105
  }
104
106
  async jitProvision(config, idpPayload, tenantId) {
@@ -136,7 +138,7 @@ class AccountLinkingService {
136
138
  id: identity.id,
137
139
  tenantId,
138
140
  organizationId: config.organizationId
139
- }).catch((e) => console.error("[SSO Event]", e));
141
+ }).catch((eventError) => logger.error("SSO event emit failed", { err: eventError }));
140
142
  return { user, identity };
141
143
  });
142
144
  }
@@ -231,62 +233,23 @@ function resolveRoleNamesFromIdpGroups(idpGroups, configMappings) {
231
233
  const roleNames = /* @__PURE__ */ new Set();
232
234
  for (const group of normalizedGroups) {
233
235
  const mapped = mergedMappings.get(group);
234
- if (mapped?.length) {
235
- for (const role of mapped) roleNames.add(role);
236
- continue;
237
- }
238
- roleNames.add(group);
239
- const segmented = group.split(/[\\/:]/).map((part) => normalizeToken(part)).filter((part) => part !== null);
240
- for (const candidate of segmented) {
241
- roleNames.add(candidate);
242
- }
236
+ if (!mapped?.length) continue;
237
+ for (const role of mapped) roleNames.add(role);
243
238
  }
244
239
  return Array.from(roleNames);
245
240
  }
246
241
  function loadMergedMappings(configMappings) {
247
- const envMappings = loadGroupRoleMappingsFromEnv();
242
+ const mappings = /* @__PURE__ */ new Map();
248
243
  if (configMappings && Object.keys(configMappings).length > 0) {
249
244
  for (const [group, roleName] of Object.entries(configMappings)) {
250
245
  const normalizedGroup = normalizeToken(group);
251
246
  if (!normalizedGroup) continue;
252
247
  const normalizedRole = normalizeToken(roleName);
253
248
  if (!normalizedRole) continue;
254
- envMappings.set(normalizedGroup, [normalizedRole]);
255
- }
256
- }
257
- return envMappings;
258
- }
259
- function loadGroupRoleMappingsFromEnv() {
260
- const raw = process.env.SSO_GROUP_ROLE_MAP;
261
- if (!raw) return /* @__PURE__ */ new Map();
262
- try {
263
- const parsed = JSON.parse(raw);
264
- const out = /* @__PURE__ */ new Map();
265
- for (const [group, roleValue] of Object.entries(parsed)) {
266
- const normalizedGroup = normalizeToken(group);
267
- if (!normalizedGroup) continue;
268
- const roles = normalizeRoleList(roleValue);
269
- if (roles.length > 0) out.set(normalizedGroup, roles);
270
- }
271
- return out;
272
- } catch {
273
- return /* @__PURE__ */ new Map();
274
- }
275
- }
276
- function normalizeRoleList(value) {
277
- if (typeof value === "string") {
278
- const token = normalizeToken(value);
279
- return token ? [token] : [];
280
- }
281
- if (Array.isArray(value)) {
282
- const out = /* @__PURE__ */ new Set();
283
- for (const entry of value) {
284
- const token = normalizeToken(entry);
285
- if (token) out.add(token);
249
+ mappings.set(normalizedGroup, [normalizedRole]);
286
250
  }
287
- return Array.from(out);
288
251
  }
289
- return [];
252
+ return mappings;
290
253
  }
291
254
  function normalizeToken(value) {
292
255
  if (typeof value !== "string") return null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sso/services/accountLinkingService.ts"],
4
- "sourcesContent": ["import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { SsoConfig, SsoIdentity, SsoRoleGrant, ScimToken } from '../data/entities'\nimport { emitSsoEvent } from '../events'\nimport { EmailNotVerifiedError } from '../lib/errors'\nimport type { SsoIdentityPayload } from '../lib/types'\n\nexport class AccountLinkingService {\n constructor(private em: EntityManager) {}\n\n async resolveUser(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId)\n if (existing) {\n await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups)\n return existing\n }\n\n if (idpPayload.emailVerified === false) {\n throw new EmailNotVerifiedError('IdP explicitly reported email as unverified \u2014 cannot link or provision account')\n }\n\n const emailDomain = idpPayload.email.split('@')[1]?.toLowerCase()\n if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {\n throw new Error('Email domain is not in the allowed domains for this SSO configuration')\n }\n\n const emailLinked = config.autoLinkByEmail\n ? await this.linkByEmail(config, idpPayload, tenantId)\n : null\n if (emailLinked) {\n await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups)\n return emailLinked\n }\n\n if (config.jitEnabled) {\n const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0\n if (scimActive) {\n throw new Error('JIT provisioning is disabled because SCIM directory sync is active')\n }\n return this.jitProvision(config, idpPayload, tenantId)\n }\n\n throw new Error('No matching user found and JIT provisioning is disabled')\n }\n\n private async findExistingLink(\n ssoConfigId: string,\n idpSubject: string,\n tenantId: string,\n organizationId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const identity = await findOneWithDecryption(\n this.em,\n SsoIdentity,\n { ssoConfigId, idpSubject, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!identity) return null\n\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n identity.deletedAt = new Date()\n await this.em.flush()\n return null\n }\n\n identity.lastLoginAt = new Date()\n await this.em.flush()\n\n return { user, identity }\n }\n\n private async linkByEmail(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const emailHash = computeEmailHash(idpPayload.email)\n const user = await findOneWithDecryption(\n this.em,\n User,\n {\n organizationId: config.organizationId,\n deletedAt: null,\n $or: [\n { email: idpPayload.email },\n { emailHash },\n ],\n } as FilterQuery<User>,\n {},\n { tenantId, organizationId: config.organizationId },\n )\n if (!user) return null\n\n const now = new Date()\n const identity = this.em.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'manual',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await this.em.persist(identity).flush()\n\n void emitSsoEvent('sso.identity.linked', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n }\n\n private async jitProvision(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n return this.em.transactional(async (txEm) => {\n const user = txEm.create(User, {\n tenantId,\n organizationId: config.organizationId,\n email: idpPayload.email,\n emailHash: computeEmailHash(idpPayload.email),\n name: idpPayload.name ?? null,\n passwordHash: null,\n isConfirmed: true,\n createdAt: new Date(),\n })\n await txEm.persist(user).flush()\n\n await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)\n\n const now = new Date()\n const identity = txEm.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'jit',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await txEm.persist(identity).flush()\n\n void emitSsoEvent('sso.identity.created', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n })\n }\n\n private async assignRolesFromSso(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0\n if (!hasMappings) return\n\n await this.syncMappedRoles(em, user, config, tenantId, idpGroups)\n\n const hasAnySsoRole = await em.findOne(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n if (!hasAnySsoRole) {\n throw new Error('No roles could be resolved from IdP groups \u2014 login denied. Configure role mappings or ensure the IdP sends matching group claims.')\n }\n }\n\n /**\n * Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced\n * with what the IdP sends, while manually-assigned roles are preserved.\n */\n private async syncMappedRoles(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const resolvedTenantId = tenantId || user.tenantId || ''\n if (!resolvedTenantId) return\n\n const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null } as FilterQuery<Role>)\n const roleByNormalizedName = new Map<string, Role>()\n for (const role of allRoles) {\n const normalized = normalizeToken(role.name)\n if (normalized) roleByNormalizedName.set(normalized, role)\n }\n\n // Resolve desired role IDs from IdP groups using merged mappings\n const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings)\n const desiredRoleIds = new Set<string>()\n for (const roleName of desiredRoleNames) {\n const role = roleByNormalizedName.get(roleName)\n if (role) desiredRoleIds.add(role.id)\n }\n\n // Query current SSO grants for this user+config\n const existingGrants = await em.find(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId))\n\n // Compute diff\n const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id))\n const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId))\n\n // Add new roles\n for (const roleId of toAdd) {\n const role = allRoles.find((r) => r.id === roleId)\n if (!role) continue\n await this.ensureUserRole(em, user, role)\n const grant = em.create(SsoRoleGrant, {\n tenantId: resolvedTenantId,\n organizationId: config.organizationId,\n userId: user.id,\n roleId,\n ssoConfigId: config.id,\n } as RequiredEntityData<SsoRoleGrant>)\n em.persist(grant)\n }\n\n // Remove stale SSO-sourced roles\n for (const grant of toRemove) {\n const userRole = await em.findOne(UserRole, {\n user: user.id,\n role: grant.roleId,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (userRole) {\n em.remove(userRole)\n }\n em.remove(grant)\n }\n\n // Clean up orphaned soft-deleted UserRole rows (ghost rows from previous soft-delete logic)\n const allUserRoles = await em.find(UserRole, { user: user.id } as FilterQuery<UserRole>)\n for (const ur of allUserRoles) {\n if (ur.deletedAt) {\n em.remove(ur)\n }\n }\n\n if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {\n await em.flush()\n }\n }\n\n private async ensureUserRole(em: EntityManager, user: User, role: Role): Promise<void> {\n const existingLink = await em.findOne(UserRole, {\n user: user.id,\n role: role.id,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (existingLink) return\n\n const userRole = em.create(UserRole, { user, role, createdAt: new Date() })\n await em.persist(userRole).flush()\n }\n}\n\nfunction resolveRoleNamesFromIdpGroups(\n idpGroups?: string[],\n configMappings?: Record<string, string>,\n): string[] {\n if (!Array.isArray(idpGroups) || idpGroups.length === 0) return []\n\n const normalizedGroups = idpGroups\n .map((group) => normalizeToken(group))\n .filter((group): group is string => group !== null)\n if (normalizedGroups.length === 0) return []\n\n const mergedMappings = loadMergedMappings(configMappings)\n const roleNames = new Set<string>()\n\n for (const group of normalizedGroups) {\n const mapped = mergedMappings.get(group)\n if (mapped?.length) {\n for (const role of mapped) roleNames.add(role)\n continue\n }\n\n roleNames.add(group)\n const segmented = group.split(/[\\\\/:]/).map((part) => normalizeToken(part)).filter((part): part is string => part !== null)\n for (const candidate of segmented) {\n roleNames.add(candidate)\n }\n }\n\n return Array.from(roleNames)\n}\n\nfunction loadMergedMappings(configMappings?: Record<string, string>): Map<string, string[]> {\n const envMappings = loadGroupRoleMappingsFromEnv()\n\n // Per-config mappings take precedence over env var\n if (configMappings && Object.keys(configMappings).length > 0) {\n for (const [group, roleName] of Object.entries(configMappings)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const normalizedRole = normalizeToken(roleName)\n if (!normalizedRole) continue\n envMappings.set(normalizedGroup, [normalizedRole])\n }\n }\n\n return envMappings\n}\n\nfunction loadGroupRoleMappingsFromEnv(): Map<string, string[]> {\n const raw = process.env.SSO_GROUP_ROLE_MAP\n if (!raw) return new Map()\n\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const out = new Map<string, string[]>()\n for (const [group, roleValue] of Object.entries(parsed)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const roles = normalizeRoleList(roleValue)\n if (roles.length > 0) out.set(normalizedGroup, roles)\n }\n return out\n } catch {\n return new Map()\n }\n}\n\nfunction normalizeRoleList(value: unknown): string[] {\n if (typeof value === 'string') {\n const token = normalizeToken(value)\n return token ? [token] : []\n }\n\n if (Array.isArray(value)) {\n const out = new Set<string>()\n for (const entry of value) {\n const token = normalizeToken(entry)\n if (token) out.add(token)\n }\n return Array.from(out)\n }\n\n return []\n}\n\nfunction normalizeToken(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const normalized = value.trim().toLowerCase()\n return normalized.length > 0 ? normalized : null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { SsoConfig, SsoIdentity, SsoRoleGrant, ScimToken } from '../data/entities'\nimport { emitSsoEvent } from '../events'\nimport { EmailNotVerifiedError } from '../lib/errors'\nimport type { SsoIdentityPayload } from '../lib/types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sso').child({ component: 'account-linking' })\n\nexport class AccountLinkingService {\n constructor(private em: EntityManager) {}\n\n async resolveUser(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId)\n if (existing) {\n await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups)\n return existing\n }\n\n if (idpPayload.emailVerified === false) {\n throw new EmailNotVerifiedError('IdP explicitly reported email as unverified \u2014 cannot link or provision account')\n }\n\n const emailDomain = idpPayload.email.split('@')[1]?.toLowerCase()\n if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {\n throw new Error('Email domain is not in the allowed domains for this SSO configuration')\n }\n\n const emailLinked = config.autoLinkByEmail\n ? await this.linkByEmail(config, idpPayload, tenantId)\n : null\n if (emailLinked) {\n await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups)\n return emailLinked\n }\n\n if (config.jitEnabled) {\n const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0\n if (scimActive) {\n throw new Error('JIT provisioning is disabled because SCIM directory sync is active')\n }\n return this.jitProvision(config, idpPayload, tenantId)\n }\n\n throw new Error('No matching user found and JIT provisioning is disabled')\n }\n\n private async findExistingLink(\n ssoConfigId: string,\n idpSubject: string,\n tenantId: string,\n organizationId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const identity = await findOneWithDecryption(\n this.em,\n SsoIdentity,\n { ssoConfigId, idpSubject, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!identity) return null\n\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n identity.deletedAt = new Date()\n await this.em.flush()\n return null\n }\n\n identity.lastLoginAt = new Date()\n await this.em.flush()\n\n return { user, identity }\n }\n\n private async linkByEmail(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const emailHash = computeEmailHash(idpPayload.email)\n const user = await findOneWithDecryption(\n this.em,\n User,\n {\n organizationId: config.organizationId,\n deletedAt: null,\n $or: [\n { email: idpPayload.email },\n { emailHash },\n ],\n } as FilterQuery<User>,\n {},\n { tenantId, organizationId: config.organizationId },\n )\n if (!user) return null\n\n const now = new Date()\n const identity = this.em.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'manual',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await this.em.persist(identity).flush()\n\n void emitSsoEvent('sso.identity.linked', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n\n return { user, identity }\n }\n\n private async jitProvision(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n return this.em.transactional(async (txEm) => {\n const user = txEm.create(User, {\n tenantId,\n organizationId: config.organizationId,\n email: idpPayload.email,\n emailHash: computeEmailHash(idpPayload.email),\n name: idpPayload.name ?? null,\n passwordHash: null,\n isConfirmed: true,\n createdAt: new Date(),\n })\n await txEm.persist(user).flush()\n\n await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)\n\n const now = new Date()\n const identity = txEm.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'jit',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await txEm.persist(identity).flush()\n\n void emitSsoEvent('sso.identity.created', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((eventError) => logger.error('SSO event emit failed', { err: eventError }))\n\n return { user, identity }\n })\n }\n\n private async assignRolesFromSso(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0\n if (!hasMappings) return\n\n await this.syncMappedRoles(em, user, config, tenantId, idpGroups)\n\n const hasAnySsoRole = await em.findOne(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n if (!hasAnySsoRole) {\n throw new Error('No roles could be resolved from IdP groups \u2014 login denied. Configure role mappings or ensure the IdP sends matching group claims.')\n }\n }\n\n /**\n * Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced\n * with what the IdP sends, while manually-assigned roles are preserved.\n */\n private async syncMappedRoles(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const resolvedTenantId = tenantId || user.tenantId || ''\n if (!resolvedTenantId) return\n\n const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null } as FilterQuery<Role>)\n const roleByNormalizedName = new Map<string, Role>()\n for (const role of allRoles) {\n const normalized = normalizeToken(role.name)\n if (normalized) roleByNormalizedName.set(normalized, role)\n }\n\n // Resolve desired role IDs from IdP groups using merged mappings\n const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings)\n const desiredRoleIds = new Set<string>()\n for (const roleName of desiredRoleNames) {\n const role = roleByNormalizedName.get(roleName)\n if (role) desiredRoleIds.add(role.id)\n }\n\n // Query current SSO grants for this user+config\n const existingGrants = await em.find(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId))\n\n // Compute diff\n const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id))\n const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId))\n\n // Add new roles\n for (const roleId of toAdd) {\n const role = allRoles.find((r) => r.id === roleId)\n if (!role) continue\n await this.ensureUserRole(em, user, role)\n const grant = em.create(SsoRoleGrant, {\n tenantId: resolvedTenantId,\n organizationId: config.organizationId,\n userId: user.id,\n roleId,\n ssoConfigId: config.id,\n } as RequiredEntityData<SsoRoleGrant>)\n em.persist(grant)\n }\n\n // Remove stale SSO-sourced roles\n for (const grant of toRemove) {\n const userRole = await em.findOne(UserRole, {\n user: user.id,\n role: grant.roleId,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (userRole) {\n em.remove(userRole)\n }\n em.remove(grant)\n }\n\n // Clean up orphaned soft-deleted UserRole rows (ghost rows from previous soft-delete logic)\n const allUserRoles = await em.find(UserRole, { user: user.id } as FilterQuery<UserRole>)\n for (const ur of allUserRoles) {\n if (ur.deletedAt) {\n em.remove(ur)\n }\n }\n\n if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {\n await em.flush()\n }\n }\n\n private async ensureUserRole(em: EntityManager, user: User, role: Role): Promise<void> {\n const existingLink = await em.findOne(UserRole, {\n user: user.id,\n role: role.id,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (existingLink) return\n\n const userRole = em.create(UserRole, { user, role, createdAt: new Date() })\n await em.persist(userRole).flush()\n }\n}\n\nfunction resolveRoleNamesFromIdpGroups(\n idpGroups?: string[],\n configMappings?: Record<string, string>,\n): string[] {\n if (!Array.isArray(idpGroups) || idpGroups.length === 0) return []\n\n const normalizedGroups = idpGroups\n .map((group) => normalizeToken(group))\n .filter((group): group is string => group !== null)\n if (normalizedGroups.length === 0) return []\n\n const mergedMappings = loadMergedMappings(configMappings)\n const roleNames = new Set<string>()\n\n for (const group of normalizedGroups) {\n const mapped = mergedMappings.get(group)\n if (!mapped?.length) continue\n\n for (const role of mapped) roleNames.add(role)\n }\n\n return Array.from(roleNames)\n}\n\nfunction loadMergedMappings(configMappings?: Record<string, string>): Map<string, string[]> {\n const mappings = new Map<string, string[]>()\n\n if (configMappings && Object.keys(configMappings).length > 0) {\n for (const [group, roleName] of Object.entries(configMappings)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const normalizedRole = normalizeToken(roleName)\n if (!normalizedRole) continue\n mappings.set(normalizedGroup, [normalizedRole])\n }\n }\n\n return mappings\n}\n\nfunction normalizeToken(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const normalized = value.trim().toLowerCase()\n return normalized.length > 0 ? normalized : null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }