{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "provider-aws-zod",
  "title": "AWS EKS Provider (Zod)",
  "description": "A self-contained form component that emits an OidcPolicy for an AWS EKS workload: cluster OIDC issuer URL, Kubernetes namespace and service account, and audience. EKS is AWS's OIDC workload identity — plain EC2 and Lambda do not mint identity tokens. Validated with Zod.",
  "dependencies": [
    "zod"
  ],
  "registryDependencies": [
    "https://ui.shadcn.com/oidc/r/policy.json",
    "https://ui.shadcn.com/oidc/r/claims.json",
    "button",
    "field",
    "input"
  ],
  "files": [
    {
      "path": "registry/oidc/flavors/none-zod/provider-aws.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { z } from \"zod\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  ClaimChip,\n  ClaimsList,\n  claimRowsFromPolicy,\n  claimsListValid,\n  mergeClaimRows,\n} from \"@/components/oidc/claims\"\nimport type { OidcPolicy } from \"@/lib/oidc/policy\"\n\nfunction AwsIcon({ ...props }: React.ComponentProps<\"svg\">) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n      <path d=\"M10.204 14.35l.007.01-.999 2.413a5.171 5.171 0 0 1-2.075-2.597l2.578-.437.004.005a.44.44 0 0 1 .484.606zm-.833-2.129a.44.44 0 0 0 .173-.756l.002-.011L7.585 9.7a5.143 5.143 0 0 0-.73 3.255l2.514-.725.002-.009zm1.145-1.98a.44.44 0 0 0 .699-.337l.01-.005.15-2.62a5.144 5.144 0 0 0-3.01 1.442l2.147 1.523.004-.002zm.76 2.75l.723.349.722-.347.18-.78-.5-.623h-.804l-.5.623.179.779zm1.5-3.095a.44.44 0 0 0 .7.336l.008.003 2.134-1.513a5.188 5.188 0 0 0-2.992-1.442l.148 2.615.002.001zm10.876 5.97l-5.773 7.181a1.6 1.6 0 0 1-1.248.594l-9.261.003a1.6 1.6 0 0 1-1.247-.596l-5.776-7.18a1.583 1.583 0 0 1-.307-1.34L2.1 5.573c.108-.47.425-.864.863-1.073L11.305.513a1.606 1.606 0 0 1 1.385 0l8.345 3.985c.438.209.755.604.863 1.073l2.062 8.955c.108.47-.005.963-.308 1.34zm-3.289-2.057c-.042-.01-.103-.026-.145-.034-.174-.033-.315-.025-.479-.038-.35-.037-.638-.067-.895-.148-.105-.04-.18-.165-.216-.216l-.201-.059a6.45 6.45 0 0 0-.105-2.332 6.465 6.465 0 0 0-.936-2.163c.052-.047.15-.133.177-.159.008-.09.001-.183.094-.282.197-.185.444-.338.743-.522.142-.084.273-.137.415-.242.032-.024.076-.062.11-.089.24-.191.295-.52.123-.736-.172-.216-.506-.236-.745-.045-.034.027-.08.062-.111.088-.134.116-.217.23-.33.35-.246.25-.45.458-.673.609-.097.056-.239.037-.303.033l-.19.135a6.545 6.545 0 0 0-4.146-2.003l-.012-.223c-.065-.062-.143-.115-.163-.25-.022-.268.015-.557.057-.905.023-.163.061-.298.068-.475.001-.04-.001-.099-.001-.142 0-.306-.224-.555-.5-.555-.275 0-.499.249-.499.555l.001.014c0 .041-.002.092 0 .128.006.177.044.312.067.475.042.348.078.637.056.906a.545.545 0 0 1-.162.258l-.012.211a6.424 6.424 0 0 0-4.166 2.003 8.373 8.373 0 0 1-.18-.128c-.09.012-.18.04-.297-.029-.223-.15-.427-.358-.673-.608-.113-.12-.195-.234-.329-.349-.03-.026-.077-.062-.111-.088a.594.594 0 0 0-.348-.132.481.481 0 0 0-.398.176c-.172.216-.117.546.123.737l.007.005.104.083c.142.105.272.159.414.242.299.185.546.338.743.522.076.082.09.226.1.288l.16.143a6.462 6.462 0 0 0-1.02 4.506l-.208.06c-.055.072-.133.184-.215.217-.257.081-.546.11-.895.147-.164.014-.305.006-.48.039-.037.007-.09.02-.133.03l-.004.002-.007.002c-.295.071-.484.342-.423.608.061.267.349.429.645.365l.007-.001.01-.003.129-.029c.17-.046.294-.113.448-.172.33-.118.604-.217.87-.256.112-.009.23.069.288.101l.217-.037a6.5 6.5 0 0 0 2.88 3.596l-.09.218c.033.084.069.199.044.282-.097.252-.263.517-.452.813-.091.136-.185.242-.268.399-.02.037-.045.095-.064.134-.128.275-.034.591.213.71.248.12.556-.007.69-.282v-.002c.02-.039.046-.09.062-.127.07-.162.094-.301.144-.458.132-.332.205-.68.387-.897.05-.06.13-.082.215-.105l.113-.205a6.453 6.453 0 0 0 4.609.012l.106.192c.086.028.18.042.256.155.136.232.229.507.342.84.05.156.074.295.145.457.016.037.043.09.062.129.133.276.442.402.69.282.247-.118.341-.435.213-.71-.02-.039-.045-.096-.065-.134-.083-.156-.177-.261-.268-.398-.19-.296-.346-.541-.443-.793-.04-.13.007-.21.038-.294-.018-.022-.059-.144-.083-.202a6.499 6.499 0 0 0 2.88-3.622c.064.01.176.03.213.038.075-.05.144-.114.28-.104.266.039.54.138.87.256.154.06.277.128.448.173.036.01.088.019.13.028l.009.003.007.001c.297.064.584-.098.645-.365.06-.266-.128-.537-.423-.608zM16.4 9.701l-1.95 1.746v.005a.44.44 0 0 0 .173.757l.003.01 2.526.728a5.199 5.199 0 0 0-.108-1.674A5.208 5.208 0 0 0 16.4 9.7zm-4.013 5.325a.437.437 0 0 0-.404-.232.44.44 0 0 0-.372.233h-.002l-1.268 2.292a5.164 5.164 0 0 0 3.326.003l-1.27-2.296h-.01zm1.888-1.293a.44.44 0 0 0-.27.036.44.44 0 0 0-.214.572l-.003.004 1.01 2.438a5.15 5.15 0 0 0 2.081-2.615l-2.6-.44-.004.005z\" />\n    </svg>\n  )\n}\n\nconst issuerSchema = z\n  .string()\n  .min(1, \"Paste the cluster issuer URL.\")\n  .regex(\n    /^https:\\/\\/[^?#\\s]+$/,\n    \"Enter an https URL without query or fragment.\"\n  )\nconst namespaceSchema = z\n  .string()\n  .min(1, \"Enter the namespace.\")\n  .regex(\n    /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,\n    \"Lowercase letters, digits, and hyphens.\"\n  )\nconst serviceAccountSchema = z\n  .string()\n  .min(1, \"Enter the service account.\")\n  .regex(\n    /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,\n    \"Lowercase letters, digits, and hyphens.\"\n  )\nconst audienceSchema = z.string().min(1, \"Enter the audience.\")\nconst extraClaimsSchema = z.array(\n  z.object({ name: z.string(), values: z.string() })\n)\n\nconst schema = z.object({\n  issuer: issuerSchema,\n  namespace: namespaceSchema,\n  serviceAccount: serviceAccountSchema,\n  audience: audienceSchema,\n  extraClaims: extraClaimsSchema,\n})\n\ntype Fields = z.infer<typeof schema>\n\nconst DEFAULTS: Fields = {\n  issuer: \"\",\n  namespace: \"\",\n  serviceAccount: \"\",\n  audience: \"\",\n  extraClaims: [],\n}\n\nfunction fieldErrors(fields: Fields) {\n  const result = schema.safeParse(fields)\n  const errors: Partial<Record<keyof Fields, string>> = {}\n  if (!result.success) {\n    for (const issue of result.error.issues) {\n      const key = issue.path[0] as keyof Fields | undefined\n      if (key && !errors[key]) {\n        errors[key] = issue.message\n      }\n    }\n  }\n  return { valid: result.success, errors }\n}\n\nconst FIRST_CLASS_CLAIMS = [\"aud\", \"sub\"]\n\nconst SUB_PREFIX = \"system:serviceaccount:\"\n\nfunction parsePastedIssuer(text: string) {\n  const match = text\n    .trim()\n    .match(\n      /^(?:https:\\/\\/)?(oidc\\.eks\\.[\\w-]+\\.amazonaws\\.com\\/id\\/[A-Za-z0-9]+)\\/?$/\n    )\n  return match ? { issuer: `https://${match[1]}` } : null\n}\n\n// EKS is AWS's OIDC workload identity story: each cluster is its own issuer\n// and pods authenticate as Kubernetes service accounts.\nfunction compile(fields: Fields) {\n  const claims: Record<string, string[]> = {}\n  if (fields.audience) {\n    claims.aud = [fields.audience]\n  }\n  if (fields.namespace && fields.serviceAccount) {\n    claims.sub = [`${SUB_PREFIX}${fields.namespace}:${fields.serviceAccount}`]\n  }\n  mergeClaimRows(claims, fields.extraClaims, FIRST_CLASS_CLAIMS)\n  return { issuer: fields.issuer, claims }\n}\n\nfunction parse(policy: OidcPolicy) {\n  if (!policy.issuer.startsWith(\"https://\")) {\n    return null\n  }\n  if (\n    FIRST_CLASS_CLAIMS.some((name) => (policy.claims[name]?.length ?? 0) > 1)\n  ) {\n    return null\n  }\n  const extraClaims = claimRowsFromPolicy(policy.claims, FIRST_CLASS_CLAIMS)\n  if (extraClaims === null) {\n    return null\n  }\n\n  const [sub = \"\"] = policy.claims.sub ?? []\n  if (!sub.startsWith(SUB_PREFIX)) {\n    return null\n  }\n  const segments = sub.slice(SUB_PREFIX.length).split(\":\")\n  if (segments.length !== 2 || !segments[0] || !segments[1]) {\n    return null\n  }\n\n  return {\n    issuer: policy.issuer,\n    namespace: segments[0],\n    serviceAccount: segments[1],\n    audience: policy.claims.aud?.[0] ?? \"\",\n    extraClaims,\n  }\n}\n\n/** True when this form can represent the policy — for routing an edit. */\nfunction matchesAwsPolicy(policy: OidcPolicy) {\n  return parse(policy) !== null\n}\n\nfunction AwsPolicyForm({\n  defaultValue,\n  onChange,\n  onSubmit,\n  className,\n}: {\n  defaultValue?: OidcPolicy\n  onChange?: (policy: OidcPolicy) => void\n  onSubmit: (policy: OidcPolicy) => void\n  className?: string\n}) {\n  const [fields, setFields] = React.useState<Fields>(() =>\n    defaultValue ? (parse(defaultValue) ?? DEFAULTS) : DEFAULTS\n  )\n  const [attempted, setAttempted] = React.useState(false)\n  const { errors } = fieldErrors(fields)\n\n  const apply = (next: Fields) => {\n    setFields(next)\n    const policy = compile(next)\n    onChange?.(\n      defaultValue?.label ? { ...policy, label: defaultValue.label } : policy\n    )\n  }\n\n  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault()\n    if (!(\n      fieldErrors(fields).valid &&\n      claimsListValid(fields.extraClaims, FIRST_CLASS_CLAIMS)\n    )) {\n      setAttempted(true)\n      return\n    }\n    const policy = compile(fields)\n    onSubmit(\n      defaultValue?.label ? { ...policy, label: defaultValue.label } : policy\n    )\n  }\n\n  return (\n    <form\n      data-slot=\"aws-policy-form\"\n      className={cn(\"flex flex-col gap-4\", className)}\n      onSubmit={handleSubmit}\n    >\n      <Field data-invalid={!!(errors.issuer && (fields.issuer || attempted))}>\n        <FieldLabel htmlFor=\"aws-issuer\" className=\"w-full\">\n          Cluster OIDC issuer URL <ClaimChip>iss</ClaimChip>\n        </FieldLabel>\n        <Input\n          id=\"aws-issuer\"\n          value={fields.issuer}\n          placeholder=\"https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE…\"\n          className=\"font-mono\"\n          aria-invalid={!!(errors.issuer && (fields.issuer || attempted))}\n          onChange={(event) =>\n            apply({ ...fields, issuer: event.target.value.trim() })\n          }\n          onPaste={(event) => {\n            const patch = parsePastedIssuer(event.clipboardData.getData(\"text\"))\n            if (!patch) {\n              return\n            }\n            event.preventDefault()\n            apply({ ...fields, ...patch })\n          }}\n        />\n        {!!(errors.issuer && (fields.issuer || attempted)) ? (\n          <FieldError>{errors.issuer}</FieldError>\n        ) : null}\n        <FieldDescription>\n          EKS console → your cluster → Overview → OpenID Connect provider URL.\n        </FieldDescription>\n      </Field>\n      <Field\n        data-invalid={!!(errors.namespace && (fields.namespace || attempted))}\n      >\n        <FieldLabel htmlFor=\"aws-namespace\" className=\"w-full\">\n          Namespace <ClaimChip>sub</ClaimChip>\n        </FieldLabel>\n        <Input\n          id=\"aws-namespace\"\n          value={fields.namespace}\n          placeholder=\"default\"\n          aria-invalid={!!(errors.namespace && (fields.namespace || attempted))}\n          onChange={(event) =>\n            apply({ ...fields, namespace: event.target.value.trim() })\n          }\n        />\n        {!!(errors.namespace && (fields.namespace || attempted)) ? (\n          <FieldError>{errors.namespace}</FieldError>\n        ) : null}\n      </Field>\n      <Field\n        data-invalid={\n          !!(errors.serviceAccount && (fields.serviceAccount || attempted))\n        }\n      >\n        <FieldLabel htmlFor=\"aws-serviceAccount\" className=\"w-full\">\n          Service account <ClaimChip>sub</ClaimChip>\n        </FieldLabel>\n        <Input\n          id=\"aws-serviceAccount\"\n          value={fields.serviceAccount}\n          placeholder=\"deploy-bot\"\n          aria-invalid={\n            !!(errors.serviceAccount && (fields.serviceAccount || attempted))\n          }\n          onChange={(event) =>\n            apply({ ...fields, serviceAccount: event.target.value.trim() })\n          }\n        />\n        {!!(errors.serviceAccount && (fields.serviceAccount || attempted)) ? (\n          <FieldError>{errors.serviceAccount}</FieldError>\n        ) : null}\n      </Field>\n      <Field\n        data-invalid={!!(errors.audience && (fields.audience || attempted))}\n      >\n        <FieldLabel htmlFor=\"aws-audience\" className=\"w-full\">\n          Audience <ClaimChip>aud</ClaimChip>\n        </FieldLabel>\n        <Input\n          id=\"aws-audience\"\n          value={fields.audience}\n          placeholder=\"https://your-app.example.com\"\n          aria-invalid={!!(errors.audience && (fields.audience || attempted))}\n          onChange={(event) =>\n            apply({ ...fields, audience: event.target.value.trim() })\n          }\n        />\n        {!!(errors.audience && (fields.audience || attempted)) ? (\n          <FieldError>{errors.audience}</FieldError>\n        ) : null}\n        <FieldDescription>\n          The audience in your pod&apos;s projected service-account token.\n        </FieldDescription>\n      </Field>\n      <ClaimsList\n        value={fields.extraClaims}\n        onChange={(rows) => apply({ ...fields, extraClaims: rows })}\n        reserved={FIRST_CLASS_CLAIMS}\n      />\n      <Button type=\"submit\" className=\"self-start\">\n        Save\n      </Button>\n    </form>\n  )\n}\n\nexport { AwsIcon, AwsPolicyForm, matchesAwsPolicy }\n",
      "type": "registry:component",
      "target": "@components/oidc/provider-aws.tsx"
    }
  ],
  "meta": {
    "tagline": "Trust a cluster's service account"
  },
  "categories": [
    "auth",
    "forms"
  ],
  "type": "registry:component"
}