{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "sso-identity-provider-config",
  "title": "Sso Identity Provider Config",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/sso-identity-provider-config/sso-identity-provider-config.component.ts",
      "content": "// Enterprise Single Sign-On (SAML 2.0 / OIDC) identity provider configuration wizard\n// with IdP preset picker, SP credentials, XML metadata dropzone, X.509 cert PEM\n// validator, domain enforcement switch, and interactive SAML test runner.\n// Port of the Vue block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  Check,\n  CheckCircle2,\n  Copy,\n  Download,\n  ExternalLink,\n  FileCode,\n  Globe,\n  KeyRound,\n  Lock,\n  LucideAngularModule,\n  Play,\n  RotateCw,\n  Save,\n  ShieldAlert,\n  ShieldCheck,\n  UploadCloud,\n  UserCheck,\n  X,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport {\n  UiDialogComponent,\n  UiDialogContentComponent,\n  UiDialogDescriptionComponent,\n  UiDialogFooterComponent,\n  UiDialogHeaderComponent,\n  UiDialogTitleComponent,\n} from '@/ui/dialog/dialog.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiSwitchComponent } from '@/ui/switch/switch.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport type IdpPresetId = 'okta' | 'entra' | 'google' | 'custom'\n\ninterface IdpPreset {\n  id: IdpPresetId\n  name: string\n  subtitle: string\n  badge: string\n  defaultSsoUrl: string\n  defaultEntityId: string\n  domain: string\n  defaultCert: string\n}\n\ninterface ClaimAttribute {\n  samlAttribute: string\n  mappedField: string\n  sampleValue: string\n}\n\nconst idpPresets: IdpPreset[] = [\n  {\n    id: 'okta',\n    name: 'Okta',\n    subtitle: 'SAML 2.0 & OIDC Enterprise',\n    badge: 'Popular',\n    defaultSsoUrl: 'https://company.okta.com/app/sso/saml',\n    defaultEntityId: 'http://www.okta.com/exk849201',\n    domain: 'company.okta.com',\n    defaultCert: `-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAX2v5L3oMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJV\nUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsG\nA1UECgwET2t0YTEUMBIGA1UECwwLU1NPIFNlcnZpY2UxGDAWBgNVBAMMD2NvbXBhbnkub2t0\nYS5jb20xIDAeBgkqhkiG9w0BCQEWEWFkbWluQG9rdGEuY29tMB4XDTI2MDgyMTE0MzAwMFoX\nDTI4MTAyMTE0MzAwMFowgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh\n-----END CERTIFICATE-----`,\n  },\n  {\n    id: 'entra',\n    name: 'Microsoft Entra ID',\n    subtitle: 'Azure AD & Microsoft 365',\n    badge: 'Enterprise',\n    defaultSsoUrl: 'https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/saml2',\n    defaultEntityId: 'https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/',\n    domain: 'login.microsoftonline.com',\n    defaultCert: `-----BEGIN CERTIFICATE-----\nMIIDBTCCAe2gAwIBAgIQJn0zJk1vVbtJpZ0R4k0D1DANBgkqhkiG9w0BAQsFADAtMSsw\nKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTI2MDgy\nMTE0MzAwMFoXDTI4MTAyMTE0MzAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNz\nY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n-----END CERTIFICATE-----`,\n  },\n  {\n    id: 'google',\n    name: 'Google Workspace',\n    subtitle: 'Google Cloud Identity SAML',\n    badge: 'SAML 2.0',\n    defaultSsoUrl: 'https://accounts.google.com/o/saml2/idp?idpid=C0123abcd',\n    defaultEntityId: 'https://accounts.google.com/o/saml2?idpid=C0123abcd',\n    domain: 'accounts.google.com',\n    defaultCert: `-----BEGIN CERTIFICATE-----\nMIIDdDCCAlygAwIBAgIGAY7GZpBqMA0GCSqGSIb3DQEBCwUAMHQxCzAJBgNVBAYTAlVT\nMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MR8wHQYD\nVQQKExZHb29nbGUgV29ya3NwYWNlIElBTSExMB8GA1UEAxMYR29vZ2xlIElkUCBTYW1s\nIFNpZ25lcjAeFw0yNjA4MjExNDMwMDBaFw0yODEwMjExNDMwMDBaMHQxCzAJBgNVBAYT\n-----END CERTIFICATE-----`,\n  },\n  {\n    id: 'custom',\n    name: 'Custom SAML 2.0',\n    subtitle: 'PingFederate, OneLogin, Keycloak',\n    badge: 'Universal',\n    defaultSsoUrl: 'https://sso.company.com/idp/profile/SAML2/POST/SSO',\n    defaultEntityId: 'https://sso.company.com/idp/shibboleth',\n    domain: 'sso.company.com',\n    defaultCert: `-----BEGIN CERTIFICATE-----\nMIIDcDCCAligAwIBAgIJAN8FpW1mG+hBMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNVBAYT\nAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ8wDQYDVQQKDAZDdXN0b20xIDAeBgNVBAMM\nF2N1c3RvbS5pZHAucHJvdmlkZXIubGFiMB4XDTI2MDgyMTE0MzAwMFoXDTI4MTAyMTE0\nMzAwMFowWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExDzANBgNVBAoM\n-----END CERTIFICATE-----`,\n  },\n]\n\nconst spAcsUrl = 'https://auth.uipkge.dev/sso/saml/callback'\nconst spEntityId = 'https://auth.uipkge.dev/sso/saml/metadata'\nconst spMetadataXmlUrl = 'https://auth.uipkge.dev/sso/saml/metadata.xml'\nconst spCertificatePem = `-----BEGIN CERTIFICATE-----\nMIIDODCCAiCgAwIBAgIUW0Qz0K7VzN8XlP4wQ6k9J1vYgLAwDQYJKoZIhvcNAQEL\nBQAwMzEXMBUGA1UEAwwNYXV0aC51aXBrZ2UuZGV2MRgwFgYDVQQKDA9VSVBLR0Ug\nQXV0aCBTU08wHhcNMjYwODIxMTQzMDAwWhcNMzEwODIxMTQzMDAwWjAzMRcwFQYD\nVQQDDA5hdXRoLnVpcGtnZS5kZXYxGDAWBgNVBAoMD1VJUEtHRSBBdXRoIFNTTzCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL1K9sU3FvX+7nQ6m2W4j1k9\n-----END CERTIFICATE-----`\n\nconst simulatedClaims: ClaimAttribute[] = [\n  { samlAttribute: 'urn:oid:0.9.2342.19200300.100.1.1', mappedField: 'nameID', sampleValue: 'alex.morgan@company.com' },\n  {\n    samlAttribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',\n    mappedField: 'email',\n    sampleValue: 'alex.morgan@company.com',\n  },\n  {\n    samlAttribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',\n    mappedField: 'firstName',\n    sampleValue: 'Alex',\n  },\n  {\n    samlAttribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',\n    mappedField: 'lastName',\n    sampleValue: 'Morgan',\n  },\n  {\n    samlAttribute: 'http://schemas.xmlsoap.org/claims/Group',\n    mappedField: 'groups',\n    sampleValue: '[\"Engineering\", \"IAM-Admins\", \"SSO-Users\"]',\n  },\n  {\n    samlAttribute: 'urn:oasis:names:tc:SAML:2.0:assertion:SessionIndex',\n    mappedField: 'sessionIndex',\n    sampleValue: '_sess_8f29ab0192e47',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sso-identity-provider-config, [ui-sso-identity-provider-config]',\n  standalone: true,\n  // Vue renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiDialogComponent,\n    UiDialogContentComponent,\n    UiDialogDescriptionComponent,\n    UiDialogFooterComponent,\n    UiDialogHeaderComponent,\n    UiDialogTitleComponent,\n    UiInputComponent,\n    UiSeparatorComponent,\n    UiSwitchComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"sso-identity-provider-config\" [class]=\"rootClass\">\n      <!-- Header Banner & Top Controls -->\n      <div ui-card class=\"border-border bg-card/80 shadow-xs backdrop-blur-xs\">\n        <div ui-card-header class=\"pb-4\">\n          <div class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n            <div class=\"space-y-1\">\n              <div class=\"flex flex-wrap items-center gap-2.5\">\n                <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                  <lucide-icon [img]=\"KeyRound\" class=\"size-4\" />\n                </div>\n                <h3 ui-card-title class=\"text-base font-semibold\">Single Sign-On (SSO) & SAML 2.0 Configuration</h3>\n                @if (ssoEnabled()) {\n                  <span ui-badge variant=\"success\" class=\"font-mono text-xs\">\n                    <span class=\"bg-success mr-1.5 inline-block size-2 animate-pulse rounded-full\"></span>\n                    SSO Active\n                  </span>\n                } @else {\n                  <span ui-badge variant=\"outline\" class=\"text-muted-foreground border-border font-mono text-xs\">\n                    Disabled\n                  </span>\n                }\n              </div>\n              <p ui-card-description class=\"text-xs\">\n                Connect your enterprise IdP (Okta, Azure AD / Entra, Google Workspace, OneLogin) for federated SAML\n                authentication.\n              </p>\n            </div>\n\n            <div class=\"flex flex-wrap items-center gap-3\">\n              <div class=\"border-border bg-muted/30 flex items-center gap-3 rounded-lg border px-3 py-1.5\">\n                <div class=\"text-right\">\n                  <p class=\"text-foreground text-xs font-medium\">SSO Status</p>\n                  <p class=\"text-muted-foreground text-xs\">\n                    {{ ssoEnabled() ? 'Enabled for @company.com' : 'Disabled' }}\n                  </p>\n                </div>\n                <button\n                  ui-switch\n                  [checked]=\"ssoEnabled()\"\n                  (checkedChange)=\"ssoEnabled.set($event)\"\n                  aria-label=\"Toggle Single Sign-On\"\n                ></button>\n              </div>\n\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"gap-1.5 text-xs font-medium\"\n                [disabled]=\"!ssoEnabled()\"\n                (click)=\"openTestModal()\"\n              >\n                <lucide-icon [img]=\"Play\" class=\"size-3.5\" />\n                <span>Test SAML Login</span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Save Success Banner -->\n      @if (saveSuccessNotice()) {\n        <div\n          class=\"border-success/30 bg-success/10 text-success flex items-center justify-between rounded-lg border px-4 py-3 text-xs\"\n          role=\"status\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <lucide-icon [img]=\"CheckCircle2\" class=\"size-4 shrink-0\" />\n            <span>\n              SSO configuration saved successfully. All assertions will validate against {{ currentPreset().name }}.\n            </span>\n          </div>\n          <button\n            type=\"button\"\n            class=\"text-success hover:opacity-75\"\n            aria-label=\"Dismiss alert\"\n            (click)=\"saveSuccessNotice.set(false)\"\n          >\n            <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n          </button>\n        </div>\n      }\n\n      <!-- IdP Preset Picker Grid -->\n      <div class=\"space-y-3\">\n        <div class=\"flex items-center justify-between\">\n          <div class=\"space-y-0.5\">\n            <h3 class=\"text-foreground text-xs font-semibold tracking-tight uppercase\">Identity Provider Presets</h3>\n            <p class=\"text-muted-foreground text-xs\">\n              Choose your identity platform to load SAML endpoints and formatting defaults\n            </p>\n          </div>\n          <span ui-badge variant=\"outline\" class=\"border-border font-mono text-xs\">SAML 2.0 / Web SSO</span>\n        </div>\n\n        <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n          @for (preset of presets; track preset.id) {\n            <button type=\"button\" [class]=\"presetCardClass(preset.id)\" (click)=\"handleSelectPreset(preset.id)\">\n              <div class=\"space-y-2\">\n                <div class=\"flex items-center justify-between\">\n                  <div [class]=\"presetIconClass(preset.id)\">\n                    @if (preset.id === 'okta') {\n                      <span>O</span>\n                    } @else if (preset.id === 'entra') {\n                      <span>⊞</span>\n                    } @else if (preset.id === 'google') {\n                      <span>G</span>\n                    } @else {\n                      <lucide-icon [img]=\"Globe\" class=\"size-4\" />\n                    }\n                  </div>\n                  <span\n                    ui-badge\n                    [variant]=\"selectedPresetId() === preset.id ? 'default' : 'secondary'\"\n                    class=\"font-mono text-xs\"\n                  >\n                    {{ preset.badge }}\n                  </span>\n                </div>\n\n                <div>\n                  <p class=\"text-foreground text-sm font-semibold\">{{ preset.name }}</p>\n                  <p class=\"text-muted-foreground mt-0.5 text-xs\">{{ preset.subtitle }}</p>\n                </div>\n              </div>\n\n              <div class=\"border-border/60 mt-4 flex items-center justify-between border-t pt-2 text-xs\">\n                <span class=\"text-muted-foreground max-w-[140px] truncate font-mono text-xs\">{{ preset.domain }}</span>\n                @if (selectedPresetId() === preset.id) {\n                  <span class=\"text-primary flex items-center gap-1 text-xs font-medium\">\n                    <lucide-icon [img]=\"Check\" class=\"size-3\" />\n                    <span>Active</span>\n                  </span>\n                } @else {\n                  <span class=\"text-muted-foreground group-hover:text-foreground text-xs\">Select →</span>\n                }\n              </div>\n            </button>\n          }\n        </div>\n      </div>\n\n      <!-- 2-Column Configuration Form -->\n      <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n        <!-- Left Column: Service Provider (SP) Credentials -->\n        <div class=\"space-y-6 lg:col-span-6\">\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"ShieldCheck\" class=\"size-4\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Service Provider (SP) Credentials</h3>\n                    <p ui-card-description class=\"text-xs\">\n                      Copy these values into your Identity Provider's SAML setup\n                    </p>\n                  </div>\n                </div>\n                <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">Our Endpoints</span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-0\">\n              <div class=\"space-y-1.5\">\n                <div class=\"flex items-center justify-between\">\n                  <label for=\"angular-sp-acs-url\" class=\"text-foreground text-xs font-medium\">\n                    Assertion Consumer Service (ACS) URL\n                  </label>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                    (click)=\"copyText(spAcsUrlValue, 'acs')\"\n                  >\n                    @if (copiedKey() === 'acs') {\n                      <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                    }\n                    <span>{{ copiedKey() === 'acs' ? 'Copied' : 'Copy' }}</span>\n                  </button>\n                </div>\n                <ui-input\n                  id=\"angular-sp-acs-url\"\n                  [value]=\"spAcsUrlValue\"\n                  [readonly]=\"true\"\n                  class=\"bg-muted/30 font-mono text-xs select-all\"\n                />\n                <p class=\"text-muted-foreground text-xs\">\n                  The callback URL where your IdP transmits HTTP-POST SAML assertion responses.\n                </p>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <div class=\"flex items-center justify-between\">\n                  <label for=\"angular-sp-entity-id\" class=\"text-foreground text-xs font-medium\">\n                    SP Entity ID / Audience URI\n                  </label>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                    (click)=\"copyText(spEntityIdValue, 'entity-id')\"\n                  >\n                    @if (copiedKey() === 'entity-id') {\n                      <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                    }\n                    <span>{{ copiedKey() === 'entity-id' ? 'Copied' : 'Copy' }}</span>\n                  </button>\n                </div>\n                <ui-input\n                  id=\"angular-sp-entity-id\"\n                  [value]=\"spEntityIdValue\"\n                  [readonly]=\"true\"\n                  class=\"bg-muted/30 font-mono text-xs select-all\"\n                />\n                <p class=\"text-muted-foreground text-xs\">\n                  The unique audience restriction URI identifying our service to your IdP.\n                </p>\n              </div>\n\n              <div class=\"border-border bg-muted/20 space-y-2 rounded-lg border p-3\">\n                <p class=\"text-foreground text-xs font-medium\">Protocol & Binding Requirements</p>\n                <div class=\"grid grid-cols-2 gap-2 text-xs\">\n                  <div>\n                    <span class=\"text-muted-foreground block text-xs\">NameID Format</span>\n                    <span class=\"text-foreground font-mono text-xs font-medium\">EmailAddress (urn:oasis:...)</span>\n                  </div>\n                  <div>\n                    <span class=\"text-muted-foreground block text-xs\">SAML Binding</span>\n                    <span class=\"text-foreground font-mono text-xs font-medium\">HTTP-POST</span>\n                  </div>\n                  <div>\n                    <span class=\"text-muted-foreground block text-xs\">Signing Algorithm</span>\n                    <span class=\"text-foreground font-mono text-xs font-medium\">RSA-SHA256</span>\n                  </div>\n                  <div>\n                    <span class=\"text-muted-foreground block text-xs\">Assertion Encryption</span>\n                    <span class=\"text-foreground font-mono text-xs font-medium\">Optional (AES-256-GCM)</span>\n                  </div>\n                </div>\n              </div>\n\n              <ui-separator />\n\n              <div class=\"space-y-2\">\n                <div class=\"flex items-center justify-between\">\n                  <label class=\"text-foreground text-xs font-medium\">SP X.509 Public Certificate</label>\n                  <div class=\"flex items-center gap-1.5\">\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                      (click)=\"copyText(spCertificatePemValue, 'sp-cert')\"\n                    >\n                      @if (copiedKey() === 'sp-cert') {\n                        <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                      } @else {\n                        <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                      }\n                      <span>{{ copiedKey() === 'sp-cert' ? 'Copied' : 'Copy PEM' }}</span>\n                    </button>\n                    <button\n                      ui-button\n                      aria-label=\"Download attachment\"\n                      variant=\"outline\"\n                      size=\"sm\"\n                      class=\"h-6 gap-1 px-2 text-xs\"\n                      (click)=\"downloadSpCertificate()\"\n                    >\n                      <lucide-icon [img]=\"Download\" class=\"size-3\" />\n                      <span>Download .crt</span>\n                    </button>\n                  </div>\n                </div>\n\n                <pre\n                  class=\"border-border bg-muted/40 text-foreground max-h-[140px] overflow-x-auto rounded-md border p-3 font-mono text-xs leading-relaxed select-all\"\n                ><code>{{ spCertificatePemValue }}</code></pre>\n                <p class=\"text-muted-foreground text-xs\">\n                  Import this certificate into your IdP if assertion signing or encryption is required.\n                </p>\n              </div>\n            </div>\n\n            <div\n              ui-card-footer\n              class=\"border-border text-muted-foreground flex items-center justify-between border-t pt-3 text-xs\"\n            >\n              <span class=\"flex items-center gap-1.5\">\n                <lucide-icon [img]=\"FileCode\" class=\"size-3.5\" />\n                <span>SP Metadata XML Endpoint</span>\n              </span>\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"sm\"\n                class=\"text-primary hover:text-primary/80 h-6 gap-1 px-2 text-xs\"\n                (click)=\"copyText(spMetadataXmlUrlValue, 'meta-url')\"\n              >\n                <span>{{ copiedKey() === 'meta-url' ? 'Copied URL' : 'Copy metadata.xml URL' }}</span>\n                <lucide-icon [img]=\"ExternalLink\" class=\"size-3\" />\n              </button>\n            </div>\n          </div>\n        </div>\n\n        <!-- Right Column: Identity Provider (IdP) Metadata Form -->\n        <div class=\"space-y-6 lg:col-span-6\">\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"Lock\" class=\"size-4\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Identity Provider (IdP) Metadata</h3>\n                    <p ui-card-description class=\"text-xs\">\n                      Configure SAML endpoints and credentials provided by {{ currentPreset().name }}\n                    </p>\n                  </div>\n                </div>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">{{ currentPreset().name }}</span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-0\">\n              <div\n                [class]=\"dropzoneClass\"\n                (dragover)=\"handleXmlDragOver($event)\"\n                (dragleave)=\"handleXmlDragLeave()\"\n                (drop)=\"handleXmlDrop($event)\"\n                (click)=\"simulateXmlUpload()\"\n              >\n                <div class=\"bg-primary/10 text-primary mb-2 grid size-9 place-items-center rounded-full\">\n                  <lucide-icon [img]=\"UploadCloud\" class=\"size-4\" />\n                </div>\n                <p class=\"text-foreground text-xs font-semibold\">\n                  Drag & drop IdP <span class=\"font-mono\">metadata.xml</span> to auto-fill\n                </p>\n                <p class=\"text-muted-foreground mt-0.5 text-xs\">\n                  Or click here to simulate parsing your Identity Provider metadata file\n                </p>\n              </div>\n\n              @if (xmlUploadedNotice()) {\n                <div\n                  class=\"border-success/30 bg-success/10 text-success flex items-center gap-2 rounded-lg border p-2.5 text-xs\"\n                >\n                  <lucide-icon [img]=\"CheckCircle2\" class=\"size-4 shrink-0\" />\n                  <span>Parsed SSO URL, Entity ID, and X.509 Certificate from metadata XML.</span>\n                </div>\n              }\n\n              <div class=\"space-y-1.5\">\n                <label for=\"angular-idp-sso-url\" class=\"text-foreground text-xs font-medium\">\n                  IdP Single Sign-On (SSO) URL\n                </label>\n                <ui-input\n                  id=\"angular-idp-sso-url\"\n                  [value]=\"idpSsoUrl()\"\n                  (valueChange)=\"idpSsoUrl.set($event)\"\n                  placeholder=\"https://company.okta.com/app/sso/saml\"\n                  class=\"font-mono text-xs\"\n                />\n                <p class=\"text-muted-foreground text-xs\">\n                  The HTTP-POST endpoint at your IdP where users are redirected for authentication.\n                </p>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <label for=\"angular-idp-entity-id\" class=\"text-foreground text-xs font-medium\">\n                  IdP Entity ID / Issuer URI\n                </label>\n                <ui-input\n                  id=\"angular-idp-entity-id\"\n                  [value]=\"idpEntityId()\"\n                  (valueChange)=\"idpEntityId.set($event)\"\n                  placeholder=\"http://www.okta.com/exk849201\"\n                  class=\"font-mono text-xs\"\n                />\n                <p class=\"text-muted-foreground text-xs\">\n                  The unique issuer string provided in your IdP metadata document.\n                </p>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <div class=\"flex items-center justify-between\">\n                  <label for=\"angular-idp-cert-pem\" class=\"text-foreground text-xs font-medium\">\n                    IdP X.509 Signing Certificate (PEM)\n                  </label>\n                  <span ui-badge variant=\"outline\" class=\"border-success/40 text-success font-mono text-xs\">\n                    Valid (Expires Oct 2028)\n                  </span>\n                </div>\n                <ui-textarea\n                  id=\"angular-idp-cert-pem\"\n                  [value]=\"idpCertPem()\"\n                  (valueChange)=\"idpCertPem.set($event)\"\n                  [rows]=\"5\"\n                  class=\"border-border/70 font-mono text-xs leading-relaxed\"\n                  placeholder=\"-----BEGIN CERTIFICATE-----...\"\n                />\n                <p class=\"text-muted-foreground text-xs\">\n                  Public X.509 certificate used by our SP to verify the cryptographic signature on SAML assertions.\n                </p>\n              </div>\n\n              <ui-separator />\n\n              <div class=\"border-border bg-muted/20 flex items-start justify-between gap-3 rounded-lg border p-3\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"flex items-center gap-1.5\">\n                    <lucide-icon [img]=\"ShieldAlert\" class=\"text-primary size-4\" />\n                    <p class=\"text-foreground text-xs font-semibold\">Enforce SSO for all domain emails</p>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Require all users with <span class=\"text-foreground font-mono font-medium\">@company.com</span> email\n                    addresses to log in via SSO. Password logins will be blocked.\n                  </p>\n                </div>\n                <button\n                  ui-switch\n                  class=\"mt-1\"\n                  [checked]=\"enforceDomain()\"\n                  (checkedChange)=\"enforceDomain.set($event)\"\n                  aria-label=\"Enforce SSO for domain\"\n                ></button>\n              </div>\n            </div>\n\n            <div ui-card-footer class=\"border-border flex flex-wrap items-center justify-between gap-2 border-t pt-4\">\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"sm\"\n                class=\"text-muted-foreground hover:text-foreground text-xs\"\n                (click)=\"handleSelectPreset(selectedPresetId())\"\n              >\n                Reset to Defaults\n              </button>\n              <button\n                ui-button\n                size=\"sm\"\n                class=\"gap-1.5 text-xs font-medium shadow-xs\"\n                [disabled]=\"isSaving() || !idpSsoUrl().trim() || !idpEntityId().trim()\"\n                (click)=\"handleSaveConfig()\"\n              >\n                @if (isSaving()) {\n                  <lucide-icon [img]=\"RotateCw\" class=\"size-3.5 animate-spin\" />\n                } @else {\n                  <lucide-icon [img]=\"Save\" class=\"size-3.5\" />\n                }\n                <span>{{ isSaving() ? 'Saving...' : 'Save SSO Configuration' }}</span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Test SAML Login Diagnostic Dialog -->\n      <ui-dialog [open]=\"testModalOpen()\" (openChange)=\"testModalOpen.set($event)\">\n        <ui-dialog-content class=\"sm:max-w-xl\">\n          <div ui-dialog-header>\n            <div class=\"flex items-center gap-2\">\n              <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                <lucide-icon [img]=\"UserCheck\" class=\"size-4\" />\n              </div>\n              <div>\n                <h2 ui-dialog-title>SAML 2.0 Authentication Diagnostic</h2>\n                <p ui-dialog-description>Live assertion handshake simulation with {{ currentPreset().name }}</p>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"space-y-4 py-2\">\n            <div\n              class=\"border-success/30 bg-success/10 text-success flex items-center justify-between rounded-lg border p-3 text-xs\"\n            >\n              <div class=\"flex items-center gap-2\">\n                <lucide-icon [img]=\"CheckCircle2\" class=\"size-4 shrink-0\" />\n                <div>\n                  <p class=\"font-semibold\">HTTP 200 OK — SAML Handshake Validated</p>\n                  <p class=\"text-xs opacity-90\">All signature envelopes matched X.509 certificate</p>\n                </div>\n              </div>\n              <span ui-badge variant=\"success\" class=\"font-mono text-xs\">Success</span>\n            </div>\n\n            <div class=\"border-border bg-muted/20 space-y-2 rounded-lg border p-3 text-xs\">\n              <h4 class=\"text-foreground font-semibold\">Verification Pipeline</h4>\n              <div class=\"space-y-1.5\">\n                <div class=\"text-foreground flex items-center gap-2\">\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5 shrink-0\" />\n                  <span>\n                    1. AuthnRequest dispatched with SP signature to\n                    <code class=\"font-mono text-xs\">{{ idpSsoUrl() }}</code>\n                  </span>\n                </div>\n                <div class=\"text-foreground flex items-center gap-2\">\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5 shrink-0\" />\n                  <span>2. SAMLResponse assertion received and verified with RSA-SHA256 signature</span>\n                </div>\n                <div class=\"text-foreground flex items-center gap-2\">\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5 shrink-0\" />\n                  <span>\n                    3. AudienceRestriction matched <code class=\"font-mono text-xs\">{{ spEntityIdValue }}</code>\n                  </span>\n                </div>\n                <div class=\"text-foreground flex items-center gap-2\">\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5 shrink-0\" />\n                  <span>4. Assertion timestamp valid within 300s clock-skew tolerance window</span>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"space-y-1.5\">\n              <h4 class=\"text-foreground text-xs font-semibold\">Asserted Identity Claims & Attributes</h4>\n              <div class=\"border-border bg-muted/10 overflow-hidden rounded-md border text-xs\">\n                <table class=\"w-full\">\n                  <thead>\n                    <tr class=\"border-border bg-muted/40 text-muted-foreground border-b text-left font-medium\">\n                      <th class=\"p-2 text-xs\">Field</th>\n                      <th class=\"p-2 text-xs\">Sample Claim Value</th>\n                    </tr>\n                  </thead>\n                  <tbody class=\"divide-border divide-y font-mono text-xs\">\n                    @for (claim of claims; track claim.mappedField) {\n                      <tr class=\"hover:bg-muted/20\">\n                        <td class=\"text-primary p-2 font-semibold\">{{ claim.mappedField }}</td>\n                        <td class=\"text-foreground max-w-[240px] truncate p-2\">{{ claim.sampleValue }}</td>\n                      </tr>\n                    }\n                  </tbody>\n                </table>\n              </div>\n            </div>\n          </div>\n\n          <div ui-dialog-footer>\n            <button ui-button variant=\"outline\" size=\"sm\" (click)=\"testModalOpen.set(false)\">Close</button>\n            <button ui-button size=\"sm\" class=\"gap-1.5\" (click)=\"openTestModal()\">\n              <lucide-icon [img]=\"RotateCw\" class=\"size-3.5\" />\n              <span>Re-test Handshake</span>\n            </button>\n          </div>\n        </ui-dialog-content>\n      </ui-dialog>\n    </div>\n  `,\n})\nexport class UiSsoIdentityProviderConfigComponent {\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Copy = Copy\n  protected readonly Download = Download\n  protected readonly ExternalLink = ExternalLink\n  protected readonly FileCode = FileCode\n  protected readonly Globe = Globe\n  protected readonly KeyRound = KeyRound\n  protected readonly Lock = Lock\n  protected readonly Play = Play\n  protected readonly RotateCw = RotateCw\n  protected readonly Save = Save\n  protected readonly ShieldAlert = ShieldAlert\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly UploadCloud = UploadCloud\n  protected readonly UserCheck = UserCheck\n  protected readonly X = X\n  protected readonly presets = idpPresets\n  protected readonly claims = simulatedClaims\n  protected readonly spAcsUrlValue = spAcsUrl\n  protected readonly spEntityIdValue = spEntityId\n  protected readonly spMetadataXmlUrlValue = spMetadataXmlUrl\n  protected readonly spCertificatePemValue = spCertificatePem\n\n  @Input() set initialSsoEnabled(v: boolean) {\n    this.ssoEnabled.set(v ?? true)\n  }\n  @Input() set initialPreset(v: IdpPresetId) {\n    if (v) this.handleSelectPreset(v)\n  }\n  @Input() set initialEnforceDomain(v: boolean) {\n    this.enforceDomain.set(v ?? true)\n  }\n  @Input('class') className?: string\n\n  readonly ssoEnabled = signal(true)\n  readonly selectedPresetId = signal<IdpPresetId>('okta')\n  readonly enforceDomain = signal(true)\n\n  readonly currentPreset = computed(() => idpPresets.find((p) => p.id === this.selectedPresetId()) ?? idpPresets[0]!)\n\n  readonly idpSsoUrl = signal(idpPresets[0]!.defaultSsoUrl)\n  readonly idpEntityId = signal(idpPresets[0]!.defaultEntityId)\n  readonly idpCertPem = signal(idpPresets[0]!.defaultCert)\n\n  readonly copiedKey = signal<string | null>(null)\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n\n  readonly isDraggingXml = signal(false)\n  readonly xmlUploadedNotice = signal(false)\n\n  readonly isSaving = signal(false)\n  readonly saveSuccessNotice = signal(false)\n\n  readonly testModalOpen = signal(false)\n  readonly isTesting = signal(false)\n\n  get rootClass(): string {\n    return cn('w-full space-y-6', this.className)\n  }\n\n  get dropzoneClass(): string {\n    return cn(\n      'border-border relative flex cursor-pointer flex-col items-center justify-center rounded-xl border border-dashed p-4 text-center transition-colors',\n      this.isDraggingXml() ? 'border-primary bg-primary/10' : 'bg-muted/20 hover:border-primary/50 hover:bg-muted/40',\n    )\n  }\n\n  presetCardClass(id: IdpPresetId): string {\n    return cn(\n      'group relative flex flex-col justify-between rounded-xl border p-4 text-left transition-colors duration-150',\n      'focus-visible:ring-ring cursor-pointer focus-visible:ring-2 focus-visible:outline-none',\n      this.selectedPresetId() === id\n        ? 'border-primary bg-primary/5 ring-primary shadow-xs ring-1'\n        : 'border-border bg-card hover:border-primary/40 hover:bg-accent/40',\n    )\n  }\n\n  presetIconClass(id: IdpPresetId): string {\n    return cn(\n      'flex size-8 items-center justify-center rounded-lg text-xs font-bold transition-colors',\n      this.selectedPresetId() === id\n        ? 'bg-primary text-primary-foreground'\n        : 'bg-muted text-foreground group-hover:bg-primary/10 group-hover:text-primary',\n    )\n  }\n\n  async copyText(text: string, key: string): Promise<void> {\n    try {\n      await navigator.clipboard.writeText(text)\n      this.copiedKey.set(key)\n      clearTimeout(this.copyTimer)\n      this.copyTimer = setTimeout(() => {\n        if (this.copiedKey() === key) this.copiedKey.set(null)\n      }, 2000)\n    } catch {\n      // Clipboard unavailable\n    }\n  }\n\n  handleSelectPreset(presetId: IdpPresetId): void {\n    this.selectedPresetId.set(presetId)\n    const preset = idpPresets.find((p) => p.id === presetId)\n    if (preset) {\n      this.idpSsoUrl.set(preset.defaultSsoUrl)\n      this.idpEntityId.set(preset.defaultEntityId)\n      this.idpCertPem.set(preset.defaultCert)\n      this.xmlUploadedNotice.set(false)\n    }\n  }\n\n  downloadSpCertificate(): void {\n    if (typeof window === 'undefined') return\n    const blob = new Blob([spCertificatePem], { type: 'application/x-pem-file' })\n    const url = URL.createObjectURL(blob)\n    const anchor = document.createElement('a')\n    anchor.href = url\n    anchor.download = 'uipkge-sp-certificate.crt'\n    document.body.appendChild(anchor)\n    anchor.click()\n    document.body.removeChild(anchor)\n    URL.revokeObjectURL(url)\n  }\n\n  handleXmlDrop(e: DragEvent): void {\n    e.preventDefault()\n    this.isDraggingXml.set(false)\n    this.simulateXmlUpload()\n  }\n\n  handleXmlDragOver(e: DragEvent): void {\n    e.preventDefault()\n    this.isDraggingXml.set(true)\n  }\n\n  handleXmlDragLeave(): void {\n    this.isDraggingXml.set(false)\n  }\n\n  simulateXmlUpload(): void {\n    const preset = this.currentPreset()\n    this.idpSsoUrl.set(preset.defaultSsoUrl)\n    this.idpEntityId.set(preset.defaultEntityId)\n    this.idpCertPem.set(preset.defaultCert)\n    this.xmlUploadedNotice.set(true)\n    setTimeout(() => this.xmlUploadedNotice.set(false), 4000)\n  }\n\n  handleSaveConfig(): void {\n    if (this.isSaving()) return\n    this.isSaving.set(true)\n    setTimeout(() => {\n      this.isSaving.set(false)\n      this.saveSuccessNotice.set(true)\n      setTimeout(() => this.saveSuccessNotice.set(false), 3500)\n    }, 600)\n  }\n\n  openTestModal(): void {\n    this.testModalOpen.set(true)\n    this.isTesting.set(true)\n    setTimeout(() => this.isTesting.set(false), 500)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/sso-identity-provider-config/sso-identity-provider-config.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/sso-identity-provider-config/index.ts",
      "content": "export { UiSsoIdentityProviderConfigComponent, type IdpPresetId } from './sso-identity-provider-config.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/sso-identity-provider-config/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/dialog.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/switch.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "Enterprise Single Sign-On (SAML 2.0 / OIDC) identity provider configuration wizard with IdP preset picker, SP credentials, XML metadata dropzone, X.509 cert PEM validator, domain enforcement switch, and interactive SAML test runner.",
  "categories": [
    "security",
    "app",
    "form",
    "dashboard"
  ]
}